Compare commits

..

1 Commits
v2.7 ... jstack

Author SHA1 Message Date
Andrey Pangin
a20d771635 Get asynchronous thread dump (jstack) 2021-01-30 01:55:07 +03:00
107 changed files with 4965 additions and 10230 deletions

View File

@@ -1,21 +0,0 @@
name: C++ CI
on:
- push
- pull_request
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2.1.0
with:
distribution: "adopt"
java-version: "11"
- run: sudo sysctl kernel.perf_event_paranoid=1
- run: make
- run: make test
- uses: actions/upload-artifact@v2
with:
path: build/

View File

@@ -1,6 +1,6 @@
language: cpp
dist: bionic
dist: precise
sudo: required

View File

@@ -1,98 +1,21 @@
# Changelog
## [2.7] - 2022-02-14
### Features
- Experimental support for OpenJ9 VM
- DWARF stack unwinding
### Improvements
- Better handling of VM threads (fixed missing JIT threads)
- More reliable recovery from `not_walkable` AGCT failures
- Do not accept unknown agent arguments
## [2.6] - 2022-01-09
### Features
- Continuous profiling; `loop` and `timeout` options
### Improvements
- Reliability improvements: avoid certain crashes and deadlocks
- Smaller and faster agent library
- Minor `jfr` and `jfrsync` enhancements (see the commit log)
## [2.5.1] - 2021-12-05
### Bug fixes
- Prevent early unloading of libasyncProfiler.so
- Read kernel symbols only for perf_events
- Escape backslashes in flame graphs
- Avoid duplicate categories in `jfrsync` mode
- Fixed stack overflow in RedefineClasses
- Fixed deadlock when flushing JFR
### Improvements
- Support OpenJDK C++ Interpreter (aka Zero)
- Allow reading incomplete JFR recordings
## [2.5] - 2021-10-01
### Features
- macOS/ARM64 (aka Apple M1) port
- PPC64LE port (contributed by @ghaug)
- Profile low-privileged processes with perf_events (contributed by @Jongy)
- Raw PMU events; kprobes & uprobes
- Dump results in the middle of profiling session
- Chunked JFR; support JFR files larger than 2 GB
- Integrate async-profiler events with JDK Flight Recordings
### Improvements
- Use RDTSC for JFR timestamps when possible
- Show line numbers and bci in Flame Graphs
- jfr2flame can produce Allocation and Lock flame graphs
- Flame Graph title depends on the event and `--total`
- Include profiler logs and native library list in JFR output
- Lock profiling no longer requires JVM symbols
- Better container support
- Native function profiler can count the specified argument
- An option to group threads by scheduling policy
- An option to prepend library name to native symbols
### Notes
- macOS build is provided as a fat binary that works both on x86-64 and ARM64
- 32-bit binaries are no longer shipped. It is still possible to build them from sources
- Dropped JDK 6 support (may still work though)
## [2.0] - 2021-03-14
## [2.0-b1] - Early access
### Features
- Profile multiple events together (cpu + alloc + lock)
- HTML 5 Flame Graphs: faster rendering, smaller size
- JFR v2 output format, compatible with FlightRecorder API
- JFR to Flame Graph converter
- Automatically turn profiling on/off at `--begin`/`--end` functions
- Time-to-safepoint profiling: `--ttsp`
- Time-to-safepoint profiling
### Improvements
- Unlimited frame buffer. Removed `-b` option and 64K stack traces limit
- Additional JFR events: OS, CPU, and JVM information; CPU load
- Record bytecode indices / line numbers
- Native stack traces for Java events
- Improved CLI experience
- Better error handling; an option to log warnings/errors to a dedicated stream
- Reduced the amount of unknown stack traces
- Record CPU load in JFR format
### Changes
- Removed non-ASL code. No more CDDL license
## [1.8.4] - 2021-02-24
### Improvements
- Smaller and faster agent library
### Bug fixes
- Fixed JDK 7 crash during wall-clock profiling
## [1.8.3] - 2021-01-06
### Improvements

View File

@@ -1,4 +1,6 @@
PROFILER_VERSION=2.7
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)
@@ -9,19 +11,16 @@ JATTACH=jattach
API_JAR=async-profiler.jar
CONVERTER_JAR=converter.jar
CFLAGS=-O3
CFLAGS=-O3 -fno-omit-frame-pointer -fvisibility=hidden
CXXFLAGS=-O3 -fno-omit-frame-pointer -fvisibility=hidden
INCLUDES=-I$(JAVA_HOME)/include
LIBS=-ldl -lpthread
MERGE=true
JAVAC=$(JAVA_HOME)/bin/javac
JAR=$(JAVA_HOME)/bin/jar
JAVAC_RELEASE_VERSION=7
SOURCES := $(wildcard src/*.cpp)
HEADERS := $(wildcard src/*.h src/fdtransfer/*.h)
JAVA_HEADERS := $(patsubst %.java,%.class.h,$(wildcard src/helper/one/profiler/*.java))
HEADERS := $(wildcard src/*.h)
API_SOURCES := $(wildcard src/api/one/profiler/*.java)
CONVERTER_SOURCES := $(shell find src/converter -name '*.java')
@@ -30,29 +29,15 @@ ifeq ($(JAVA_HOME),)
endif
OS:=$(shell uname -s)
ifeq ($(OS),Darwin)
ifeq ($(OS), Darwin)
CXXFLAGS += -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE
INCLUDES += -I$(JAVA_HOME)/include/darwin
FDTRANSFER_BIN=
SOEXT=dylib
PACKAGE_EXT=zip
OS_TAG=macos
ifeq ($(FAT_BINARY),true)
FAT_BINARY_FLAGS=-arch x86_64 -arch arm64 -mmacos-version-min=10.12
CFLAGS += $(FAT_BINARY_FLAGS)
CXXFLAGS += $(FAT_BINARY_FLAGS)
PACKAGE_NAME=async-profiler-$(PROFILER_VERSION)-$(OS_TAG)
endif
else
CXXFLAGS += -Wl,-z,defs -Wl,-z,nodelete
ifeq ($(MERGE),true)
CXXFLAGS += -fwhole-program
endif
LIBS += -lrt
INCLUDES += -I$(JAVA_HOME)/include/linux
FDTRANSFER_BIN=build/fdtransfer
SOEXT=so
PACKAGE_EXT=tar.gz
ifeq ($(findstring musl,$(shell ldd /bin/ls)),musl)
OS_TAG=linux-musl
else
@@ -65,97 +50,62 @@ ifeq ($(ARCH),x86_64)
ARCH_TAG=x64
else
ifeq ($(findstring arm,$(ARCH)),arm)
ifeq ($(findstring 64,$(ARCH)),64)
ARCH_TAG=arm64
else
ARCH_TAG=arm32
endif
ARCH_TAG=arm
else
ifeq ($(findstring aarch64,$(ARCH)),aarch64)
ARCH_TAG=arm64
ARCH_TAG=aarch64
else
ifeq ($(ARCH),ppc64le)
ARCH_TAG=ppc64le
else
ARCH_TAG=x86
endif
ARCH_TAG=x86
endif
endif
endif
ifneq ($(ARCH),ppc64le)
CXXFLAGS += -momit-leaf-frame-pointer
endif
.PHONY: all release test clean
all: build build/$(LIB_PROFILER) build/$(JATTACH) $(FDTRANSFER_BIN) build/$(API_JAR) build/$(CONVERTER_JAR)
all: build build/$(LIB_PROFILER) build/$(JATTACH) build/$(API_JAR) build/$(CONVERTER_JAR)
release: build $(PACKAGE_NAME).$(PACKAGE_EXT)
release: build $(PACKAGE_NAME).tar.gz
$(PACKAGE_NAME).tar.gz: $(PACKAGE_DIR)
tar czf $@ -C $(PACKAGE_DIR)/.. $(PACKAGE_NAME)
rm -r $(PACKAGE_DIR)
$(PACKAGE_NAME).zip: $(PACKAGE_DIR)
codesign -s "Developer ID" -o runtime --timestamp -v $(PACKAGE_DIR)/build/$(JATTACH) $(PACKAGE_DIR)/build/$(LIB_PROFILER_SO)
ditto -c -k --keepParent $(PACKAGE_DIR) $@
rm -r $(PACKAGE_DIR)
$(PACKAGE_DIR): build/$(LIB_PROFILER) build/$(JATTACH) $(FDTRANSFER_BIN) \
build/$(API_JAR) build/$(CONVERTER_JAR) \
profiler.sh LICENSE *.md
$(PACKAGE_NAME).tar.gz: build/$(LIB_PROFILER) build/$(JATTACH) \
build/$(API_JAR) build/$(CONVERTER_JAR) \
profiler.sh LICENSE *.md
mkdir -p $(PACKAGE_DIR)
cp -RP build profiler.sh LICENSE *.md $(PACKAGE_DIR)
chmod -R 755 $(PACKAGE_DIR)
chmod 644 $(PACKAGE_DIR)/LICENSE $(PACKAGE_DIR)/*.md $(PACKAGE_DIR)/build/*.jar
tar cvzf $@ -C $(PACKAGE_DIR)/.. $(PACKAGE_NAME)
rm -r $(PACKAGE_DIR)
%.$(SOEXT): %.so
rm -f $@
-ln -s $(<F) $@
build:
mkdir -p build
build/$(LIB_PROFILER_SO): $(SOURCES) $(HEADERS) $(JAVA_HEADERS)
ifeq ($(MERGE),true)
for f in src/*.cpp; do echo '#include "'$$f'"'; done |\
$(CXX) $(CXXFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -fPIC -shared -o $@ -xc++ - $(LIBS)
else
build/$(LIB_PROFILER_SO): $(SOURCES) $(HEADERS)
$(CXX) $(CXXFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -fPIC -shared -o $@ $(SOURCES) $(LIBS)
endif
build/$(JATTACH): src/jattach/*.c src/jattach/*.h
$(CC) $(CFLAGS) -DJATTACH_VERSION=\"$(PROFILER_VERSION)-ap\" -o $@ src/jattach/*.c
build/fdtransfer: src/fdtransfer/*.cpp src/fdtransfer/*.h src/jattach/psutil.c src/jattach/psutil.h
$(CXX) $(CFLAGS) -o $@ src/fdtransfer/*.cpp src/jattach/psutil.c
build/$(JATTACH): src/jattach/jattach.c
$(CC) $(CFLAGS) -DJATTACH_VERSION=\"$(JATTACH_VERSION)\" -o $@ $^
build/$(API_JAR): $(API_SOURCES)
mkdir -p build/api
$(JAVAC) -source $(JAVAC_RELEASE_VERSION) -target $(JAVAC_RELEASE_VERSION) -d build/api $^
$(JAR) cf $@ -C build/api .
$(JAR) cvf $@ -C build/api .
$(RM) -r build/api
build/$(CONVERTER_JAR): $(CONVERTER_SOURCES) src/converter/MANIFEST.MF
mkdir -p build/converter
$(JAVAC) -source 7 -target 7 -d build/converter $(CONVERTER_SOURCES)
$(JAR) cfm $@ src/converter/MANIFEST.MF -C build/converter .
$(JAR) cvfm $@ src/converter/MANIFEST.MF -C build/converter .
$(RM) -r build/converter
%.class.h: %.class
hexdump -v -e '1/1 "%u,"' $^ > $@
%.class: %.java
$(JAVAC) -g:none -source $(JAVAC_RELEASE_VERSION) -target $(JAVAC_RELEASE_VERSION) $(*D)/*.java
test: all
test/smoke-test.sh
test/thread-smoke-test.sh
test/alloc-smoke-test.sh
test/load-library-test.sh
test/fdtransfer-smoke-test.sh
echo "All tests passed"
clean:

613
README.md
View File

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

2247
demo/SwingSet2.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 141 KiB

View File

@@ -1,877 +0,0 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<style>
body {margin: 0; padding: 10px; background-color: #ffffff}
h1 {margin: 5px 0 0 0; font-size: 18px; font-weight: normal; text-align: center}
header {margin: -24px 0 5px 0; line-height: 24px}
button {font: 12px sans-serif; cursor: pointer}
p {margin: 5px 0 5px 0}
a {color: #0366d6}
#hl {position: absolute; display: none; overflow: hidden; white-space: nowrap; pointer-events: none; background-color: #ffffe0; outline: 1px solid #ffc000; height: 15px}
#hl span {padding: 0 3px 0 3px}
#status {overflow: hidden; white-space: nowrap}
#match {overflow: hidden; white-space: nowrap; display: none; float: right; text-align: right}
#reset {cursor: pointer}
</style>
</head>
<body style='font: 12px Verdana, sans-serif'>
<h1>Flame Graph</h1>
<header style='text-align: left'><button id='reverse' title='Reverse'>&#x1f53b;</button>&nbsp;&nbsp;<button id='search' title='Search'>&#x1f50d;</button></header>
<header style='text-align: right'>Produced by <a href='https://github.com/jvm-profiling-tools/async-profiler'>async-profiler</a></header>
<canvas id='canvas' style='width: 100%; height: 752px'></canvas>
<div id='hl'><span></span></div>
<p id='match'>Matched: <span id='matchval'></span> <span id='reset' title='Clear'>&#x274c;</span></p>
<p id='status'>&nbsp;</p>
<script>
// Copyright 2020 Andrei Pangin
// Licensed under the Apache License, Version 2.0.
'use strict';
var root, rootLevel, px, pattern;
var reverse = false;
const levels = Array(47);
for (let h = 0; h < levels.length; h++) {
levels[h] = [];
}
const canvas = document.getElementById('canvas');
const c = canvas.getContext('2d');
const hl = document.getElementById('hl');
const status = document.getElementById('status');
const canvasWidth = canvas.offsetWidth;
const canvasHeight = canvas.offsetHeight;
canvas.style.width = canvasWidth + 'px';
canvas.width = canvasWidth * (devicePixelRatio || 1);
canvas.height = canvasHeight * (devicePixelRatio || 1);
if (devicePixelRatio) c.scale(devicePixelRatio, devicePixelRatio);
c.font = document.body.style.font;
const palette = [
[0x50e150, 30, 30, 30],
[0x50bebe, 30, 30, 30],
[0xe17d00, 30, 30, 0],
[0xc8c83c, 30, 30, 10],
[0xe15a5a, 30, 40, 40],
];
function getColor(p) {
const v = Math.random();
return '#' + (p[0] + ((p[1] * v) << 16 | (p[2] * v) << 8 | (p[3] * v))).toString(16);
}
function f(level, left, width, type, title) {
levels[level].push({left: left, width: width, color: getColor(palette[type]), title: title});
}
function samples(n) {
return n === 1 ? '1 sample' : n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' samples';
}
function pct(a, b) {
return a >= b ? '100' : (100 * a / b).toFixed(2);
}
function findFrame(frames, x) {
let left = 0;
let right = frames.length - 1;
while (left <= right) {
const mid = (left + right) >>> 1;
const f = frames[mid];
if (f.left > x) {
right = mid - 1;
} else if (f.left + f.width <= x) {
left = mid + 1;
} else {
return f;
}
}
if (frames[left] && (frames[left].left - x) * px < 0.5) return frames[left];
if (frames[right] && (x - (frames[right].left + frames[right].width)) * px < 0.5) return frames[right];
return null;
}
function search(r) {
if (r && (r = prompt('Enter regexp to search:', '')) === null) {
return;
}
pattern = r ? RegExp(r) : undefined;
const matched = render(root, rootLevel);
document.getElementById('matchval').textContent = pct(matched, root.width) + '%';
document.getElementById('match').style.display = r ? 'inherit' : 'none';
}
function render(newRoot, newLevel) {
if (root) {
c.fillStyle = '#ffffff';
c.fillRect(0, 0, canvasWidth, canvasHeight);
}
root = newRoot || levels[0][0];
rootLevel = newLevel || 0;
px = canvasWidth / root.width;
const x0 = root.left;
const x1 = x0 + root.width;
const marked = [];
function mark(f) {
return marked[f.left] >= f.width || (marked[f.left] = f.width);
}
function totalMarked() {
let total = 0;
let left = 0;
for (let x in marked) {
if (+x >= left) {
total += marked[x];
left = +x + marked[x];
}
}
return total;
}
function drawFrame(f, y, alpha) {
if (f.left < x1 && f.left + f.width > x0) {
c.fillStyle = pattern && f.title.match(pattern) && mark(f) ? '#ee00ee' : f.color;
c.fillRect((f.left - x0) * px, y, f.width * px, 15);
if (f.width * px >= 21) {
const chars = Math.floor(f.width * px / 7);
const title = f.title.length <= chars ? f.title : f.title.substring(0, chars - 2) + '..';
c.fillStyle = '#000000';
c.fillText(title, Math.max(f.left - x0, 0) * px + 3, y + 12, f.width * px - 6);
}
if (alpha) {
c.fillStyle = 'rgba(255, 255, 255, 0.5)';
c.fillRect((f.left - x0) * px, y, f.width * px, 15);
}
}
}
for (let h = 0; h < levels.length; h++) {
const y = reverse ? h * 16 : canvasHeight - (h + 1) * 16;
const frames = levels[h];
for (let i = 0; i < frames.length; i++) {
drawFrame(frames[i], y, h < rootLevel);
}
}
return totalMarked();
}
canvas.onmousemove = function() {
const h = Math.floor((reverse ? event.offsetY : (canvasHeight - event.offsetY)) / 16);
if (h >= 0 && h < levels.length) {
const f = findFrame(levels[h], event.offsetX / px + root.left);
if (f) {
hl.style.left = (Math.max(f.left - root.left, 0) * px + canvas.offsetLeft) + 'px';
hl.style.width = (Math.min(f.width, root.width) * px) + 'px';
hl.style.top = ((reverse ? h * 16 : canvasHeight - (h + 1) * 16) + canvas.offsetTop) + 'px';
hl.firstChild.textContent = f.title;
hl.style.display = 'block';
canvas.title = f.title + '\n(' + samples(f.width) + ', ' + pct(f.width, levels[0][0].width) + '%)';
canvas.style.cursor = 'pointer';
canvas.onclick = function() {
if (f != root) {
render(f, h);
canvas.onmousemove();
}
};
status.textContent = 'Function: ' + canvas.title;
return;
}
}
canvas.onmouseout();
}
canvas.onmouseout = function() {
hl.style.display = 'none';
status.textContent = '\xa0';
canvas.title = '';
canvas.style.cursor = '';
canvas.onclick = '';
}
document.getElementById('reverse').onclick = function() {
reverse = !reverse;
render();
}
document.getElementById('search').onclick = function() {
search(true);
}
document.getElementById('reset').onclick = function() {
search(false);
}
window.onkeydown = function() {
if (event.ctrlKey && event.keyCode === 70) {
event.preventDefault();
search(true);
} else if (event.keyCode === 27) {
search(false);
}
}
f(0,0,641,4,'all')
f(1,0,5,0,'com/sun/glass/ui/InvokeLaterDispatcher.run')
f(2,0,4,0,'com/sun/glass/ui/gtk/GtkApplication.submitForLaterInvocation')
f(3,0,4,0,'com/sun/glass/ui/gtk/GtkApplication._submitForLaterInvocation')
f(4,0,4,4,'Java_com_sun_glass_ui_gtk_GtkApplication__1submitForLaterInvocation')
f(5,0,4,4,'__write')
f(6,0,4,2,'entry_SYSCALL_64_after_hwframe')
f(7,0,4,2,'do_syscall_64')
f(8,0,4,2,'__x64_sys_write')
f(9,0,4,2,'ksys_write')
f(10,0,4,2,'vfs_write')
f(11,0,4,2,'__vfs_write')
f(12,0,4,2,'eventfd_write')
f(1,5,408,0,'java/lang/Thread.run')
f(2,5,179,0,'com/sun/glass/ui/gtk/GtkApplication$$Lambda$42/1642360923.run')
f(3,5,179,0,'com/sun/glass/ui/gtk/GtkApplication.lambda$null$48')
f(4,5,179,0,'com/sun/glass/ui/gtk/GtkApplication._runLoop')
f(5,5,4,4,'Java_com_sun_glass_ui_gtk_GtkApplication__1runLoop')
f(6,6,2,4,'__writev')
f(7,6,2,2,'entry_SYSCALL_64_after_hwframe')
f(8,6,2,2,'do_syscall_64')
f(9,6,2,2,'__x64_sys_writev')
f(10,6,2,2,'do_writev')
f(11,6,2,2,'vfs_writev')
f(12,6,2,2,'do_iter_write')
f(13,6,2,2,'do_iter_readv_writev')
f(14,6,2,2,'sock_write_iter')
f(15,6,2,2,'sock_sendmsg')
f(16,6,2,2,'inet_sendmsg')
f(17,6,2,2,'tcp_sendmsg')
f(18,6,2,2,'tcp_sendmsg_locked')
f(19,6,2,2,'tcp_push')
f(20,6,2,2,'__tcp_push_pending_frames')
f(21,6,2,2,'tcp_write_xmit')
f(22,6,2,2,'__tcp_transmit_skb')
f(23,6,2,2,'ip_queue_xmit')
f(24,6,2,2,'__ip_queue_xmit')
f(25,6,2,2,'ip_local_out')
f(26,6,2,2,'ip_output')
f(27,6,2,2,'ip_finish_output')
f(28,6,2,2,'__ip_finish_output')
f(29,6,2,2,'ip_finish_output2')
f(30,6,2,2,'dev_queue_xmit')
f(31,6,2,2,'__dev_queue_xmit')
f(32,6,2,2,'sch_direct_xmit')
f(33,6,2,2,'dev_hard_start_xmit')
f(34,6,2,2,'e1000_xmit_frame?[e1000]')
f(5,10,161,0,'com/sun/glass/ui/InvokeLaterDispatcher$Future.run')
f(6,10,6,3,'InterpreterRuntime::monitorexit(JavaThread*, BasicObjectLock*)')
f(7,10,6,3,'ObjectMonitor::ExitEpilog(Thread*, ObjectWaiter*)')
f(8,10,6,4,'pthread_cond_signal@@GLIBC_2.3.2')
f(9,10,6,2,'entry_SYSCALL_64_after_hwframe')
f(10,10,6,2,'do_syscall_64')
f(11,10,6,2,'__x64_sys_futex')
f(12,10,6,2,'do_futex')
f(13,10,6,2,'wake_up_q')
f(14,10,6,2,'try_to_wake_up')
f(15,10,6,2,'_raw_spin_unlock_irqrestore')
f(6,16,58,0,'com/sun/javafx/application/PlatformImpl$$Lambda$53/233530418.run')
f(7,16,58,0,'com/sun/javafx/application/PlatformImpl.lambda$runLater$173')
f(8,17,57,0,'java/security/AccessController.doPrivileged')
f(9,17,57,0,'com/sun/javafx/application/PlatformImpl$$Lambda$54/1140247440.run')
f(10,17,57,0,'com/sun/javafx/application/PlatformImpl.lambda$null$172')
f(11,17,55,0,'com/sun/javafx/application/PlatformImpl$$Lambda$52/1364335809.run')
f(12,17,55,0,'com/sun/javafx/application/PlatformImpl.lambda$runAndWait$174')
f(13,17,4,0,'com/sun/javafx/application/LauncherImpl$$Lambda$57/1790390841.run')
f(14,17,4,0,'com/sun/javafx/application/LauncherImpl.lambda$launchApplication1$160')
f(15,17,4,0,'java/lang/reflect/Constructor.newInstance')
f(16,17,4,0,'sun/reflect/DelegatingConstructorAccessorImpl.newInstance')
f(17,17,4,0,'sun/reflect/NativeConstructorAccessorImpl.newInstance')
f(18,17,4,0,'sun/reflect/NativeConstructorAccessorImpl.newInstance0')
f(19,17,4,0,'demo/parallel/Main.<init>')
f(20,17,2,0,'java/lang/ClassLoader.loadClass')
f(21,17,2,0,'java/lang/ClassLoader.loadClass')
f(22,17,2,0,'java/net/URLClassLoader.findClass')
f(23,17,2,0,'java/security/AccessController.doPrivileged')
f(24,17,2,0,'java/net/URLClassLoader$1.run')
f(25,17,2,0,'java/net/URLClassLoader$1.run')
f(26,17,2,0,'java/net/URLClassLoader.access$100')
f(27,17,2,0,'java/net/URLClassLoader.defineClass')
f(28,17,2,0,'java/security/SecureClassLoader.defineClass')
f(13,21,50,0,'com/sun/javafx/application/LauncherImpl$$Lambda$63/508611611.run')
f(14,21,50,0,'com/sun/javafx/application/LauncherImpl.lambda$launchApplication1$161')
f(15,21,49,0,'demo/parallel/Main.start')
f(16,22,17,0,'demo/parallel/Main.createContent')
f(17,23,14,0,'demo/parallel/Main.createControlPane')
f(18,26,8,0,'javafx/scene/control/Control.<clinit>')
f(19,26,8,0,'com/sun/javafx/application/PlatformImpl.setDefaultPlatformUserAgentStylesheet')
f(20,26,8,0,'com/sun/javafx/application/PlatformImpl.setPlatformUserAgentStylesheet')
f(21,26,8,0,'com/sun/javafx/application/PlatformImpl._setPlatformUserAgentStylesheet')
f(22,27,7,0,'java/security/AccessController.doPrivileged')
f(23,27,7,0,'com/sun/javafx/application/PlatformImpl$$Lambda$68/360857571.run')
f(24,27,7,0,'com/sun/javafx/application/PlatformImpl.lambda$_setPlatformUserAgentStylesheet$181')
f(25,27,7,0,'com/sun/javafx/css/StyleManager.setUserAgentStylesheets')
f(26,27,6,0,'com/sun/javafx/css/StyleManager._setDefaultUserAgentStylesheet')
f(27,28,5,0,'com/sun/javafx/css/StyleManager.loadStylesheet')
f(28,28,5,0,'com/sun/javafx/css/StyleManager.loadStylesheetUnPrivileged')
f(29,28,2,0,'com/sun/javafx/css/StyleManager.getURL')
f(30,28,2,0,'java/lang/Class.forName')
f(31,28,2,0,'java/lang/Class.forName0')
f(32,28,2,0,'com/sun/javafx/scene/control/skin/Utils.<clinit>')
f(29,30,3,0,'com/sun/javafx/css/Stylesheet.loadBinary')
f(30,30,3,0,'com/sun/javafx/css/Stylesheet.readBinary')
f(31,30,3,0,'com/sun/javafx/css/Rule.readBinary')
f(32,30,3,0,'com/sun/javafx/css/Selector.readBinary')
f(33,30,2,0,'com/sun/javafx/css/CompoundSelector.readBinary')
f(16,40,11,0,'javafx/scene/Scene.<init>')
f(17,40,11,0,'javafx/scene/Scene.<init>')
f(18,40,11,0,'javafx/scene/Scene.setRoot')
f(19,40,11,0,'javafx/beans/property/ObjectPropertyBase.set')
f(20,40,11,0,'javafx/beans/property/ObjectPropertyBase.markInvalid')
f(21,40,11,0,'javafx/scene/Scene$9.invalidated')
f(22,40,11,0,'javafx/scene/Node.setScenes')
f(23,40,11,0,'javafx/scene/Node.invalidatedScenes')
f(24,40,11,0,'javafx/scene/Node.impl_reapplyCSS')
f(25,41,10,0,'javafx/scene/Node.reapplyCss')
f(26,42,3,0,'javafx/scene/CssStyleHelper.<clinit>')
f(27,42,3,0,'javafx/scene/text/Font.getDefault')
f(28,42,3,0,'javafx/scene/text/Font.<init>')
f(29,42,3,0,'com/sun/javafx/font/PrismFontLoader.loadFont')
f(30,42,2,0,'com/sun/javafx/font/PrismFontFactory.createFont')
f(31,42,2,0,'com/sun/javafx/font/LogicalFont.getLogicalFont')
f(32,42,2,0,'com/sun/javafx/font/LogicalFont.getLogicalFont')
f(33,42,2,0,'com/sun/javafx/font/LogicalFont.<init>')
f(34,42,2,0,'com/sun/javafx/font/FontConfigManager.getFontConfigFont')
f(35,42,2,0,'com/sun/javafx/font/FontConfigManager.initFontConfigLogFonts')
f(36,42,2,0,'com/sun/javafx/font/FontConfigManager.getFontConfig')
f(26,45,2,0,'javafx/scene/CssStyleHelper.createStyleHelper')
f(26,47,4,0,'javafx/scene/Node.reapplyCss')
f(27,47,3,0,'javafx/scene/CssStyleHelper.createStyleHelper')
f(28,47,3,0,'com/sun/javafx/css/StyleManager.findMatchingStyles')
f(29,47,3,0,'com/sun/javafx/css/StyleManager.gatherParentStylesheets')
f(30,47,3,0,'com/sun/javafx/css/StyleManager.processStylesheets')
f(31,47,3,0,'com/sun/javafx/css/StyleManager.loadStylesheet')
f(32,47,3,0,'com/sun/javafx/css/StyleManager.loadStylesheetUnPrivileged')
f(33,48,2,0,'com/sun/javafx/css/parser/CSSParser.parse')
f(34,48,2,0,'com/sun/javafx/css/parser/CSSParser.parse')
f(16,51,19,0,'javafx/stage/Stage.show')
f(17,51,19,0,'javafx/stage/Window.show')
f(18,51,19,0,'javafx/stage/Window.setShowing')
f(19,51,19,0,'javafx/beans/property/BooleanPropertyBase.set')
f(20,51,19,0,'javafx/beans/property/BooleanPropertyBase.markInvalid')
f(21,51,19,0,'javafx/stage/Window$9.invalidated')
f(22,52,3,0,'javafx/scene/Scene.impl_initPeer')
f(22,55,14,0,'javafx/scene/Scene.impl_preferredSize')
f(23,55,14,0,'javafx/scene/Scene.preferredSize')
f(24,55,11,0,'javafx/scene/Scene.doCSSPass')
f(25,55,11,0,'javafx/scene/Node.processCSS')
f(26,55,11,0,'javafx/scene/Parent.impl_processCSS')
f(27,56,10,0,'javafx/scene/Parent.impl_processCSS')
f(28,56,10,0,'javafx/scene/control/Control.impl_processCSS')
f(29,56,3,0,'javafx/scene/Parent.impl_processCSS')
f(30,56,3,0,'javafx/scene/Node.impl_processCSS')
f(31,56,3,0,'javafx/scene/CssStyleHelper.transitionToState')
f(29,59,4,0,'javafx/scene/control/Button.createDefaultSkin')
f(30,59,4,0,'com/sun/javafx/scene/control/skin/ButtonSkin.<init>')
f(31,59,4,0,'com/sun/javafx/scene/control/skin/LabeledSkinBase.<init>')
f(32,61,2,0,'com/sun/javafx/scene/control/skin/LabeledText.<init>')
f(33,61,2,0,'javafx/css/StyleableObjectProperty.bind')
f(34,61,2,0,'javafx/beans/property/ObjectPropertyBase.bind')
f(35,61,2,0,'javafx/beans/property/ObjectPropertyBase.markInvalid')
f(36,61,2,0,'javafx/scene/text/Text$5.invalidated')
f(37,61,2,0,'javafx/scene/text/Text.access$200')
f(38,61,2,0,'javafx/scene/text/Text.needsFullTextLayout')
f(39,61,2,0,'javafx/scene/text/Text.getTextLayout')
f(40,61,2,0,'com/sun/javafx/text/PrismTextLayout.setContent')
f(41,61,2,0,'com/sun/javafx/font/PrismFont.getStrike')
f(42,61,2,0,'com/sun/javafx/font/LogicalFont.getStrike')
f(43,61,2,0,'com/sun/javafx/font/LogicalFont.getDefaultAAMode')
f(44,61,2,0,'com/sun/javafx/font/LogicalFont.getSlot0Resource')
f(45,61,2,0,'com/sun/javafx/font/PrismFontFactory.getFontResource')
f(46,61,2,0,'com/sun/javafx/font/PrismFontFactory.getFullNameToFileMap')
f(29,64,2,0,'javafx/scene/control/ProgressIndicator.createDefaultSkin')
f(24,67,2,0,'javafx/scene/Scene.resizeRootToPreferredSize')
f(25,67,2,0,'javafx/scene/Scene.getPreferredWidth')
f(26,67,2,0,'javafx/scene/layout/Region.prefWidth')
f(27,67,2,0,'javafx/scene/Parent.prefWidth')
f(28,67,2,0,'javafx/scene/layout/Region.computePrefWidth')
f(29,67,2,0,'javafx/scene/Parent.computePrefWidth')
f(30,67,2,0,'javafx/scene/layout/Region.prefWidth')
f(31,67,2,0,'javafx/scene/Parent.prefWidth')
f(32,67,2,0,'javafx/scene/layout/GridPane.computePrefWidth')
f(33,67,2,0,'javafx/scene/layout/GridPane.computePrefWidths')
f(34,67,2,0,'javafx/scene/layout/Region.computeChildPrefAreaWidth')
f(35,67,2,0,'javafx/scene/layout/Region.minWidth')
f(36,67,2,0,'javafx/scene/Parent.minWidth')
f(37,67,2,0,'javafx/scene/control/Control.computeMinWidth')
f(38,67,2,0,'com/sun/javafx/scene/control/skin/LabeledSkinBase.computeMinWidth')
f(39,67,2,0,'com/sun/javafx/scene/control/skin/LabeledSkinBase.computeMinLabeledPartWidth')
f(40,67,2,0,'com/sun/javafx/scene/control/skin/Utils.computeTextWidth')
f(41,67,2,0,'com/sun/javafx/text/PrismTextLayout.getBounds')
f(42,67,2,0,'com/sun/javafx/text/PrismTextLayout.ensureLayout')
f(43,67,2,0,'com/sun/javafx/text/PrismTextLayout.layout')
f(44,67,2,0,'com/sun/javafx/text/PrismTextLayout.buildRuns')
f(45,67,2,0,'com/sun/javafx/text/GlyphLayout.breakRuns')
f(6,74,12,0,'com/sun/javafx/tk/quantum/QuantumToolkit$$Lambda$46/1696939523.run')
f(7,74,12,0,'com/sun/javafx/tk/quantum/QuantumToolkit.lambda$runToolkit$403')
f(8,74,12,0,'com/sun/javafx/tk/quantum/QuantumToolkit.pulseFromQueue')
f(9,74,12,0,'com/sun/javafx/tk/quantum/QuantumToolkit.pulse')
f(10,74,12,0,'com/sun/javafx/tk/quantum/QuantumToolkit.pulse')
f(11,74,8,0,'com/sun/javafx/tk/Toolkit.firePulse')
f(12,74,6,0,'com/sun/javafx/tk/Toolkit.runPulse')
f(13,74,6,0,'java/security/AccessController.doPrivileged')
f(14,74,6,0,'com/sun/javafx/tk/Toolkit$$Lambda$193/1813875389.run')
f(15,74,6,0,'com/sun/javafx/tk/Toolkit.lambda$runPulse$29')
f(16,74,6,0,'javafx/scene/Scene$ScenePulseListener.pulse')
f(17,76,4,0,'javafx/scene/Scene.doLayoutPass')
f(18,76,4,0,'javafx/scene/Parent.layout')
f(19,76,4,0,'demo/parallel/Main$1.layoutChildren')
f(20,76,4,0,'javafx/scene/Parent.layoutChildren')
f(21,76,4,0,'javafx/scene/Node.autosize')
f(22,76,2,0,'javafx/scene/layout/Region.prefHeight')
f(23,76,2,0,'javafx/scene/Parent.prefHeight')
f(24,76,2,0,'javafx/scene/layout/GridPane.computePrefHeight')
f(25,76,2,0,'javafx/scene/layout/GridPane.computePrefHeights')
f(22,78,2,0,'javafx/scene/layout/Region.prefWidth')
f(23,78,2,0,'javafx/scene/Parent.prefWidth')
f(24,78,2,0,'javafx/scene/layout/GridPane.computePrefWidth')
f(25,78,2,0,'javafx/scene/layout/GridPane.computePrefWidths')
f(11,82,3,0,'com/sun/javafx/tk/quantum/PaintCollector.renderAll')
f(12,82,3,0,'com/sun/javafx/tk/quantum/ViewScene.repaint')
f(13,82,3,0,'com/sun/javafx/tk/quantum/QuantumToolkit.addRenderJob')
f(14,82,3,0,'com/sun/javafx/tk/quantum/QuantumRenderer.submitRenderJob')
f(15,82,3,0,'java/util/concurrent/AbstractExecutorService.submit')
f(16,82,3,0,'java/util/concurrent/ThreadPoolExecutor.execute')
f(17,82,3,0,'java/util/concurrent/LinkedBlockingQueue.offer')
f(18,82,3,0,'java/util/concurrent/LinkedBlockingQueue.signalNotEmpty')
f(19,82,3,0,'java/util/concurrent/locks/ReentrantLock.unlock')
f(20,82,3,0,'java/util/concurrent/locks/AbstractQueuedSynchronizer.release')
f(21,82,3,0,'java/util/concurrent/locks/AbstractQueuedSynchronizer.unparkSuccessor')
f(22,82,3,0,'java/util/concurrent/locks/LockSupport.unpark')
f(23,82,3,0,'sun/misc/Unsafe.unpark')
f(24,82,3,4,'Unsafe_Unpark')
f(25,82,3,4,'pthread_cond_signal@@GLIBC_2.3.2')
f(26,82,3,2,'entry_SYSCALL_64_after_hwframe')
f(27,82,3,2,'do_syscall_64')
f(28,82,3,2,'__x64_sys_futex')
f(29,82,3,2,'do_futex')
f(30,82,3,2,'wake_up_q')
f(31,82,3,2,'try_to_wake_up')
f(32,82,3,2,'_raw_spin_unlock_irqrestore')
f(6,86,85,0,'com/sun/javafx/tk/quantum/SceneState$$Lambda$205/1725489206.run')
f(7,86,85,0,'com/sun/javafx/tk/quantum/SceneState.lambda$uploadPixels$305')
f(8,86,85,0,'com/sun/javafx/tk/quantum/SceneState.access$001')
f(9,86,85,0,'com/sun/prism/PresentableState.uploadPixels')
f(10,86,85,0,'com/sun/glass/ui/View.uploadPixels')
f(11,86,85,0,'com/sun/glass/ui/gtk/GtkView._uploadPixels')
f(12,86,85,0,'com/sun/glass/ui/gtk/GtkView._uploadPixelsDirect')
f(13,86,85,4,'Java_com_sun_glass_ui_gtk_GtkView__1uploadPixelsDirect')
f(14,86,85,4,'__writev')
f(15,86,85,2,'entry_SYSCALL_64_after_hwframe')
f(16,86,85,2,'do_syscall_64')
f(17,86,85,2,'__x64_sys_writev')
f(18,86,85,2,'do_writev')
f(19,86,85,2,'vfs_writev')
f(20,86,85,2,'do_iter_write')
f(21,86,85,2,'do_iter_readv_writev')
f(22,86,85,2,'sock_write_iter')
f(23,86,85,2,'sock_sendmsg')
f(24,86,85,2,'inet_sendmsg')
f(25,86,85,2,'tcp_sendmsg')
f(26,86,3,2,'lock_sock_nested')
f(27,86,3,2,'_raw_spin_lock_bh')
f(28,86,3,2,'queued_spin_lock_slowpath')
f(29,86,3,2,'native_queued_spin_lock_slowpath')
f(26,90,81,2,'tcp_sendmsg_locked')
f(27,90,3,2,'__sk_flush_backlog')
f(28,90,3,2,'__release_sock')
f(29,90,3,2,'tcp_v4_do_rcv')
f(30,90,3,2,'tcp_rcv_established')
f(31,90,3,2,'__tcp_push_pending_frames')
f(32,90,3,2,'tcp_write_xmit')
f(33,90,3,2,'__tcp_transmit_skb')
f(34,90,3,2,'ip_queue_xmit')
f(35,90,3,2,'__ip_queue_xmit')
f(36,90,3,2,'ip_local_out')
f(37,90,3,2,'ip_output')
f(38,90,3,2,'ip_finish_output')
f(39,90,3,2,'__ip_finish_output')
f(40,90,3,2,'ip_finish_output2')
f(41,90,3,2,'dev_queue_xmit')
f(42,90,3,2,'__dev_queue_xmit')
f(43,90,3,2,'sch_direct_xmit')
f(44,90,3,2,'dev_hard_start_xmit')
f(45,90,3,2,'e1000_xmit_frame?[e1000]')
f(27,93,78,2,'tcp_push_one')
f(28,93,78,2,'tcp_write_xmit')
f(29,93,78,2,'__tcp_transmit_skb')
f(30,93,78,2,'ip_queue_xmit')
f(31,93,78,2,'__ip_queue_xmit')
f(32,93,78,2,'ip_local_out')
f(33,93,78,2,'ip_output')
f(34,93,78,2,'ip_finish_output')
f(35,93,78,2,'__ip_finish_output')
f(36,93,78,2,'ip_finish_output2')
f(37,93,9,2,'__local_bh_enable_ip')
f(38,93,9,2,'do_softirq')
f(39,93,9,2,'do_softirq_own_stack')
f(40,93,9,2,'__softirqentry_text_start')
f(41,93,9,2,'net_rx_action')
f(42,93,9,2,'e1000_clean?[e1000]')
f(37,102,69,2,'dev_queue_xmit')
f(38,102,69,2,'__dev_queue_xmit')
f(39,102,69,2,'sch_direct_xmit')
f(40,102,69,2,'dev_hard_start_xmit')
f(41,102,69,2,'e1000_xmit_frame?[e1000]')
f(5,171,3,0,'com/sun/glass/ui/View.notifyMouse')
f(6,171,3,0,'com/sun/glass/ui/View.handleMouseEvent')
f(7,171,3,0,'com/sun/javafx/tk/quantum/GlassViewEventHandler.handleMouseEvent')
f(8,171,3,0,'com/sun/javafx/tk/quantum/QuantumToolkit.runWithoutRenderLock')
f(9,171,3,0,'com/sun/javafx/tk/quantum/GlassViewEventHandler$$Lambda$181/241733715.get')
f(10,171,3,0,'com/sun/javafx/tk/quantum/GlassViewEventHandler.lambda$handleMouseEvent$353')
f(11,171,3,0,'java/security/AccessController.doPrivileged')
f(12,171,3,0,'com/sun/javafx/tk/quantum/GlassViewEventHandler$MouseEventNotification.run')
f(13,171,3,0,'com/sun/javafx/tk/quantum/GlassViewEventHandler$MouseEventNotification.run')
f(14,171,3,0,'javafx/scene/Scene$ScenePeerListener.mouseEvent')
f(15,171,3,0,'javafx/scene/Scene.impl_processMouseEvent')
f(16,171,3,0,'javafx/scene/Scene$MouseHandler.access$1500')
f(17,171,3,0,'javafx/scene/Scene$MouseHandler.process')
f(18,172,2,0,'javafx/scene/Scene.access$6700')
f(19,172,2,0,'javafx/scene/Scene.pick')
f(20,172,2,0,'javafx/scene/Scene$MouseHandler.access$1600')
f(21,172,2,0,'javafx/scene/Scene$MouseHandler.pickNode')
f(22,172,2,0,'javafx/scene/Node.impl_pickNode')
f(23,172,2,0,'javafx/scene/layout/Region.impl_pickNodeLocal')
f(5,174,2,0,'com/sun/javafx/tk/quantum/QuantumToolkit$$Lambda$47/605813029.run')
f(6,174,2,0,'com/sun/javafx/tk/quantum/QuantumToolkit.lambda$runToolkit$404')
f(7,174,2,0,'com/sun/javafx/tk/quantum/QuantumToolkit.postPulse')
f(8,174,2,0,'com/sun/glass/ui/Application.invokeLater')
f(9,174,2,0,'com/sun/glass/ui/gtk/GtkApplication._invokeLater')
f(10,174,2,0,'com/sun/glass/ui/InvokeLaterDispatcher.invokeLater')
f(11,174,2,0,'java/util/concurrent/LinkedBlockingDeque.addLast')
f(12,174,2,0,'java/util/concurrent/LinkedBlockingDeque.offerLast')
f(13,174,2,0,'java/util/concurrent/locks/ReentrantLock.unlock')
f(14,174,2,0,'java/util/concurrent/locks/AbstractQueuedSynchronizer.release')
f(15,174,2,0,'java/util/concurrent/locks/AbstractQueuedSynchronizer.unparkSuccessor')
f(16,174,2,0,'java/util/concurrent/locks/LockSupport.unpark')
f(17,174,2,0,'sun/misc/Unsafe.unpark')
f(18,174,2,4,'Unsafe_Unpark')
f(19,174,2,4,'pthread_cond_signal@@GLIBC_2.3.2')
f(20,174,2,2,'entry_SYSCALL_64_after_hwframe')
f(21,174,2,2,'do_syscall_64')
f(22,174,2,2,'__x64_sys_futex')
f(23,174,2,2,'do_futex')
f(24,174,2,2,'wake_up_q')
f(25,174,2,2,'try_to_wake_up')
f(26,174,2,2,'_raw_spin_unlock_irqrestore')
f(5,176,8,4,'recvmsg')
f(6,176,8,2,'entry_SYSCALL_64_after_hwframe')
f(7,176,8,2,'do_syscall_64')
f(8,176,8,2,'__x64_sys_recvmsg')
f(9,176,8,2,'__sys_recvmsg')
f(10,176,8,2,'___sys_recvmsg')
f(11,176,8,2,'sock_recvmsg')
f(12,176,8,2,'inet_recvmsg')
f(13,176,8,2,'tcp_recvmsg')
f(14,176,3,2,'lock_sock_nested')
f(15,176,3,2,'_raw_spin_lock_bh')
f(16,176,3,2,'queued_spin_lock_slowpath')
f(17,176,3,2,'native_queued_spin_lock_slowpath')
f(14,180,4,2,'tcp_cleanup_rbuf')
f(15,180,4,2,'tcp_send_ack')
f(16,180,4,2,'__tcp_send_ack.part.45')
f(17,180,4,2,'__tcp_transmit_skb')
f(18,180,4,2,'ip_queue_xmit')
f(19,180,4,2,'__ip_queue_xmit')
f(20,180,4,2,'ip_local_out')
f(21,180,4,2,'ip_output')
f(22,180,4,2,'ip_finish_output')
f(23,180,4,2,'__ip_finish_output')
f(24,180,4,2,'ip_finish_output2')
f(25,180,4,2,'dev_queue_xmit')
f(26,180,4,2,'__dev_queue_xmit')
f(27,180,4,2,'sch_direct_xmit')
f(28,180,4,2,'dev_hard_start_xmit')
f(29,180,4,2,'e1000_xmit_frame?[e1000]')
f(2,184,45,0,'com/sun/javafx/tk/quantum/QuantumRenderer$PipelineRunnable.run')
f(3,184,45,0,'java/util/concurrent/ThreadPoolExecutor$Worker.run')
f(4,184,45,0,'java/util/concurrent/ThreadPoolExecutor.runWorker')
f(5,184,45,0,'com/sun/javafx/tk/RenderJob.run')
f(6,184,45,0,'java/util/concurrent/FutureTask.runAndReset')
f(7,184,45,0,'java/util/concurrent/Executors$RunnableAdapter.call')
f(8,184,45,0,'com/sun/javafx/tk/quantum/UploadingPainter.run')
f(9,184,2,0,'com/sun/javafx/tk/quantum/SceneState.uploadPixels')
f(10,184,2,0,'com/sun/glass/ui/Application.invokeLater')
f(11,184,2,0,'com/sun/glass/ui/gtk/GtkApplication._invokeLater')
f(12,184,2,0,'com/sun/glass/ui/InvokeLaterDispatcher.invokeLater')
f(13,184,2,0,'java/util/concurrent/LinkedBlockingDeque.addLast')
f(14,184,2,0,'java/util/concurrent/LinkedBlockingDeque.offerLast')
f(15,184,2,0,'java/util/concurrent/locks/ReentrantLock.unlock')
f(16,184,2,0,'java/util/concurrent/locks/AbstractQueuedSynchronizer.release')
f(17,184,2,0,'java/util/concurrent/locks/AbstractQueuedSynchronizer.unparkSuccessor')
f(18,184,2,0,'java/util/concurrent/locks/LockSupport.unpark')
f(19,184,2,0,'sun/misc/Unsafe.unpark')
f(20,184,2,4,'Unsafe_Unpark')
f(21,184,2,4,'pthread_cond_signal@@GLIBC_2.3.2')
f(22,184,2,2,'entry_SYSCALL_64_after_hwframe')
f(23,184,2,2,'do_syscall_64')
f(24,184,2,2,'__x64_sys_futex')
f(25,184,2,2,'do_futex')
f(26,184,2,2,'wake_up_q')
f(27,184,2,2,'try_to_wake_up')
f(28,184,2,2,'_raw_spin_unlock_irqrestore')
f(9,186,40,0,'com/sun/javafx/tk/quantum/ViewPainter.paintImpl')
f(10,188,38,0,'com/sun/javafx/tk/quantum/ViewPainter.doPaint')
f(11,188,38,0,'com/sun/javafx/sg/prism/NGNode.render')
f(12,188,38,0,'com/sun/javafx/sg/prism/NGNode.doRender')
f(13,188,38,0,'com/sun/javafx/sg/prism/NGRegion.renderContent')
f(14,188,38,0,'com/sun/javafx/sg/prism/NGGroup.renderContent')
f(15,188,38,0,'com/sun/javafx/sg/prism/NGNode.render')
f(16,188,38,0,'com/sun/javafx/sg/prism/NGNode.doRender')
f(17,188,21,0,'com/sun/javafx/sg/prism/NGCanvas.renderContent')
f(18,188,15,0,'com/sun/javafx/sg/prism/NGCanvas.renderStream')
f(19,188,15,0,'com/sun/javafx/sg/prism/NGCanvas.handleRenderOp')
f(20,188,11,0,'com/sun/prism/impl/BaseResourceFactory.getCachedTexture')
f(21,188,11,0,'com/sun/prism/impl/BaseResourceFactory.getCachedTexture')
f(22,189,10,0,'com/sun/prism/sw/SWTexture.update')
f(23,189,10,0,'com/sun/prism/sw/SWArgbPreTexture.update')
f(24,189,10,0,'com/sun/javafx/image/impl/BaseByteToIntConverter.convert')
f(25,189,10,0,'com/sun/javafx/image/impl/BaseByteToIntConverter.convert')
f(26,189,10,0,'com/sun/javafx/image/impl/ByteBgra$ToIntArgbSameConv.doConvert')
f(20,199,3,0,'com/sun/prism/sw/SWGraphics.drawTexture')
f(21,199,3,0,'com/sun/prism/sw/SWGraphics.drawTexture')
f(22,199,3,0,'com/sun/prism/sw/SWGraphics.drawTexture')
f(23,199,3,0,'com/sun/pisces/PiscesRenderer.drawImage')
f(24,199,3,0,'com/sun/pisces/PiscesRenderer.drawImageImpl')
f(25,199,3,4,'Java_com_sun_pisces_PiscesRenderer_drawImageImpl')
f(26,199,3,4,'fillRect')
f(27,200,2,4,'emitLinePTSourceOver8888_pre')
f(18,203,6,0,'com/sun/prism/sw/SWGraphics.drawTexture')
f(19,203,6,0,'com/sun/prism/sw/SWGraphics.drawTexture')
f(20,203,6,0,'com/sun/prism/sw/SWGraphics.drawTexture')
f(21,203,6,0,'com/sun/pisces/PiscesRenderer.drawImage')
f(22,203,6,0,'com/sun/pisces/PiscesRenderer.drawImageImpl')
f(23,203,6,4,'Java_com_sun_pisces_PiscesRenderer_drawImageImpl')
f(24,203,6,4,'fillRect')
f(25,204,5,4,'emitLinePTSourceOver8888_pre')
f(17,209,17,0,'com/sun/javafx/sg/prism/NGNode.renderOpacity')
f(18,210,10,0,'com/sun/javafx/sg/prism/NGRegion.renderContent')
f(19,210,10,0,'com/sun/javafx/sg/prism/NGGroup.renderContent')
f(20,210,10,0,'com/sun/javafx/sg/prism/NGNode.render')
f(21,210,10,0,'com/sun/javafx/sg/prism/NGNode.doRender')
f(22,210,10,0,'com/sun/javafx/sg/prism/NGRegion.renderContent')
f(23,210,8,0,'com/sun/javafx/sg/prism/NGGroup.renderContent')
f(24,210,8,0,'com/sun/javafx/sg/prism/NGNode.render')
f(25,210,8,0,'com/sun/javafx/sg/prism/NGNode.doRender')
f(26,210,3,0,'com/sun/javafx/sg/prism/NGRegion.renderContent')
f(27,211,2,0,'com/sun/javafx/sg/prism/NGRegion.renderAsRectangle')
f(28,211,2,0,'com/sun/javafx/sg/prism/NGRegion.renderBackgroundRectangle')
f(29,211,2,0,'com/sun/javafx/sg/prism/NGRegion.renderBackgroundRectanglesDirectly')
f(30,211,2,0,'com/sun/prism/sw/SWGraphics.fill')
f(31,211,2,0,'com/sun/prism/sw/SWGraphics.paintShape')
f(32,211,2,0,'com/sun/prism/sw/SWGraphics.paintShapePaintAlreadySet')
f(33,211,2,0,'com/sun/prism/sw/SWContext.renderShape')
f(34,211,2,0,'com/sun/prism/sw/SWContext$JavaShapeRenderer.renderShape')
f(35,211,2,0,'com/sun/prism/impl/shape/OpenPiscesPrismUtils.setupRenderer')
f(26,213,5,0,'com/sun/javafx/sg/prism/NGShape.renderContent')
f(27,213,5,0,'com/sun/javafx/sg/prism/NGText.renderContent2D')
f(28,213,4,0,'com/sun/javafx/sg/prism/NGText.renderText')
f(29,213,4,0,'com/sun/prism/sw/SWGraphics.drawString')
f(30,214,3,0,'com/sun/prism/sw/SWGraphics.drawGlyph')
f(23,218,2,0,'com/sun/javafx/sg/prism/NGRegion.renderAsRectangle')
f(24,218,2,0,'com/sun/javafx/sg/prism/NGRegion.renderBackgroundRectangle')
f(18,220,5,0,'com/sun/prism/sw/SWGraphics.drawTexture')
f(19,220,5,0,'com/sun/prism/sw/SWGraphics.drawTexture')
f(20,220,5,0,'com/sun/prism/sw/SWGraphics.drawTexture')
f(21,220,5,0,'com/sun/prism/sw/SWGraphics.drawTexture')
f(22,220,5,0,'com/sun/pisces/PiscesRenderer.drawImage')
f(23,220,5,0,'com/sun/pisces/PiscesRenderer.drawImageImpl')
f(24,220,5,4,'Java_com_sun_pisces_PiscesRenderer_drawImageImpl')
f(25,220,5,4,'fillRect')
f(26,220,3,4,'emitLinePTSourceOver8888_pre')
f(26,223,2,4,'genTexturePaintMultiply')
f(9,227,2,0,'java/nio/DirectIntBufferU.put')
f(10,227,2,0,'java/nio/Bits.copyFromArray')
f(11,227,2,0,'sun/misc/Unsafe.copyMemory')
f(12,227,2,4,'acl_CopyRight')
f(2,229,184,0,'java/util/concurrent/FutureTask.run')
f(3,229,184,0,'javafx/concurrent/Task$TaskCallable.call')
f(4,229,184,0,'demo/parallel/MandelbrotSetTask.call')
f(5,229,184,0,'demo/parallel/MandelbrotSetTask.call')
f(6,233,175,0,'java/util/stream/IntPipeline$Head.forEach')
f(7,233,68,0,'java/util/stream/IntPipeline.forEach')
f(8,233,68,0,'java/util/stream/AbstractPipeline.evaluate')
f(9,233,68,0,'java/util/stream/ForEachOps$ForEachOp$OfInt.evaluateParallel')
f(10,233,68,0,'java/util/stream/ForEachOps$ForEachOp.evaluateParallel')
f(11,233,68,0,'java/util/concurrent/ForkJoinTask.invoke')
f(12,233,68,0,'java/util/concurrent/ForkJoinTask.doInvoke')
f(13,233,25,0,'java/util/concurrent/ForkJoinTask.doExec')
f(14,233,25,0,'java/util/concurrent/CountedCompleter.exec')
f(15,233,25,0,'java/util/stream/ForEachOps$ForEachTask.compute')
f(16,233,24,0,'java/util/stream/AbstractPipeline.copyInto')
f(17,233,24,0,'java/util/Spliterator$OfInt.forEachRemaining')
f(18,233,24,0,'java/util/stream/Streams$RangeIntSpliterator.forEachRemaining')
f(19,233,24,0,'java/util/stream/ForEachOps$ForEachOp$OfInt.accept')
f(20,233,24,0,'demo/parallel/MandelbrotSetTask$$Lambda$99/105194951.accept')
f(21,233,24,0,'demo/parallel/MandelbrotSetTask.lambda$call$19')
f(22,234,2,3,'SharedRuntime::complete_monitor_locking_C(oopDesc*, BasicLock*, JavaThread*)')
f(23,234,2,3,'ObjectMonitor::enter(Thread*)')
f(24,234,2,3,'ObjectMonitor::TrySpin_VaryDuration(Thread*)')
f(22,236,21,0,'demo/parallel/MandelbrotSetTask.calcAntialiasedPixel')
f(23,237,19,0,'demo/parallel/MandelbrotSetTask.calcPixel')
f(24,237,19,0,'demo/parallel/MandelbrotSetTask.calc')
f(25,248,5,0,'demo/parallel/Complex.lengthSQ')
f(25,253,3,0,'demo/parallel/Complex.plus')
f(13,258,43,0,'java/util/concurrent/ForkJoinTask.externalAwaitDone')
f(14,258,43,0,'java/util/concurrent/ForkJoinPool.externalHelpComplete')
f(15,258,43,0,'java/util/concurrent/ForkJoinPool.helpComplete')
f(16,258,14,0,'java/util/concurrent/ForkJoinPool$WorkQueue.pollAndExecCC')
f(17,258,14,0,'java/util/concurrent/ForkJoinTask.doExec')
f(18,258,14,0,'java/util/concurrent/CountedCompleter.exec')
f(19,258,14,0,'java/util/stream/ForEachOps$ForEachTask.compute')
f(20,258,14,0,'java/util/stream/AbstractPipeline.copyInto')
f(21,258,14,0,'java/util/Spliterator$OfInt.forEachRemaining')
f(22,258,14,0,'java/util/stream/Streams$RangeIntSpliterator.forEachRemaining')
f(23,258,14,0,'java/util/stream/ForEachOps$ForEachOp$OfInt.accept')
f(24,258,14,0,'demo/parallel/MandelbrotSetTask$$Lambda$99/105194951.accept')
f(25,258,14,0,'demo/parallel/MandelbrotSetTask.lambda$call$19')
f(26,258,14,0,'demo/parallel/MandelbrotSetTask.calcAntialiasedPixel')
f(27,259,13,0,'demo/parallel/MandelbrotSetTask.calcPixel')
f(28,259,13,0,'demo/parallel/MandelbrotSetTask.calc')
f(29,267,4,0,'demo/parallel/Complex.plus')
f(16,272,29,0,'java/util/concurrent/ForkJoinTask.doExec')
f(17,272,29,0,'java/util/concurrent/CountedCompleter.exec')
f(18,272,29,0,'java/util/stream/ForEachOps$ForEachTask.compute')
f(19,272,29,0,'java/util/stream/AbstractPipeline.copyInto')
f(20,272,29,0,'java/util/Spliterator$OfInt.forEachRemaining')
f(21,272,29,0,'java/util/stream/Streams$RangeIntSpliterator.forEachRemaining')
f(22,272,29,0,'java/util/stream/ForEachOps$ForEachOp$OfInt.accept')
f(23,272,29,0,'demo/parallel/MandelbrotSetTask$$Lambda$99/105194951.accept')
f(24,272,29,0,'demo/parallel/MandelbrotSetTask.lambda$call$19')
f(25,273,28,0,'demo/parallel/MandelbrotSetTask.calcAntialiasedPixel')
f(26,274,27,0,'demo/parallel/MandelbrotSetTask.calcPixel')
f(27,274,27,0,'demo/parallel/MandelbrotSetTask.calc')
f(28,287,3,0,'demo/parallel/Complex.lengthSQ')
f(28,290,8,0,'demo/parallel/Complex.plus')
f(28,298,3,0,'demo/parallel/Complex.times')
f(7,301,107,0,'java/util/stream/Streams$RangeIntSpliterator.forEachRemaining')
f(8,301,107,0,'demo/parallel/MandelbrotSetTask$$Lambda$99/105194951.accept')
f(9,301,107,0,'demo/parallel/MandelbrotSetTask.lambda$call$19')
f(10,301,105,0,'demo/parallel/MandelbrotSetTask.calcAntialiasedPixel')
f(11,309,97,0,'demo/parallel/MandelbrotSetTask.calcPixel')
f(12,312,94,0,'demo/parallel/MandelbrotSetTask.calc')
f(13,357,23,0,'demo/parallel/Complex.lengthSQ')
f(13,380,16,0,'demo/parallel/Complex.plus')
f(13,396,10,0,'demo/parallel/Complex.times')
f(6,408,5,0,'javafx/scene/image/WritableImage$2.setColor')
f(7,408,3,0,'java/lang/Math.round')
f(7,411,2,0,'javafx/scene/image/WritableImage$2.setArgb')
f(8,411,2,0,'com/sun/prism/Image.setArgb')
f(1,413,163,0,'java/util/concurrent/ForkJoinWorkerThread.run')
f(2,413,163,0,'java/util/concurrent/ForkJoinPool.runWorker')
f(3,413,163,0,'java/util/concurrent/ForkJoinPool$WorkQueue.runTask')
f(4,413,38,0,'java/util/concurrent/ForkJoinPool$WorkQueue.execLocalTasks')
f(5,413,38,0,'java/util/concurrent/ForkJoinTask.doExec')
f(6,413,38,0,'java/util/concurrent/CountedCompleter.exec')
f(7,413,38,0,'java/util/stream/ForEachOps$ForEachTask.compute')
f(8,413,38,0,'java/util/stream/AbstractPipeline.copyInto')
f(9,413,38,0,'java/util/Spliterator$OfInt.forEachRemaining')
f(10,413,38,0,'java/util/stream/Streams$RangeIntSpliterator.forEachRemaining')
f(11,413,38,0,'java/util/stream/ForEachOps$ForEachOp$OfInt.accept')
f(12,413,38,0,'demo/parallel/MandelbrotSetTask$$Lambda$99/105194951.accept')
f(13,413,38,0,'demo/parallel/MandelbrotSetTask.lambda$call$19')
f(14,415,32,0,'demo/parallel/MandelbrotSetTask.calcAntialiasedPixel')
f(15,421,26,0,'demo/parallel/MandelbrotSetTask.calcPixel')
f(16,424,22,0,'demo/parallel/MandelbrotSetTask.calc')
f(17,432,7,0,'demo/parallel/Complex.lengthSQ')
f(17,439,7,0,'demo/parallel/Complex.plus')
f(14,447,4,0,'javafx/scene/image/WritableImage$2.setColor')
f(15,448,3,0,'javafx/scene/image/WritableImage$2.setArgb')
f(16,449,2,0,'javafx/scene/image/Image.getWritablePlatformImage')
f(4,451,125,0,'java/util/concurrent/ForkJoinTask.doExec')
f(5,451,125,0,'java/util/concurrent/CountedCompleter.exec')
f(6,451,125,0,'java/util/stream/ForEachOps$ForEachTask.compute')
f(7,452,124,0,'java/util/stream/AbstractPipeline.copyInto')
f(8,452,124,0,'java/util/Spliterator$OfInt.forEachRemaining')
f(9,452,124,0,'java/util/stream/Streams$RangeIntSpliterator.forEachRemaining')
f(10,452,124,0,'java/util/stream/ForEachOps$ForEachOp$OfInt.accept')
f(11,452,124,0,'demo/parallel/MandelbrotSetTask$$Lambda$99/105194951.accept')
f(12,452,124,0,'demo/parallel/MandelbrotSetTask.lambda$call$19')
f(13,454,119,0,'demo/parallel/MandelbrotSetTask.calcAntialiasedPixel')
f(14,462,111,0,'demo/parallel/MandelbrotSetTask.calcPixel')
f(15,464,109,0,'demo/parallel/MandelbrotSetTask.calc')
f(16,504,39,0,'demo/parallel/Complex.lengthSQ')
f(16,543,22,0,'demo/parallel/Complex.plus')
f(16,565,8,0,'demo/parallel/Complex.times')
f(13,573,3,0,'javafx/scene/image/WritableImage$2.setColor')
f(1,576,65,4,'start_thread')
f(2,576,65,4,'java_start(Thread*)')
f(3,576,4,3,'GCTaskThread::run()')
f(4,577,3,3,'OldToYoungRootsTask::do_it(GCTaskManager*, unsigned int)')
f(5,577,3,3,'CardTableExtension::scavenge_contents_parallel(ObjectStartArray*, MutableSpace*, HeapWord*, PSPromotionManager*, unsigned int, unsigned int)')
f(6,577,3,3,'PSPromotionManager::drain_stacks_depth(bool)')
f(7,578,2,3,'oopDesc* PSPromotionManager::copy_to_survivor_space<false>(oopDesc*)')
f(3,580,61,3,'JavaThread::run()')
f(4,580,61,3,'JavaThread::thread_main_inner()')
f(5,580,61,3,'CompileBroker::compiler_thread_loop()')
f(6,580,58,3,'CompileBroker::invoke_compiler_on_method(CompileTask*)')
f(7,580,58,3,'C2Compiler::compile_method(ciEnv*, ciMethod*, int)')
f(8,580,58,3,'Compile::Compile(ciEnv*, C2Compiler*, ciMethod*, int, bool, bool, bool)')
f(9,580,32,3,'Compile::Code_Gen()')
f(10,582,3,3,'PhaseCFG::do_global_code_motion()')
f(11,582,3,3,'PhaseCFG::global_code_motion()')
f(10,585,27,3,'PhaseChaitin::Register_Allocate()')
f(11,585,2,3,'PhaseChaitin::Select()')
f(12,585,2,3,'PhaseIFG::re_insert(unsigned int)')
f(11,589,9,3,'PhaseChaitin::build_ifg_physical(ResourceArea*)')
f(12,592,3,3,'PhaseChaitin::interfere_with_live(unsigned int, IndexSet*)')
f(12,596,2,3,'RegMask::smear_to_sets(int)')
f(11,599,3,3,'PhaseChaitin::gather_lrg_masks(bool)')
f(11,602,5,3,'PhaseChaitin::post_allocate_copy_removal()')
f(12,604,2,3,'PhaseChaitin::elide_copy(Node*, int, Block*, Node_List&, Node_List&, bool)')
f(11,609,2,3,'PhaseIFG::init(unsigned int)')
f(12,609,2,3,'IndexSet::initialize(unsigned int)')
f(9,613,15,3,'Compile::Optimize()')
f(10,614,13,3,'PhaseIdealLoop::build_and_optimize(bool, bool)')
f(11,617,2,3,'PhaseIdealLoop::build_loop_early(VectorSet&, Node_List&, Node_Stack&)')
f(11,619,4,3,'PhaseIdealLoop::build_loop_late(VectorSet&, Node_List&, Node_Stack&)')
f(11,625,2,3,'PhaseIterGVN::optimize()')
f(12,625,2,3,'PhaseIterGVN::transform_old(Node*)')
f(9,629,3,3,'ParseGenerator::generate(JVMState*)')
f(10,629,3,3,'Parse::Parse(JVMState*, ciMethod*, float)')
f(11,629,3,3,'Parse::do_all_blocks()')
f(12,629,3,3,'Parse::do_one_block()')
f(13,629,3,3,'Parse::do_one_bytecode()')
f(14,629,3,3,'Parse::do_call()')
f(15,630,2,3,'PredictedCallGenerator::generate(JVMState*)')
f(16,630,2,3,'ParseGenerator::generate(JVMState*)')
f(17,630,2,3,'Parse::Parse(JVMState*, ciMethod*, float)')
f(18,630,2,3,'Parse::do_all_blocks()')
f(19,630,2,3,'Parse::do_one_block()')
f(20,630,2,3,'Parse::do_one_bytecode()')
f(21,630,2,3,'Parse::do_call()')
f(9,632,6,3,'ciEnv::register_method(ciMethod*, int, CodeOffsets*, int, CodeBuffer*, int, OopMapSet*, ExceptionHandlerTable*, ImplicitExceptionTable*, AbstractCompiler*, int, bool, bool, RTMState)')
f(10,632,6,3,'nmethod::post_compiled_method_load_event()')
f(11,632,6,4,'pthread_cond_signal@@GLIBC_2.3.2')
f(12,632,6,2,'entry_SYSCALL_64_after_hwframe')
f(13,632,6,2,'do_syscall_64')
f(14,632,6,2,'__x64_sys_futex')
f(15,632,6,2,'do_futex')
f(16,632,6,2,'wake_up_q')
f(17,632,6,2,'try_to_wake_up')
f(18,632,6,2,'_raw_spin_unlock_irqrestore')
f(6,638,3,3,'CompileQueue::get()')
f(7,638,3,4,'pthread_cond_signal@@GLIBC_2.3.2')
f(8,638,3,2,'entry_SYSCALL_64_after_hwframe')
f(9,638,3,2,'do_syscall_64')
f(10,638,3,2,'__x64_sys_futex')
f(11,638,3,2,'do_futex')
f(12,638,3,2,'wake_up_q')
f(13,638,3,2,'try_to_wake_up')
f(14,638,3,2,'_raw_spin_unlock_irqrestore')
render();
</script></body></html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>tools.profiler</groupId>
<artifactId>async-profiler</artifactId>
<version>2.7</version>
<version>1.8.3</version>
<packaging>jar</packaging>
<name>async-profiler</name>

View File

@@ -7,7 +7,7 @@ usage() {
echo " start start profiling and return immediately"
echo " resume resume profiling without resetting collected data"
echo " stop stop profiling"
echo " dump dump collected data without stopping profiling session"
echo " 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"
@@ -23,8 +23,7 @@ usage() {
echo " -s simple class names instead of FQN"
echo " -g print method signatures"
echo " -a annotate Java method names"
echo " -l prepend library names"
echo " -o fmt output format: flat|traces|collapsed|flamegraph|tree|jfr"
echo " -o fmt output format: flat|collapsed|html|tree|jfr"
echo " -I include output only stack traces containing the specified pattern"
echo " -X exclude exclude stack traces with the specified pattern"
echo " -v, --version display version string"
@@ -33,25 +32,17 @@ usage() {
echo " --minwidth pct skip frames smaller than pct%"
echo " --reverse generate stack-reversed FlameGraph / Call tree"
echo ""
echo " --loop time run profiler in a loop"
echo " --alloc bytes allocation profiling interval in bytes"
echo " --lock duration lock profiling threshold in nanoseconds"
echo " --total accumulate the total value (time, bytes, etc.)"
echo " --all-kernel only include kernel-mode events"
echo " --all-user only include user-mode events"
echo " --sched group threads by scheduling policy"
echo " --cstack mode how to traverse C stack: fp|dwarf|lbr|no"
echo " --cstack mode how to traverse C stack: fp|lbr|no"
echo " --begin function begin profiling when function is executed"
echo " --end function end profiling when function is executed"
echo " --ttsp time-to-safepoint profiling"
echo " --jfrsync config synchronize profiler with JFR recording"
echo " --fdtransfer use fdtransfer to serve perf requests"
echo " from the non-privileged target"
echo ""
echo "<pid> is a numeric process ID of the target JVM"
echo " or 'jps' keyword to find running JVM automatically"
echo " or the application's name as it would appear in the jps tool"
echo ""
echo "Example: $0 -d 30 -f profile.html 3456"
echo "Example: $0 -d 30 -f profile.svg 3456"
echo " $0 start -i 999000 jps"
echo " $0 stop -o flat jps"
echo " $0 -d 5 -e alloc MyAppName"
@@ -64,25 +55,10 @@ mirror_output() {
if [ -f "$FILE" ]; then
cat "$FILE"
rm "$FILE"
elif [ -f "$ROOT_PREFIX$FILE" ]; then
cat "$ROOT_PREFIX$FILE"
rm "$ROOT_PREFIX$FILE"
fi
fi
}
mirror_log() {
# Try to access the log file both directly and through /proc/[pid]/root,
# in case the target namespace differs
if [ -f "$LOG" ]; then
cat "$LOG" >&2
rm "$LOG"
elif [ -f "$ROOT_PREFIX$LOG" ]; then
cat "$ROOT_PREFIX$LOG" >&2
rm "$ROOT_PREFIX$LOG"
fi
}
check_if_terminated() {
if ! kill -0 "$PID" 2> /dev/null; then
mirror_output
@@ -90,48 +66,34 @@ check_if_terminated() {
fi
}
fdtransfer() {
if [ "$USE_FDTRANSFER" = "true" ]; then
"$FDTRANSFER" "$PID"
fi
}
jattach() {
set +e
"$JATTACH" "$PID" load "$PROFILER" true "$1,log=$LOG" > /dev/null
"$JATTACH" "$PID" load "$PROFILER" true "$1" > /dev/null
RET=$?
set -e
# Check if jattach failed
if [ $RET -ne 0 ]; then
if [ $RET -eq 255 ]; then
echo "Failed to inject profiler into $PID"
if [ "$UNAME_S" = "Darwin" ]; then
if [ "$(uname -s)" = "Darwin" ]; then
otool -L "$PROFILER"
else
LD_PRELOAD="$PROFILER" /bin/true
ldd "$PROFILER"
fi
fi
mirror_log
exit $RET
fi
mirror_log
mirror_output
set -e
}
SCRIPT_BIN="$0"
while [ -h "$SCRIPT_BIN" ]; do
SCRIPT_BIN="$(readlink "$SCRIPT_BIN")"
done
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_BIN")" > /dev/null 2>&1; pwd -P)"
OPTIND=1
SCRIPT_DIR="$(cd "$(dirname "$0")" > /dev/null 2>&1; pwd -P)"
JATTACH=$SCRIPT_DIR/build/jattach
FDTRANSFER=$SCRIPT_DIR/build/fdtransfer
USE_FDTRANSFER="false"
PROFILER=$SCRIPT_DIR/build/libasyncProfiler.so
ACTION="collect"
EVENT="cpu"
DURATION="60"
FILE=""
USE_TMP="true"
@@ -145,14 +107,19 @@ while [ $# -gt 0 ]; do
-h|"-?")
usage
;;
start|resume|stop|dump|check|status|list|collect)
start|resume|stop|check|status|list|collect)
ACTION="$1"
;;
jstack)
ACTION="start"
EVENT="jstack"
PARAMS="$PARAMS,threads"
;;
-v|--version)
ACTION="version"
;;
-e)
PARAMS="$PARAMS,event=$2"
EVENT="$(echo "$2" | sed 's/,/,event=/g')"
shift
;;
-d)
@@ -184,9 +151,6 @@ while [ $# -gt 0 ]; do
-a)
FORMAT="$FORMAT,ann"
;;
-l)
FORMAT="$FORMAT,lib"
;;
-o)
OUTPUT="$2"
shift
@@ -217,26 +181,12 @@ while [ $# -gt 0 ]; do
--reverse)
FORMAT="$FORMAT,reverse"
;;
--samples|--total)
FORMAT="$FORMAT,${1#--}"
;;
--alloc|--lock|--chunksize|--chunktime)
PARAMS="$PARAMS,${1#--}=$2"
shift
;;
--timeout|--loop)
if [ "$ACTION" = "collect" ]; then
ACTION="start"
fi
PARAMS="$PARAMS,${1#--}=$2"
shift
--all-kernel)
PARAMS="$PARAMS,allkernel"
;;
--all-user)
PARAMS="$PARAMS,alluser"
;;
--sched)
PARAMS="$PARAMS,sched"
;;
--cstack|--call-graph)
PARAMS="$PARAMS,cstack=$2"
shift
@@ -245,18 +195,6 @@ while [ $# -gt 0 ]; do
PARAMS="$PARAMS,${1#--}=$2"
shift
;;
--ttsp)
PARAMS="$PARAMS,begin=SafepointSynchronize::begin,end=RuntimeService::record_safepoint_synchronized"
;;
--jfrsync)
OUTPUT="jfr"
PARAMS="$PARAMS,jfrsync=$2"
shift
;;
--fdtransfer)
PARAMS="$PARAMS,fdtransfer"
USE_FDTRANSFER="true"
;;
--safe-mode)
PARAMS="$PARAMS,safemode=$2"
shift
@@ -311,45 +249,27 @@ else
;;
esac
fi
LOG=/tmp/async-profiler-log.$$.$PID
UNAME_S=$(uname -s)
if [ "$UNAME_S" = "Linux" ]; then
ROOT_PREFIX="/proc/$PID/root"
else
ROOT_PREFIX=""
fi
case $ACTION in
start|resume)
fdtransfer
jattach "$ACTION,file=$FILE,$OUTPUT$FORMAT$PARAMS"
start|resume|check)
jattach "$ACTION,event=$EVENT,file=$FILE,$OUTPUT$FORMAT$PARAMS"
;;
check)
jattach "$ACTION,file=$FILE,$OUTPUT$FORMAT$PARAMS"
stop)
jattach "stop,file=$FILE,$OUTPUT$FORMAT"
;;
stop|dump)
jattach "$ACTION,file=$FILE,$OUTPUT$FORMAT"
status)
jattach "status,file=$FILE"
;;
status|list)
jattach "$ACTION,file=$FILE"
list)
jattach "list,file=$FILE"
;;
collect)
fdtransfer
jattach "start,file=$FILE,$OUTPUT$FORMAT$PARAMS"
echo Profiling for "$DURATION" seconds >&2
set +e
trap 'DURATION=0' INT
jattach "start,event=$EVENT,file=$FILE,$OUTPUT$FORMAT$PARAMS"
while [ "$DURATION" -gt 0 ]; do
DURATION=$(( DURATION-1 ))
check_if_terminated
sleep 1
done
set -e
trap - INT
echo Done >&2
jattach "stop,file=$FILE,$OUTPUT$FORMAT"
;;
version)

View File

@@ -15,21 +15,22 @@
*/
#include "allocTracer.h"
#include "os.h"
#include "profiler.h"
#include "stackFrame.h"
#include "vmStructs.h"
int AllocTracer::_trap_kind;
Trap AllocTracer::_in_new_tlab(0);
Trap AllocTracer::_outside_tlab(1);
Trap AllocTracer::_in_new_tlab;
Trap AllocTracer::_outside_tlab;
u64 AllocTracer::_interval;
volatile u64 AllocTracer::_allocated_bytes;
// Called whenever our breakpoint trap is hit
void AllocTracer::trapHandler(int signo, siginfo_t* siginfo, void* ucontext) {
void AllocTracer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
StackFrame frame(ucontext);
int event_type;
uintptr_t total_size;
@@ -50,7 +51,6 @@ void AllocTracer::trapHandler(int signo, siginfo_t* siginfo, void* ucontext) {
instance_size = 0;
} else {
// Not our trap
Profiler::instance()->trapHandler(signo, siginfo, ucontext);
return;
}
@@ -59,14 +59,28 @@ void AllocTracer::trapHandler(int signo, siginfo_t* siginfo, void* ucontext) {
frame.ret();
if (_enabled) {
// TODO: _enabled also uses traps
recordAllocation(ucontext, event_type, klass, total_size, instance_size);
}
}
void AllocTracer::recordAllocation(void* ucontext, int event_type, uintptr_t rklass,
uintptr_t total_size, uintptr_t instance_size) {
if (!updateCounter(_allocated_bytes, total_size, _interval)) {
return;
if (_interval) {
// Do not record allocation unless allocated at least _interval bytes
while (true) {
u64 prev = _allocated_bytes;
u64 next = prev + total_size;
if (next < _interval) {
if (__sync_bool_compare_and_swap(&_allocated_bytes, prev, next)) {
return;
}
} else {
if (__sync_bool_compare_and_swap(&_allocated_bytes, prev, next % _interval)) {
break;
}
}
}
}
AllocEvent event;
@@ -76,10 +90,10 @@ void AllocTracer::recordAllocation(void* ucontext, int event_type, uintptr_t rkl
if (VMStructs::hasClassNames()) {
VMSymbol* symbol = VMKlass::fromHandle(rklass)->name();
event._class_id = Profiler::instance()->classMap()->lookup(symbol->body(), symbol->length());
event._class_id = Profiler::_instance.classMap()->lookup(symbol->body(), symbol->length());
}
Profiler::instance()->recordSample(ucontext, total_size, event_type, &event);
Profiler::_instance.recordSample(ucontext, total_size, event_type, &event);
}
Error AllocTracer::check(Arguments& args) {
@@ -87,7 +101,7 @@ Error AllocTracer::check(Arguments& args) {
return Error::OK;
}
CodeCache* libjvm = VMStructs::libjvm();
NativeCodeCache* libjvm = VMStructs::libjvm();
const void* ne;
const void* oe;
@@ -104,9 +118,9 @@ Error AllocTracer::check(Arguments& args) {
return Error("No AllocTracer symbols found. Are JDK debug symbols installed?");
}
_in_new_tlab.assign(ne);
_outside_tlab.assign(oe);
_in_new_tlab.pair(_outside_tlab);
if (!_in_new_tlab.assign(ne) || !_outside_tlab.assign(oe)) {
return Error("Unable to install allocation trap");
}
return Error::OK;
}
@@ -117,12 +131,13 @@ Error AllocTracer::start(Arguments& args) {
return error;
}
_interval = args._alloc;
_interval = args._interval;
_allocated_bytes = 0;
if (!_in_new_tlab.install() || !_outside_tlab.install()) {
return Error("Cannot install allocation breakpoints");
}
OS::installSignalHandler(SIGTRAP, signalHandler);
_in_new_tlab.install();
_outside_tlab.install();
return Error::OK;
}

View File

@@ -32,23 +32,27 @@ class AllocTracer : public Engine {
static u64 _interval;
static volatile u64 _allocated_bytes;
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
static void recordAllocation(void* ucontext, int event_type, uintptr_t rklass,
uintptr_t total_size, uintptr_t instance_size);
public:
const char* title() {
return "Allocation profile";
const char* name() {
return "alloc";
}
const char* units() {
return "bytes";
}
CStack cstack() {
return CSTACK_NO;
}
Error check(Arguments& args);
Error start(Arguments& args);
void stop();
static void trapHandler(int signo, siginfo_t* siginfo, void* ucontext);
};
#endif // _ALLOCTRACER_H

View File

@@ -58,9 +58,6 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
*/
@Override
public void start(String event, long interval) throws IllegalStateException {
if (event == null) {
throw new NullPointerException();
}
start0(event, interval, true);
}
@@ -74,9 +71,6 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
*/
@Override
public void resume(String event, long interval) throws IllegalStateException {
if (event == null) {
throw new NullPointerException();
}
start0(event, interval, false);
}
@@ -122,10 +116,7 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
* @throws IOException If failed to create output file
*/
@Override
public String execute(String command) throws IllegalArgumentException, IllegalStateException, IOException {
if (command == null) {
throw new NullPointerException();
}
public String execute(String command) throws IllegalArgumentException, IOException {
return execute0(command);
}
@@ -138,22 +129,7 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
@Override
public String dumpCollapsed(Counter counter) {
try {
return execute0("collapsed," + counter.name().toLowerCase());
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
/**
* Dump collected stack traces
*
* @param maxTraces Maximum number of stack traces to dump. 0 means no limit
* @return Textual representation of the profile
*/
@Override
public String dumpTraces(int maxTraces) {
try {
return execute0(maxTraces == 0 ? "traces" : "traces=" + maxTraces);
return execute0("collapsed,counter=" + counter.name().toLowerCase());
} catch (IOException e) {
throw new IllegalStateException(e);
}
@@ -168,7 +144,7 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
@Override
public String dumpFlat(int maxMethods) {
try {
return execute0(maxMethods == 0 ? "flat" : "flat=" + maxMethods);
return execute0("flat=" + maxMethods);
} catch (IOException e) {
throw new IllegalStateException(e);
}
@@ -195,7 +171,7 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
}
private void filterThread(Thread thread, boolean enable) {
if (thread == null || thread == Thread.currentThread()) {
if (thread == null) {
filterThread0(null, enable);
} else {
// Need to take lock to avoid race condition with a thread state change
@@ -210,6 +186,6 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
private native void start0(String event, long interval, boolean reset) throws IllegalStateException;
private native void stop0() throws IllegalStateException;
private native String execute0(String command) throws IllegalArgumentException, IllegalStateException, IOException;
private native String execute0(String command) throws IllegalArgumentException, IOException;
private native void filterThread0(Thread thread, boolean enable);
}

View File

@@ -35,9 +35,8 @@ public interface AsyncProfilerMXBean {
long getSamples();
String getVersion();
String execute(String command) throws IllegalArgumentException, IllegalStateException, java.io.IOException;
String execute(String command) throws IllegalArgumentException, java.io.IOException;
String dumpCollapsed(Counter counter);
String dumpTraces(int maxTraces);
String dumpFlat(int maxMethods);
}

View File

@@ -31,42 +31,28 @@ static inline int atomicInc(volatile int& var, int increment = 1) {
return __sync_fetch_and_add(&var, increment);
}
static inline u64 loadAcquire(u64& var) {
return __atomic_load_n(&var, __ATOMIC_ACQUIRE);
}
static inline void storeRelease(u64& var, u64 value) {
return __atomic_store_n(&var, value, __ATOMIC_RELEASE);
}
#if defined(__x86_64__) || defined(__i386__)
typedef unsigned char instruction_t;
const instruction_t BREAKPOINT = 0xcc;
const int BREAKPOINT_OFFSET = 0;
const bool CAN_MOVE_SP = true;
const int SYSCALL_SIZE = 2;
const int FRAME_PC_SLOT = 1;
const int PLT_HEADER_SIZE = 16;
const int PLT_ENTRY_SIZE = 16;
const int PERF_REG_PC = 8; // PERF_REG_X86_IP
#define spinPause() asm volatile("pause")
#define rmb() asm volatile("lfence" : : : "memory")
#define flushCache(addr) asm volatile("mfence; clflush (%0); mfence" : : "r" (addr) : "memory")
#define flushCache(addr) asm volatile("mfence; clflush (%0); mfence" : : "r"(addr) : "memory")
#elif defined(__arm__) || defined(__thumb__)
typedef unsigned int instruction_t;
const instruction_t BREAKPOINT = 0xe7f001f0;
const instruction_t BREAKPOINT_THUMB = 0xde01de01;
const int BREAKPOINT_OFFSET = 0;
const bool CAN_MOVE_SP = true;
const int SYSCALL_SIZE = sizeof(instruction_t);
const int FRAME_PC_SLOT = 1;
const int PLT_HEADER_SIZE = 20;
const int PLT_ENTRY_SIZE = 12;
const int PERF_REG_PC = 15; // PERF_REG_ARM_PC
@@ -79,42 +65,16 @@ const int PERF_REG_PC = 15; // PERF_REG_ARM_PC
typedef unsigned int instruction_t;
const instruction_t BREAKPOINT = 0xd4200000;
const int BREAKPOINT_OFFSET = 0;
const bool CAN_MOVE_SP = true;
const int SYSCALL_SIZE = sizeof(instruction_t);
const int FRAME_PC_SLOT = 1;
const int PLT_HEADER_SIZE = 32;
const int PLT_ENTRY_SIZE = 16;
const int PERF_REG_PC = 32; // PERF_REG_ARM64_PC
#define spinPause() asm volatile("isb")
#define spinPause() asm volatile("yield")
#define rmb() asm volatile("dmb ish" : : : "memory")
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
#elif defined(__PPC64__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
typedef unsigned int instruction_t;
const instruction_t BREAKPOINT = 0x7fe00008;
// We place the break point in the third instruction slot on PPCLE as the first two are skipped if
// the call comes from within the same compilation unit according to the LE ABI.
const int BREAKPOINT_OFFSET = 8;
// The sp may not be moved on ppc. There is a valid back link to the previous frame at all times.
// The callee stores the return address in the caller's frame before it constructs its own frame
// with one atomic operation.
const bool CAN_MOVE_SP = false;
const int SYSCALL_SIZE = sizeof(instruction_t);
const int FRAME_PC_SLOT = 2;
const int PLT_HEADER_SIZE = 24;
const int PLT_ENTRY_SIZE = 24;
const int PERF_REG_PC = 32; // PERF_REG_POWERPC_NIP
#define spinPause() asm volatile("yield") // does nothing, but using or 1,1,1 would lead to other problems
#define rmb() asm volatile ("sync" : : : "memory") // lwsync would do but better safe than sorry
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
#else
#error "Compiling on unsupported arch"
@@ -122,22 +82,4 @@ const int PERF_REG_PC = 32; // PERF_REG_POWERPC_NIP
#endif
// Return address signing support.
// Apple M1 has 47 bit virtual addresses.
#if defined(__aarch64__) && defined(__APPLE__)
# define ADDRESS_BITS 47
# define WX_MEMORY true
#else
# define WX_MEMORY false
#endif
#ifdef ADDRESS_BITS
static inline const void* stripPointer(const void* p) {
return (const void*) ((unsigned long)p & ((1UL << ADDRESS_BITS) - 1));
}
#else
# define stripPointer(p) (p)
#endif
#endif // _ARCH_H

View File

@@ -30,78 +30,60 @@ const Error Error::OK(NULL);
// Extra buffer space for expanding file pattern
const size_t EXTRA_BUF_SIZE = 512;
static const Multiplier NANOS[] = {{'n', 1}, {'u', 1000}, {'m', 1000000}, {'s', 1000000000}, {0, 0}};
static const Multiplier BYTES[] = {{'b', 1}, {'k', 1024}, {'m', 1048576}, {'g', 1073741824}, {0, 0}};
static const Multiplier SECONDS[] = {{'s', 1}, {'m', 60}, {'h', 3600}, {'d', 86400}, {0, 0}};
static const Multiplier UNIVERSAL[] = {{'n', 1}, {'u', 1000}, {'m', 1000000}, {'s', 1000000000}, {'b', 1}, {'k', 1024}, {'g', 1073741824}, {0, 0}};
// Statically compute hash code of a string containing up to 12 [a-z] letters
#define HASH(s) ((s[0] & 31LL) | (s[1] & 31LL) << 5 | (s[2] & 31LL) << 10 | (s[3] & 31LL) << 15 | \
(s[4] & 31LL) << 20 | (s[5] & 31LL) << 25 | (s[6] & 31LL) << 30 | (s[7] & 31LL) << 35 | \
(s[8] & 31LL) << 40 | (s[9] & 31LL) << 45 | (s[10] & 31LL) << 50 | (s[11] & 31LL) << 55)
#define HASH(s) (HASH12(s " "))
#define HASH12(s) (s[0] & 31LL) | (s[1] & 31LL) << 5 | (s[2] & 31LL) << 10 | (s[3] & 31LL) << 15 | \
(s[4] & 31LL) << 20 | (s[5] & 31LL) << 25 | (s[6] & 31LL) << 30 | (s[7] & 31LL) << 35 | \
(s[8] & 31LL) << 40 | (s[9] & 31LL) << 45 | (s[10] & 31LL) << 50 | (s[11] & 31LL) << 55
// Simulate switch statement over string hashes
#define SWITCH(arg) long long arg_hash = hash(arg); if (0)
#define CASE(s) } else if (arg_hash == HASH(s " ")) {
#define CASE(s) } else if (arg_hash == HASH(s)) {
#define DEFAULT() } else {
#define CASE2(s1, s2) } else if (arg_hash == HASH(s1) || arg_hash == HASH(s2)) {
// Parses agent arguments.
// The format of the string is:
// arg[,arg...]
// where arg is one of the following options:
// start - start profiling
// resume - start or resume profiling without resetting collected data
// stop - stop profiling
// dump - dump collected data without stopping profiling session
// check - check if the specified profiling event is available
// status - print profiling status (inactive / running for X seconds)
// list - show the list of available profiling events
// version[=full] - display the agent version
// event=EVENT - which event to trace (cpu, wall, cache-misses, etc.)
// alloc[=BYTES] - profile allocations with BYTES interval
// lock[=DURATION] - profile contended locks longer than DURATION ns
// collapsed - dump collapsed stacks (the format used by FlameGraph script)
// flamegraph - produce Flame Graph in HTML format
// tree - produce call tree in HTML format
// jfr - dump events in Java Flight Recorder format
// jfrsync[=CONFIG] - start Java Flight Recording with the given config along with the profiler
// traces[=N] - dump top N call traces
// flat[=N] - dump top N methods (aka flat profile)
// samples - count the number of samples (default)
// total - count the total value (time, bytes, etc.) instead of samples
// chunksize=N - approximate size of JFR chunk in bytes (default: 100 MB)
// chunktime=N - duration of JFR chunk in seconds (default: 1 hour)
// timeout=TIME - automatically stop profiler at TIME (absolute or relative)
// loop=TIME - run profiler in a loop (continuous profiling)
// interval=N - sampling interval in ns (default: 10'000'000, i.e. 10 ms)
// jstackdepth=N - maximum Java stack depth (default: 2048)
// safemode=BITS - disable stack recovery techniques (default: 0, i.e. everything enabled)
// file=FILENAME - output file name for dumping
// log=FILENAME - log warnings and errors to the given dedicated stream
// filter=FILTER - thread filter
// threads - profile different threads separately
// sched - group threads by scheduling policy
// cstack=MODE - how to collect C stack frames in addition to Java stack
// MODE is 'fp' (Frame Pointer), 'dwarf', 'lbr' (Last Branch Record) or 'no'
// allkernel - include only kernel-mode events
// alluser - include only user-mode events
// fdtransfer - use fdtransfer to pass fds to the profiler
// simple - simple class names instead of FQN
// dot - dotted class names
// sig - print method signatures
// ann - annotate Java method names
// lib - prepend library names
// include=PATTERN - include stack traces containing PATTERN
// exclude=PATTERN - exclude stack traces containing PATTERN
// begin=FUNCTION - begin profiling when FUNCTION is executed
// end=FUNCTION - end profiling when FUNCTION is executed
// title=TITLE - FlameGraph title
// minwidth=PCT - FlameGraph minimum frame width in percent
// reverse - generate stack-reversed FlameGraph / Call tree
// start - start profiling
// resume - start or resume profiling without resetting collected data
// stop - stop profiling
// check - check if the specified profiling event is available
// status - print profiling status (inactive / running for X seconds)
// list - show the list of available profiling events
// version[=full] - display the agent version
// event=EVENT - which event to trace (cpu, alloc, lock, cache-misses etc.)
// collapsed[=C] - dump collapsed stacks (the format used by FlameGraph script)
// html[=C] - produce Flame Graph in HTML format
// tree[=C] - produce call tree in HTML format
// C is counter type: 'samples' or 'total'
// jfr - dump events in Java Flight Recorder format
// flat[=N] - dump top N methods (aka flat profile)
// interval=N - sampling interval in ns (default: 10'000'000, i.e. 10 ms)
// jstackdepth=N - maximum Java stack depth (default: 2048)
// safemode=BITS - disable stack recovery techniques (default: 0, i.e. everything enabled)
// file=FILENAME - output file name for dumping
// filter=FILTER - thread filter
// threads - profile different threads separately
// cstack=MODE - how to collect C stack frames in addition to Java stack
// MODE is 'fp' (Frame Pointer), 'lbr' (Last Branch Record) or 'no'
// allkernel - include only kernel-mode events
// alluser - include only user-mode events
// simple - simple class names instead of FQN
// dot - dotted class names
// sig - print method signatures
// ann - annotate Java method names
// include=PATTERN - include stack traces containing PATTERN
// exclude=PATTERN - exclude stack traces containing PATTERN
// begin=FUNCTION - begin profiling when FUNCTION is executed
// end=FUNCTION - end profiling when FUNCTION is executed
// title=TITLE - FlameGraph title
// minwidth=PCT - FlameGraph minimum frame width in percent
// reverse - generate stack-reversed FlameGraph / Call tree
//
// It is possible to specify multiple dump options at the same time
@@ -112,15 +94,13 @@ Error Arguments::parse(const char* args) {
size_t len = strlen(args);
free(_buf);
_buf = (char*)malloc(len + EXTRA_BUF_SIZE + 1);
_buf = (char*)malloc(len + EXTRA_BUF_SIZE);
if (_buf == NULL) {
return Error("Not enough memory to parse arguments");
}
char* args_copy = strcpy(_buf + EXTRA_BUF_SIZE, args);
strcpy(_buf, args);
const char* msg = NULL;
for (char* arg = strtok(args_copy, ","); arg != NULL; arg = strtok(NULL, ",")) {
for (char* arg = strtok(_buf, ","); arg != NULL; arg = strtok(NULL, ",")) {
char* value = strchr(arg, '=');
if (value != NULL) *value++ = 0;
@@ -135,9 +115,6 @@ Error Arguments::parse(const char* args) {
CASE("stop")
_action = ACTION_STOP;
CASE("dump")
_action = ACTION_DUMP;
CASE("check")
_action = ACTION_CHECK;
@@ -151,113 +128,54 @@ Error Arguments::parse(const char* args) {
_action = value == NULL ? ACTION_VERSION : ACTION_FULL_VERSION;
// Output formats
CASE("collapsed")
CASE2("collapsed", "folded")
_output = OUTPUT_COLLAPSED;
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
CASE("flamegraph")
CASE2("flamegraph", "html")
_output = OUTPUT_FLAMEGRAPH;
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
CASE("tree")
_output = OUTPUT_TREE;
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
CASE("jfr")
_output = OUTPUT_JFR;
if (value != NULL) {
_jfr_options = (int)strtol(value, NULL, 0);
}
CASE("jfrsync")
_output = OUTPUT_JFR;
_jfr_options = JFR_SYNC_OPTS;
_jfr_sync = value == NULL ? "default" : value;
CASE("traces")
_output = OUTPUT_TEXT;
_dump_traces = value == NULL ? INT_MAX : atoi(value);
CASE("flat")
_output = OUTPUT_TEXT;
_output = OUTPUT_FLAT;
_dump_flat = value == NULL ? INT_MAX : atoi(value);
CASE("samples")
_counter = COUNTER_SAMPLES;
CASE("total")
_counter = COUNTER_TOTAL;
CASE("chunksize")
if (value == NULL || (_chunk_size = parseUnits(value, BYTES)) < 0) {
msg = "Invalid chunksize";
}
CASE("chunktime")
if (value == NULL || (_chunk_time = parseUnits(value, SECONDS)) < 0) {
msg = "Invalid chunktime";
}
// Basic options
CASE("event")
if (value == NULL || value[0] == 0) {
msg = "event must not be empty";
} else if (strcmp(value, EVENT_ALLOC) == 0) {
if (_alloc <= 0) _alloc = 1;
} else if (strcmp(value, EVENT_LOCK) == 0) {
if (_lock <= 0) _lock = 1;
} else if (_event != NULL) {
msg = "Duplicate event argument";
} else {
_event = value;
return Error("event must not be empty");
}
CASE("timeout")
if (value == NULL || (_timeout = parseTimeout(value)) == -1 || !_persistent) {
msg = "Invalid timeout";
}
CASE("loop")
_loop = true;
if (value == NULL || (_timeout = parseTimeout(value)) == -1 || !_persistent) {
msg = "Invalid loop duration";
}
CASE("alloc")
_alloc = value == NULL ? 1 : parseUnits(value, BYTES);
if (_alloc < 0) {
msg = "alloc must be >= 0";
}
CASE("lock")
_lock = value == NULL ? 1 : parseUnits(value, NANOS);
if (_lock < 0) {
msg = "lock must be >= 0";
if (!addEvent(value)) {
return Error("multiple incompatible events");
}
CASE("interval")
if (value == NULL || (_interval = parseUnits(value, UNIVERSAL)) <= 0) {
msg = "Invalid interval";
if (value == NULL || (_interval = parseUnits(value)) <= 0) {
return Error("Invalid interval");
}
CASE("jstackdepth")
if (value == NULL || (_jstackdepth = atoi(value)) <= 0) {
msg = "jstackdepth must be > 0";
return Error("jstackdepth must be > 0");
}
CASE("safemode")
_safe_mode = value == NULL ? INT_MAX : (int)strtol(value, NULL, 0);
_safe_mode = value == NULL ? INT_MAX : atoi(value);
CASE("file")
if (value == NULL || value[0] == 0) {
msg = "file must not be empty";
return Error("file must not be empty");
}
_file = value;
CASE("log")
_log = value == NULL || value[0] == 0 ? NULL : value;
CASE("fdtransfer")
_fdtransfer = true;
_fdtransfer_path = value;
// Filters
CASE("filter")
_filter = value == NULL ? "" : value;
@@ -271,9 +189,6 @@ Error Arguments::parse(const char* args) {
CASE("threads")
_threads = true;
CASE("sched")
_sched = true;
CASE("allkernel")
_ring = RING_KERNEL;
@@ -284,8 +199,6 @@ Error Arguments::parse(const char* args) {
if (value != NULL) {
if (value[0] == 'n') {
_cstack = CSTACK_NO;
} else if (value[0] == 'd') {
_cstack = CSTACK_DWARF;
} else if (value[0] == 'l') {
_cstack = CSTACK_LBR;
} else {
@@ -306,9 +219,6 @@ Error Arguments::parse(const char* args) {
CASE("ann")
_style |= STYLE_ANNOTATE;
CASE("lib")
_style |= STYLE_LIB_NAMES;
CASE("begin")
_begin = value;
@@ -317,49 +227,45 @@ Error Arguments::parse(const char* args) {
// FlameGraph options
CASE("title")
_title = value;
if (value != NULL) _title = value;
CASE("minwidth")
if (value != NULL) _minwidth = atof(value);
CASE("reverse")
_reverse = true;
DEFAULT()
msg = "Unknown argument";
}
}
// Return error only after parsing all arguments, when 'log' is already set
if (msg != NULL) {
return Error(msg);
}
if (_event == NULL && _alloc == 0 && _lock == 0) {
_event = EVENT_CPU;
if (_file != NULL && strchr(_file, '%') != NULL) {
_file = expandFilePattern(_buf + len + 1, EXTRA_BUF_SIZE - 1, _file);
}
if (_file != NULL && _output == OUTPUT_NONE) {
_output = detectOutputFormat(_file);
if (_output == OUTPUT_SVG) {
return Error("SVG format is obsolete, use .html for FlameGraph");
}
_dump_traces = 100;
_dump_flat = 200;
}
if (_action == ACTION_NONE && _output != OUTPUT_NONE) {
if (_output != OUTPUT_NONE && (_action == ACTION_NONE || _action == ACTION_STOP)) {
_action = ACTION_DUMP;
}
return Error::OK;
}
const char* Arguments::file() {
if (_file != NULL && strchr(_file, '%') != NULL) {
return expandFilePattern(_buf, EXTRA_BUF_SIZE - 1, _file);
bool Arguments::addEvent(const char* event) {
if (strcmp(event, EVENT_ALLOC) == 0) {
_events |= EK_ALLOC;
} else if (strcmp(event, EVENT_LOCK) == 0) {
_events |= EK_LOCK;
} else {
if (_events & EK_CPU) {
return false;
}
_events |= EK_CPU;
_event_desc = event;
}
return _file;
return true;
}
// The linked list of string offsets is embedded right into _buf array
@@ -400,19 +306,6 @@ const char* Arguments::expandFilePattern(char* dest, size_t max_size, const char
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
t.tm_hour, t.tm_min, t.tm_sec);
continue;
} else if (c == '{') {
char env_key[128];
const char* p = strchr(pattern, '}');
if (p != NULL && p - pattern < sizeof(env_key)) {
memcpy(env_key, pattern, p - pattern);
env_key[p - pattern] = 0;
const char* env_value = getenv(env_key);
if (env_value != NULL) {
ptr += snprintf(ptr, end - ptr, "%s", env_value);
pattern = p + 1;
continue;
}
}
}
}
*ptr++ = c;
@@ -431,46 +324,31 @@ Output Arguments::detectOutputFormat(const char* file) {
return OUTPUT_JFR;
} else if (strcmp(ext, ".collapsed") == 0 || strcmp(ext, ".folded") == 0) {
return OUTPUT_COLLAPSED;
} else if (strcmp(ext, ".svg") == 0) {
return OUTPUT_SVG;
}
}
return OUTPUT_TEXT;
return OUTPUT_FLAT;
}
long Arguments::parseUnits(const char* str, const Multiplier* multipliers) {
long Arguments::parseUnits(const char* str) {
char* end;
long result = strtol(str, &end, 0);
char c = *end;
if (c == 0) {
return result;
}
if (c >= 'A' && c <= 'Z') {
c += 'a' - 'A';
}
for (const Multiplier* m = multipliers; m->symbol; m++) {
if (c == m->symbol) {
return result * m->multiplier;
}
switch (*end) {
case 0:
return result;
case 'K': case 'k':
case 'U': case 'u': // microseconds
return result * 1000;
case 'M': case 'm': // million, megabytes or milliseconds
return result * 1000000;
case 'G': case 'g':
case 'S': case 's': // seconds
return result * 1000000000;
}
return -1;
}
int Arguments::parseTimeout(const char* str) {
const char* p = strchr(str, ':');
if (p == NULL) {
return parseUnits(str, SECONDS);
}
int hh = str[0] >= '0' && str[0] <= '2' ? atoi(str) : 0xff;
int mm = p[1] >= '0' && p[1] <= '5' ? atoi(p + 1) : 0xff;
int ss = (p = strchr(p + 1, ':')) != NULL && p[1] >= '0' && p[1] <= '5' ? atoi(p + 1) : 0xff;
return 0xff000000 | hh << 16 | mm << 8 | ss;
}
Arguments::~Arguments() {
if (!_shared) free(_buf);
}

View File

@@ -28,18 +28,19 @@ const char* const EVENT_ALLOC = "alloc";
const char* const EVENT_LOCK = "lock";
const char* const EVENT_WALL = "wall";
const char* const EVENT_ITIMER = "itimer";
const char* const EVENT_JSTACK = "jstack";
enum Action {
ACTION_NONE,
ACTION_START,
ACTION_RESUME,
ACTION_STOP,
ACTION_DUMP,
ACTION_CHECK,
ACTION_STATUS,
ACTION_LIST,
ACTION_VERSION,
ACTION_FULL_VERSION
ACTION_FULL_VERSION,
ACTION_DUMP
};
enum Counter {
@@ -53,47 +54,35 @@ enum Ring {
RING_USER
};
enum EventKind {
EK_CPU = 1,
EK_ALLOC = 2,
EK_LOCK = 4
};
enum Style {
STYLE_SIMPLE = 1,
STYLE_DOTTED = 2,
STYLE_SIGNATURES = 4,
STYLE_ANNOTATE = 8,
STYLE_LIB_NAMES = 16
STYLE_ANNOTATE = 8
};
enum CStack {
CSTACK_DEFAULT,
CSTACK_NO,
CSTACK_FP,
CSTACK_DWARF,
CSTACK_LBR
};
enum Output {
OUTPUT_NONE,
OUTPUT_TEXT,
OUTPUT_SVG, // obsolete
OUTPUT_FLAT,
OUTPUT_COLLAPSED,
OUTPUT_FLAMEGRAPH,
OUTPUT_TREE,
OUTPUT_JFR
};
enum JfrOption {
NO_SYSTEM_INFO = 0x1,
NO_SYSTEM_PROPS = 0x2,
NO_NATIVE_LIBS = 0x4,
NO_CPU_LOAD = 0x8,
JFR_SYNC_OPTS = NO_SYSTEM_INFO | NO_SYSTEM_PROPS | NO_NATIVE_LIBS | NO_CPU_LOAD
};
struct Multiplier {
char symbol;
long multiplier;
};
class Error {
private:
@@ -119,45 +108,31 @@ class Arguments {
private:
char* _buf;
bool _shared;
bool _persistent;
void appendToEmbeddedList(int& list, char* value);
static long long hash(const char* arg);
static const char* expandFilePattern(char* dest, size_t max_size, const char* pattern);
static Output detectOutputFormat(const char* file);
static long parseUnits(const char* str, const Multiplier* multipliers);
static int parseTimeout(const char* str);
static long parseUnits(const char* str);
public:
Action _action;
Counter _counter;
Ring _ring;
const char* _event;
int _timeout;
int _events;
const char* _event_desc;
long _interval;
long _alloc;
long _lock;
int _jstackdepth;
int _safe_mode;
const char* _file;
const char* _log;
const char* _filter;
int _include;
int _exclude;
bool _loop;
bool _threads;
bool _sched;
bool _fdtransfer;
const char* _fdtransfer_path;
int _style;
CStack _cstack;
Output _output;
long _chunk_size;
long _chunk_time;
const char* _jfr_sync;
int _jfr_options;
int _dump_traces;
int _dump_flat;
const char* _begin;
const char* _end;
@@ -166,42 +141,28 @@ class Arguments {
double _minwidth;
bool _reverse;
Arguments(bool persistent = false) :
Arguments() :
_buf(NULL),
_shared(false),
_persistent(persistent),
_action(ACTION_NONE),
_counter(COUNTER_SAMPLES),
_ring(RING_ANY),
_event(NULL),
_timeout(0),
_events(0),
_event_desc(NULL),
_interval(0),
_alloc(0),
_lock(0),
_jstackdepth(DEFAULT_JSTACKDEPTH),
_safe_mode(0),
_file(NULL),
_log(NULL),
_filter(NULL),
_include(0),
_exclude(0),
_loop(false),
_threads(false),
_sched(false),
_fdtransfer(false),
_fdtransfer_path(NULL),
_style(0),
_cstack(CSTACK_DEFAULT),
_output(OUTPUT_NONE),
_chunk_size(100 * 1024 * 1024),
_chunk_time(3600),
_jfr_sync(NULL),
_jfr_options(0),
_dump_traces(0),
_dump_flat(0),
_begin(NULL),
_end(NULL),
_title(NULL),
_title("Flame Graph"),
_minwidth(0),
_reverse(false) {
}
@@ -212,16 +173,7 @@ class Arguments {
Error parse(const char* args);
const char* file();
bool hasOutputFile() const {
return _file != NULL &&
(_action == ACTION_STOP || _action == ACTION_DUMP ? _output != OUTPUT_JFR : _action >= ACTION_STATUS);
}
bool hasOption(JfrOption option) const {
return (_jfr_options & option) != 0;
}
bool addEvent(const char* event);
friend class FrameName;
friend class Recording;

View File

@@ -15,13 +15,14 @@
*/
#include <string.h>
#include <unistd.h>
#include "callTraceStorage.h"
#include "os.h"
static const u32 INITIAL_CAPACITY = 65536;
static const u32 CALL_TRACE_CHUNK = 8 * 1024 * 1024;
static const u32 OVERFLOW_TRACE_ID = 0x7fffffff;
static const size_t PAGE_ALIGNMENT = sysconf(_SC_PAGESIZE) - 1;
class LongHashTable {
@@ -35,7 +36,7 @@ class LongHashTable {
static size_t getSize(u32 capacity) {
size_t size = sizeof(LongHashTable) + (sizeof(u64) + sizeof(CallTraceSample)) * capacity;
return (size + OS::page_mask) & ~OS::page_mask;
return (size + PAGE_ALIGNMENT) & ~PAGE_ALIGNMENT;
}
public:
@@ -86,11 +87,8 @@ class LongHashTable {
};
CallTrace CallTraceStorage::_overflow_trace = {1, {BCI_ERROR, (jmethodID)"storage_overflow"}};
CallTraceStorage::CallTraceStorage() : _allocator(CALL_TRACE_CHUNK) {
_current_table = LongHashTable::allocate(NULL, INITIAL_CAPACITY);
_overflow = 0;
}
CallTraceStorage::~CallTraceStorage() {
@@ -105,7 +103,6 @@ void CallTraceStorage::clear() {
}
_current_table->clear();
_allocator.clear();
_overflow = 0;
}
void CallTraceStorage::collectTraces(std::map<u32, CallTrace*>& map) {
@@ -115,17 +112,11 @@ void CallTraceStorage::collectTraces(std::map<u32, CallTrace*>& map) {
u32 capacity = table->capacity();
for (u32 slot = 0; slot < capacity; slot++) {
if (keys[slot] != 0 && loadAcquire(values[slot].samples) != 0) {
// Reset samples to avoid duplication of call traces between JFR chunks
values[slot].samples = 0;
if (keys[slot] != 0) {
map[capacity - (INITIAL_CAPACITY - 1) + slot] = values[slot].trace;
}
}
}
if (_overflow > 0) {
map[OVERFLOW_TRACE_ID] = &_overflow_trace;
}
}
void CallTraceStorage::collectSamples(std::vector<CallTraceSample*>& samples) {
@@ -142,20 +133,6 @@ void CallTraceStorage::collectSamples(std::vector<CallTraceSample*>& samples) {
}
}
void CallTraceStorage::collectSamples(std::map<u64, CallTraceSample>& map) {
for (LongHashTable* table = _current_table; table != NULL; table = table->prev()) {
u64* keys = table->keys();
CallTraceSample* values = table->values();
u32 capacity = table->capacity();
for (u32 slot = 0; slot < capacity; slot++) {
if (keys[slot] != 0 && loadAcquire(values[slot].counter) != 0) {
map[keys[slot]] += values[slot];
}
}
}
}
// Adaptation of MurmurHash64A by Austin Appleby
u64 CallTraceStorage::calcHash(int num_frames, ASGCT_CallFrame* frames) {
const u64 M = 0xc6a4a7935bd1e995ULL;
@@ -254,13 +231,13 @@ u32 CallTraceStorage::put(int num_frames, ASGCT_CallFrame* frames, u64 counter)
if (++step >= capacity) {
// Very unlikely case of a table overflow
atomicInc(_overflow);
return OVERFLOW_TRACE_ID;
return 0;
}
// Improved version of linear probing
slot = (slot + step) & (capacity - 1);
}
// TODO: check overhead
CallTraceSample& s = table->values()[slot];
atomicInc(s.samples);
atomicInc(s.counter, counter);

View File

@@ -35,26 +35,12 @@ struct CallTraceSample {
CallTrace* trace;
u64 samples;
u64 counter;
CallTraceSample& operator+=(const CallTraceSample& s) {
trace = s.trace;
samples += s.samples;
counter += s.counter;
return *this;
}
bool operator<(const CallTraceSample& other) const {
return counter > other.counter;
}
};
class CallTraceStorage {
private:
static CallTrace _overflow_trace;
LinearAllocator _allocator;
LongHashTable* _current_table;
u64 _overflow;
u64 calcHash(int num_frames, ASGCT_CallFrame* frames);
CallTrace* storeCallTrace(int num_frames, ASGCT_CallFrame* frames);
@@ -67,7 +53,6 @@ class CallTraceStorage {
void clear();
void collectTraces(std::map<u32, CallTrace*>& map);
void collectSamples(std::vector<CallTraceSample*>& samples);
void collectSamples(std::map<u64, CallTraceSample>& map);
u32 put(int num_frames, ASGCT_CallFrame* frames, u64 counter);
};

View File

@@ -17,66 +17,26 @@
#include <stdlib.h>
#include <string.h>
#include "codeCache.h"
#include "dwarf.h"
char* NativeFunc::create(const char* name, short lib_index) {
NativeFunc* f = (NativeFunc*)malloc(sizeof(NativeFunc) + 1 + strlen(name));
f->_lib_index = lib_index;
f->_mark = 0;
return strcpy(f->_name, name);
}
void NativeFunc::destroy(char* name) {
free(from(name));
}
CodeCache::CodeCache(const char* name, short lib_index, const void* min_address, const void* max_address) {
_name = NativeFunc::create(name, -1);
_lib_index = lib_index;
_min_address = min_address;
_max_address = max_address;
_text_base = NULL;
_got_start = NULL;
_got_end = NULL;
_dwarf_table = NULL;
_dwarf_table_length = 0;
_capacity = INITIAL_CODE_CACHE_CAPACITY;
_count = 0;
_blobs = new CodeBlob[_capacity];
}
CodeCache::~CodeCache() {
for (int i = 0; i < _count; i++) {
NativeFunc::destroy(_blobs[i]._name);
}
NativeFunc::destroy(_name);
delete[] _blobs;
free(_dwarf_table);
}
void CodeCache::expand() {
CodeBlob* old_blobs = _blobs;
CodeBlob* new_blobs = new CodeBlob[_capacity * 2];
memcpy(new_blobs, old_blobs, _count * sizeof(CodeBlob));
int live = 0;
for (int i = 0; i < _count; i++) {
if (_blobs[i]._method != NULL) {
new_blobs[live++] = _blobs[i];
}
}
_count = live;
_capacity *= 2;
_blobs = new_blobs;
delete[] old_blobs;
}
void CodeCache::add(const void* start, int length, const char* name, bool update_bounds) {
char* name_copy = NativeFunc::create(name, _lib_index);
// Replace non-printable characters
for (char* s = name_copy; *s != 0; s++) {
if (*s < ' ') *s = '?';
}
void CodeCache::add(const void* start, int length, jmethodID method, bool update_bounds) {
if (_count >= _capacity) {
expand();
}
@@ -84,20 +44,58 @@ void CodeCache::add(const void* start, int length, const char* name, bool update
const void* end = (const char*)start + length;
_blobs[_count]._start = start;
_blobs[_count]._end = end;
_blobs[_count]._name = name_copy;
_blobs[_count]._method = method;
_count++;
if (update_bounds) {
updateBounds(start, end);
if (start < _min_address) _min_address = start;
if (end > _max_address) _max_address = end;
}
}
void CodeCache::updateBounds(const void* start, const void* end) {
if (start < _min_address) _min_address = start;
if (end > _max_address) _max_address = end;
void CodeCache::remove(const void* start, jmethodID method) {
for (int i = 0; i < _count; i++) {
if (_blobs[i]._start == start && _blobs[i]._method == method) {
_blobs[i]._method = NULL;
return;
}
}
}
void CodeCache::sort() {
jmethodID CodeCache::find(const void* address) {
for (int i = 0; i < _count; i++) {
CodeBlob* cb = _blobs + i;
if (address >= cb->_start && address < cb->_end && cb->_method != NULL) {
return _blobs[i]._method;
}
}
return NULL;
}
NativeCodeCache::NativeCodeCache(const char* name, const void* min_address, const void* max_address) {
_name = strdup(name);
_min_address = min_address;
_max_address = max_address;
}
NativeCodeCache::~NativeCodeCache() {
for (int i = 0; i < _count; i++) {
free(_blobs[i]._method);
}
free(_name);
}
void NativeCodeCache::add(const void* start, int length, const char* name, bool update_bounds) {
char* name_copy = strdup(name);
// Replace non-printable characters
for (char* s = name_copy; *s != 0; s++) {
if (*s < ' ') *s = '?';
}
CodeCache::add(start, length, (jmethodID)name_copy, update_bounds);
}
void NativeCodeCache::sort() {
if (_count == 0) return;
qsort(_blobs, _count, sizeof(CodeBlob), CodeBlob::comparator);
@@ -106,25 +104,7 @@ void CodeCache::sort() {
if (_max_address == NO_MAX_ADDRESS) _max_address = _blobs[_count - 1]._end;
}
void CodeCache::mark(NamePredicate predicate) {
for (int i = 0; i < _count; i++) {
const char* blob_name = _blobs[i]._name;
if (blob_name != NULL && predicate(blob_name)) {
NativeFunc::mark(blob_name);
}
}
}
const char* CodeCache::find(const void* address) {
for (int i = 0; i < _count; i++) {
if (address >= _blobs[i]._start && address < _blobs[i]._end) {
return _blobs[i]._name;
}
}
return NULL;
}
const char* CodeCache::binarySearch(const void* address) {
const char* NativeCodeCache::binarySearch(const void* address) {
int low = 0;
int high = _count - 1;
@@ -135,21 +115,21 @@ const char* CodeCache::binarySearch(const void* address) {
} else if (_blobs[mid]._start > address) {
high = mid - 1;
} else {
return _blobs[mid]._name;
return (const char*)_blobs[mid]._method;
}
}
// 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 _blobs[low - 1]._name;
return (const char*)_blobs[low - 1]._method;
}
return _name;
}
const void* CodeCache::findSymbol(const char* name) {
const void* NativeCodeCache::findSymbol(const char* name) {
for (int i = 0; i < _count; i++) {
const char* blob_name = _blobs[i]._name;
const char* blob_name = (const char*)_blobs[i]._method;
if (blob_name != NULL && strcmp(blob_name, name) == 0) {
return _blobs[i]._start;
}
@@ -157,54 +137,16 @@ const void* CodeCache::findSymbol(const char* name) {
return NULL;
}
const void* CodeCache::findSymbolByPrefix(const char* prefix) {
const void* NativeCodeCache::findSymbolByPrefix(const char* prefix) {
return findSymbolByPrefix(prefix, strlen(prefix));
}
const void* CodeCache::findSymbolByPrefix(const char* prefix, int prefix_len) {
const void* NativeCodeCache::findSymbolByPrefix(const char* prefix, int prefix_len) {
for (int i = 0; i < _count; i++) {
const char* blob_name = _blobs[i]._name;
const char* blob_name = (const char*)_blobs[i]._method;
if (blob_name != NULL && strncmp(blob_name, prefix, prefix_len) == 0) {
return _blobs[i]._start;
}
}
return NULL;
}
void CodeCache::setGlobalOffsetTable(const void* start, unsigned int size) {
_got_start = (const void**) start;
_got_end = (const void**) ((const char*)start + size);
}
const void** CodeCache::findGlobalOffsetEntry(const void* address) {
for (const void** entry = _got_start; entry < _got_end; entry++) {
if (*entry == address) {
return entry;
}
}
return NULL;
}
void CodeCache::setDwarfTable(FrameDesc* table, int length) {
_dwarf_table = table;
_dwarf_table_length = length;
}
FrameDesc* CodeCache::findFrameDesc(const void* pc) {
u32 target_loc = (const char*)pc - _text_base;
int low = 0;
int high = _dwarf_table_length - 1;
while (low <= high) {
int mid = (unsigned int)(low + high) >> 1;
if (_dwarf_table[mid].loc < target_loc) {
low = mid + 1;
} else if (_dwarf_table[mid].loc > target_loc) {
high = mid - 1;
} else {
return &_dwarf_table[mid];
}
}
return low > 0 ? &_dwarf_table[low - 1] : NULL;
}

View File

@@ -23,45 +23,14 @@
#define NO_MIN_ADDRESS ((const void*)-1)
#define NO_MAX_ADDRESS ((const void*)0)
typedef bool (*NamePredicate)(const char* name);
const int INITIAL_CODE_CACHE_CAPACITY = 1000;
class NativeFunc {
private:
short _lib_index;
char _mark;
char _reserved;
char _name[0];
static NativeFunc* from(const char* name) {
return (NativeFunc*)(name - sizeof(NativeFunc));
}
public:
static char* create(const char* name, short lib_index);
static void destroy(char* name);
static short libIndex(const char* name) {
return from(name)->_lib_index;
}
static bool isMarked(const char* name) {
return from(name)->_mark != 0;
}
static void mark(const char* name) {
from(name)->_mark = 1;
}
};
class CodeBlob {
public:
const void* _start;
const void* _end;
char* _name;
jmethodID _method;
static int comparator(const void* c1, const void* c2) {
CodeBlob* cb1 = (CodeBlob*)c1;
@@ -79,84 +48,60 @@ class CodeBlob {
};
class FrameDesc;
class CodeCache {
protected:
char* _name;
short _lib_index;
const void* _min_address;
const void* _max_address;
const char* _text_base;
const void** _got_start;
const void** _got_end;
FrameDesc* _dwarf_table;
int _dwarf_table_length;
int _capacity;
int _count;
CodeBlob* _blobs;
const void* _min_address;
const void* _max_address;
void expand();
public:
CodeCache(const char* name,
short lib_index = -1,
const void* min_address = NO_MIN_ADDRESS,
const void* max_address = NO_MAX_ADDRESS);
~CodeCache();
const char* name() const {
return _name;
CodeCache() {
_capacity = INITIAL_CODE_CACHE_CAPACITY;
_count = 0;
_blobs = new CodeBlob[_capacity];
_min_address = NO_MIN_ADDRESS;
_max_address = NO_MAX_ADDRESS;
}
const void* minAddress() const {
return _min_address;
~CodeCache() {
delete[] _blobs;
}
const void* maxAddress() const {
return _max_address;
}
bool contains(const void* address) const {
bool contains(const void* address) {
return address >= _min_address && address < _max_address;
}
const char* textBase() const {
return _text_base;
}
void add(const void* start, int length, jmethodID method, bool update_bounds = false);
void remove(const void* start, jmethodID method);
jmethodID find(const void* address);
};
void setTextBase(const char* text_base) {
_text_base = text_base;
}
const void** gotStart() const {
return _got_start;
}
class NativeCodeCache : public CodeCache {
private:
char* _name;
const void** gotEnd() const {
return _got_end;
public:
NativeCodeCache(const char* name,
const void* min_address = NO_MIN_ADDRESS,
const void* max_address = NO_MAX_ADDRESS);
~NativeCodeCache();
const char* name() {
return _name;
}
void add(const void* start, int length, const char* name, bool update_bounds = false);
void updateBounds(const void* start, const void* end);
void sort();
void mark(NamePredicate predicate);
const char* find(const void* address);
const char* binarySearch(const void* address);
const void* findSymbol(const char* name);
const void* findSymbolByPrefix(const char* prefix);
const void* findSymbolByPrefix(const char* prefix, int prefix_len);
void setGlobalOffsetTable(const void* start, unsigned int size);
const void** findGlobalOffsetEntry(const void* address);
void setDwarfTable(FrameDesc* table, int length);
FrameDesc* findFrameDesc(const void* pc);
};
#endif // _CODECACHE_H

View File

@@ -107,15 +107,13 @@ public class FlameGraph {
}
public void dump(PrintStream out) {
mintotal = (long) (root.total * minwidth / 100);
int depth = mintotal > 1 ? root.depth(mintotal) : this.depth + 1;
out.print(applyReplacements(HEADER,
"{title}", title,
"{height}", Math.min(depth * 16, 32767),
"{depth}", depth,
"{height}", (depth + 1) * 16,
"{depth}", depth + 1,
"{reverse}", reverse));
mintotal = (long) (root.total * minwidth / 100);
printFrame(out, "all", root, 0, 0);
out.print(FOOTER);
@@ -145,9 +143,6 @@ public class FlameGraph {
private void printFrame(PrintStream out, String title, Frame frame, int level, long x) {
int type = frameType(title);
title = stripSuffix(title);
if (title.indexOf('\\') >= 0) {
title = title.replace("\\", "\\\\");
}
if (title.indexOf('\'') >= 0) {
title = title.replace("'", "\\'");
}
@@ -174,18 +169,17 @@ public class FlameGraph {
private int frameType(String title) {
if (title.endsWith("_[j]")) {
return 1;
return 0;
} else if (title.endsWith("_[i]")) {
return 2;
return 1;
} else if (title.endsWith("_[k]")) {
return 5;
return 2;
} else if (title.contains("::") || title.startsWith("-[") || title.startsWith("+[")) {
return 4;
} else if (title.indexOf('/') > 0 && title.charAt(0) != '['
|| title.indexOf('.') > 0 && Character.isUpperCase(title.charAt(0))) {
return 3;
} else if (title.indexOf('/') > 0 || title.indexOf('.') > 0 && Character.isUpperCase(title.charAt(0))) {
return 0;
} else {
return 3;
return 4;
}
}
@@ -217,18 +211,6 @@ public class FlameGraph {
}
return child;
}
int depth(long cutoff) {
int depth = 0;
if (size() > 0) {
for (Frame child : values()) {
if (child.total >= cutoff) {
depth = Math.max(depth, child.depth(cutoff));
}
}
}
return depth + 1;
}
}
private static final String HEADER = "<!DOCTYPE html>\n" +
@@ -282,12 +264,11 @@ public class FlameGraph {
"\tc.font = document.body.style.font;\n" +
"\n" +
"\tconst palette = [\n" +
"\t\t[0xa6e1a6, 20, 20, 20],\n" +
"\t\t[0x50e150, 30, 30, 30],\n" +
"\t\t[0x50cccc, 30, 30, 30],\n" +
"\t\t[0xe15a5a, 30, 40, 40],\n" +
"\t\t[0xc8c83c, 30, 30, 10],\n" +
"\t\t[0x50bebe, 30, 30, 30],\n" +
"\t\t[0xe17d00, 30, 30, 0],\n" +
"\t\t[0xc8c83c, 30, 30, 10],\n" +
"\t\t[0xe15a5a, 30, 40, 40],\n" +
"\t];\n" +
"\n" +
"\tfunction getColor(p) {\n" +
@@ -362,12 +343,12 @@ public class FlameGraph {
"\t\tfunction totalMarked() {\n" +
"\t\t\tlet total = 0;\n" +
"\t\t\tlet left = 0;\n" +
"\t\t\tObject.keys(marked).sort(function(a, b) { return a - b; }).forEach(function(x) {\n" +
"\t\t\tfor (let x in marked) {\n" +
"\t\t\t\tif (+x >= left) {\n" +
"\t\t\t\t\ttotal += marked[x];\n" +
"\t\t\t\t\tleft = +x + marked[x];\n" +
"\t\t\t\t}\n" +
"\t\t\t});\n" +
"\t\t\t}\n" +
"\t\t\treturn total;\n" +
"\t\t}\n" +
"\n" +

View File

@@ -19,24 +19,15 @@ import one.jfr.Dictionary;
import one.jfr.JfrReader;
import one.jfr.MethodRef;
import one.jfr.StackTrace;
import one.jfr.event.AllocationSample;
import one.jfr.event.ContendedLock;
import one.jfr.event.Event;
import one.jfr.event.EventAggregator;
import one.jfr.event.ExecutionSample;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
/**
* Converts .jfr output produced by async-profiler to HTML Flame Graph.
*/
public class jfr2flame {
private static final String[] FRAME_SUFFIX = {"_[j]", "_[j]", "_[i]", "", "", "_[k]"};
private static final byte JAVA_FRAME_TYPES = 3;
private static final int FRAME_KERNEL = 5;
private final JfrReader jfr;
private final Dictionary<String> methodNames = new Dictionary<>();
@@ -45,133 +36,40 @@ public class jfr2flame {
this.jfr = jfr;
}
public void convert(final FlameGraph fg, final boolean threads, final boolean total,
final boolean lines, final boolean bci,
final Class<? extends Event> eventClass) throws IOException {
EventAggregator agg = new EventAggregator(threads, total);
for (Event event; (event = jfr.readEvent(eventClass)) != null; ) {
agg.collect(event);
}
final double ticksToNanos = 1e9 / jfr.ticksPerSec;
final boolean scale = total && eventClass == ContendedLock.class && ticksToNanos != 1.0;
public void convert(final FlameGraph fg) {
// Don't use lambda for faster startup
agg.forEach(new EventAggregator.Visitor() {
jfr.stackTraces.forEach(new Dictionary.Visitor<StackTrace>() {
@Override
public void visit(Event event, long value) {
StackTrace stackTrace = jfr.stackTraces.get(event.stackTraceId);
if (stackTrace != null) {
long[] methods = stackTrace.methods;
byte[] types = stackTrace.types;
int[] locations = stackTrace.locations;
String classFrame = getClassFrame(event);
String[] trace = new String[methods.length + (threads ? 1 : 0) + (classFrame != null ? 1 : 0)];
if (threads) {
trace[0] = getThreadFrame(event.tid);
}
int idx = trace.length;
if (classFrame != null) {
trace[--idx] = classFrame;
}
for (int i = 0; i < methods.length; i++) {
String methodName = getMethodName(methods[i], types[i]);
int location;
if (lines && (location = locations[i] >>> 16) != 0) {
methodName += ":" + location;
} else if (bci && (location = locations[i] & 0xffff) != 0) {
methodName += "@" + location;
}
trace[--idx] = methodName + FRAME_SUFFIX[types[i]];
}
fg.addSample(trace, scale ? (long) (value * ticksToNanos) : value);
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 getThreadFrame(int tid) {
String threadName = jfr.threads.get(tid);
return threadName == null ? "[tid=" + tid + ']' : '[' + threadName + " tid=" + tid + ']';
}
private String getClassFrame(Event event) {
long classId;
String suffix;
if (event instanceof AllocationSample) {
classId = ((AllocationSample) event).classId;
suffix = ((AllocationSample) event).tlabSize == 0 ? "_[k]" : "_[i]";
} else if (event instanceof ContendedLock) {
classId = ((ContendedLock) event).classId;
suffix = "_[i]";
} else {
return null;
}
ClassRef cls = jfr.classes.get(classId);
if (cls == null) {
return "null";
}
byte[] className = jfr.symbols.get(cls.name);
int arrayDepth = 0;
while (className[arrayDepth] == '[') {
arrayDepth++;
}
StringBuilder sb = new StringBuilder(toJavaClassName(className, arrayDepth));
while (arrayDepth-- > 0) {
sb.append("[]");
}
return sb.append(suffix).toString();
}
private String toJavaClassName(byte[] symbol, int start) {
switch (symbol[start]) {
case 'B':
return "byte";
case 'C':
return "char";
case 'S':
return "short";
case 'I':
return "int";
case 'J':
return "long";
case 'Z':
return "boolean";
case 'F':
return "float";
case 'D':
return "double";
case 'L':
return new String(symbol, start + 1, symbol.length - start - 2, StandardCharsets.UTF_8).replace('/', '.');
default:
return new String(symbol, start, symbol.length - start, StandardCharsets.UTF_8).replace('/', '.');
}
}
private String getMethodName(long methodId, byte methodType) {
private String getMethodName(long methodId, int type) {
String result = methodNames.get(methodId);
if (result != null) {
return result;
}
MethodRef method = jfr.methods.get(methodId);
if (method == null) {
result = "unknown";
} else {
ClassRef cls = jfr.classes.get(method.cls);
byte[] className = jfr.symbols.get(cls.name);
byte[] methodName = jfr.symbols.get(method.name);
ClassRef cls = jfr.classes.get(method.cls);
byte[] className = jfr.symbols.get(cls.name);
byte[] methodName = jfr.symbols.get(method.name);
if (methodType >= JAVA_FRAME_TYPES || className == null || className.length == 0) {
result = new String(methodName, StandardCharsets.UTF_8);
} else {
String classStr = new String(className, StandardCharsets.UTF_8);
String methodStr = new String(methodName, StandardCharsets.UTF_8);
result = classStr + '.' + methodStr;
}
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);
@@ -182,34 +80,11 @@ public class jfr2flame {
FlameGraph fg = new FlameGraph(args);
if (fg.input == null) {
System.out.println("Usage: java " + jfr2flame.class.getName() + " [options] input.jfr [output.html]");
System.out.println();
System.out.println("options include all supported FlameGraph options, plus the following:");
System.out.println(" --alloc Allocation Flame Graph");
System.out.println(" --lock Lock contention Flame Graph");
System.out.println(" --threads Split profile by threads");
System.out.println(" --total Accumulate the total value (time, bytes, etc.)");
System.out.println(" --lines Show line numbers");
System.out.println(" --bci Show bytecode indices");
System.exit(1);
}
HashSet<String> options = new HashSet<>(Arrays.asList(args));
boolean threads = options.contains("--threads");
boolean total = options.contains("--total");
boolean lines = options.contains("--lines");
boolean bci = options.contains("--bci");
Class<? extends Event> eventClass;
if (options.contains("--alloc")) {
eventClass = AllocationSample.class;
} else if (options.contains("--lock")) {
eventClass = ContendedLock.class;
} else {
eventClass = ExecutionSample.class;
}
try (JfrReader jfr = new JfrReader(fg.input)) {
new jfr2flame(jfr).convert(fg, threads, total, lines, bci, eventClass);
new jfr2flame(jfr).convert(fg);
}
fg.dump();

View File

@@ -15,12 +15,11 @@
*/
import one.jfr.ClassRef;
import one.jfr.Dictionary;
import one.jfr.JfrReader;
import one.jfr.MethodRef;
import one.jfr.Sample;
import one.jfr.StackTrace;
import one.jfr.event.Event;
import one.jfr.event.EventAggregator;
import one.jfr.event.ExecutionSample;
import one.proto.Proto;
import java.io.File;
@@ -28,7 +27,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
/**
* Converts .jfr output produced by async-profiler to nflxprofile format
@@ -38,27 +36,23 @@ import java.util.List;
public class jfr2nflx {
private static final String[] FRAME_TYPE = {"jit", "jit", "inlined", "user", "user", "kernel"};
private static final byte JAVA_FRAME_TYPES = 3;
private static final byte[] NO_STACK = "[no_stack]".getBytes();
private static final byte[] UNKNOWN = "[unknown]".getBytes();
private final JfrReader jfr;
private final List<ExecutionSample> samples;
public jfr2nflx(JfrReader jfr) throws IOException {
public jfr2nflx(JfrReader jfr) {
this.jfr = jfr;
this.samples = jfr.readAllEvents(ExecutionSample.class);
}
public void dump(OutputStream out) throws IOException {
long startTime = System.nanoTime();
int size = samples.size();
long durationTicks = size == 0 ? 0 : samples.get(size - 1).time - jfr.startTicks + 1;
int samples = jfr.samples.size();
long durationTicks = samples == 0 ? 0 : jfr.samples.get(samples - 1).time - jfr.startTicks + 1;
final Proto profile = new Proto(200000)
.field(1, 0.0)
.field(2, Math.max(jfr.durationNanos() / 1e9, durationTicks / (double) jfr.ticksPerSec))
.field(2, Math.max(jfr.durationNanos / 1e9, durationTicks / (double) jfr.ticksPerSec))
.field(3, packSamples())
.field(4, packDeltas())
.field(6, "async-profiler")
@@ -69,23 +63,15 @@ public class jfr2nflx {
final Proto nodes = new Proto(10000);
final Proto node = new Proto(10000);
EventAggregator agg = new EventAggregator(false, false);
for (ExecutionSample sample : samples) {
agg.collect(sample);
}
// Don't use lambda for faster startup
agg.forEach(new EventAggregator.Visitor() {
jfr.stackTraces.forEach(new Dictionary.Visitor<StackTrace>() {
@Override
public void visit(Event event, long value) {
StackTrace stackTrace = jfr.stackTraces.get(event.stackTraceId);
if (stackTrace != null) {
profile.field(5, nodes
.field(1, event.stackTraceId)
.field(2, packNode(node, stackTrace)));
nodes.reset();
node.reset();
}
public void visit(long id, StackTrace stackTrace) {
profile.field(5, nodes
.field(1, (int) id)
.field(2, packNode(node, stackTrace)));
nodes.reset();
node.reset();
}
});
@@ -100,13 +86,13 @@ public class jfr2nflx {
byte[] types = stackTrace.types;
int top = methods.length - 1;
node.field(1, top >= 0 ? getMethodName(methods[top], types[top]) : NO_STACK);
node.field(1, top >= 0 ? getMethodName(methods[top]) : NO_STACK);
node.field(2, 1);
node.field(4, top >= 0 ? FRAME_TYPE[types[top]] : "user");
for (Proto frame = new Proto(100); --top >= 0; frame.reset()) {
node.field(10, frame
.field(1, getMethodName(methods[top], types[top]))
.field(1, getMethodName(methods[top]))
.field(2, FRAME_TYPE[types[top]]));
}
@@ -115,7 +101,7 @@ public class jfr2nflx {
private Proto packSamples() {
Proto proto = new Proto(10000);
for (ExecutionSample sample : samples) {
for (Sample sample : jfr.samples) {
proto.writeInt(sample.stackTraceId);
}
return proto;
@@ -125,7 +111,7 @@ public class jfr2nflx {
Proto proto = new Proto(10000);
double ticksPerSec = jfr.ticksPerSec;
long prevTime = jfr.startTicks;
for (ExecutionSample sample : samples) {
for (Sample sample : jfr.samples) {
proto.writeDouble((sample.time - prevTime) / ticksPerSec);
prevTime = sample.time;
}
@@ -134,23 +120,19 @@ public class jfr2nflx {
private Proto packTids() {
Proto proto = new Proto(10000);
for (ExecutionSample sample : samples) {
for (Sample sample : jfr.samples) {
proto.writeInt(sample.tid);
}
return proto;
}
private byte[] getMethodName(long methodId, byte methodType) {
private byte[] getMethodName(long methodId) {
MethodRef method = jfr.methods.get(methodId);
if (method == null) {
return UNKNOWN;
}
ClassRef cls = jfr.classes.get(method.cls);
byte[] className = jfr.symbols.get(cls.name);
byte[] methodName = jfr.symbols.get(method.name);
if (methodType >= JAVA_FRAME_TYPES || className == null || className.length == 0) {
if (className == null || className.length == 0) {
return methodName;
} else {
byte[] fullName = Arrays.copyOf(className, className.length + 1 + methodName.length);

View File

@@ -31,32 +31,22 @@ public class Dictionary<T> {
this.values = new Object[INITIAL_CAPACITY];
}
public void clear() {
keys = new long[INITIAL_CAPACITY];
values = new Object[INITIAL_CAPACITY];
size = 0;
}
public void put(long key, T value) {
if (key == 0) {
throw new IllegalArgumentException("Zero key not allowed");
}
if (++size * 2 > keys.length) {
resize(keys.length * 2);
}
int mask = keys.length - 1;
int i = hashCode(key) & mask;
while (keys[i] != 0) {
if (keys[i] == key) {
values[i] = value;
return;
}
while (keys[i] != 0 && keys[i] != key) {
i = (i + 1) & mask;
}
keys[i] = key;
values[i] = value;
if (++size * 2 > keys.length) {
resize(keys.length * 2);
}
}
@SuppressWarnings("unchecked")
@@ -79,8 +69,9 @@ public class Dictionary<T> {
}
public int preallocate(int count) {
if (count * 2 > keys.length) {
resize(Integer.highestOneBit(count * 4 - 1));
int newSize = size + count;
if (newSize * 2 > keys.length) {
resize(Integer.highestOneBit(newSize * 4 - 1));
}
return count;
}
@@ -107,7 +98,6 @@ public class Dictionary<T> {
}
private static int hashCode(long key) {
key *= 0xc6a4a7935bd1e995L;
return (int) (key ^ (key >>> 32));
}

View File

@@ -16,11 +16,6 @@
package one.jfr;
import one.jfr.event.AllocationSample;
import one.jfr.event.ContendedLock;
import one.jfr.event.Event;
import one.jfr.event.ExecutionSample;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -38,19 +33,17 @@ import java.util.Map;
* Parses JFR output produced by async-profiler.
*/
public class JfrReader implements Closeable {
private static final int BUFFER_SIZE = 2 * 1024 * 1024;
private static final int CHUNK_HEADER_SIZE = 68;
private static final int CHUNK_SIGNATURE = 0x464c5200;
private static final int CPOOL_OFFSET = 16;
private static final int META_OFFSET = 24;
private final FileChannel ch;
private ByteBuffer buf;
private long filePosition;
private final ByteBuffer buf;
public boolean incomplete;
public long startNanos = Long.MAX_VALUE;
public long endNanos = Long.MIN_VALUE;
public long startTicks = Long.MAX_VALUE;
public long ticksPerSec;
public final long startNanos;
public final long durationNanos;
public final long startTicks;
public final long ticksPerSec;
public final Dictionary<JfrClass> types = new Dictionary<>();
public final Map<String, JfrClass> typesByName = new HashMap<>();
@@ -61,27 +54,29 @@ public class JfrReader implements Closeable {
public final Dictionary<StackTrace> stackTraces = new Dictionary<>();
public final Map<Integer, String> frameTypes = new HashMap<>();
public final Map<Integer, String> threadStates = new HashMap<>();
public final Map<String, String> settings = new HashMap<>();
private int executionSample;
private int nativeMethodSample;
private int allocationInNewTLAB;
private int allocationOutsideTLAB;
private int allocationSample;
private int monitorEnter;
private int threadPark;
private int activeSetting;
private boolean activeSettingHasStack;
public final List<Sample> samples = new ArrayList<>();
public JfrReader(String fileName) throws IOException {
this.ch = FileChannel.open(Paths.get(fileName), StandardOpenOption.READ);
this.buf = ByteBuffer.allocateDirect(BUFFER_SIZE);
this.buf = ch.map(FileChannel.MapMode.READ_ONLY, 0, ch.size());
buf.flip();
ensureBytes(CHUNK_HEADER_SIZE);
if (!readChunk(0)) {
throw new IOException("Incomplete JFR file");
if (buf.getInt(0) != 0x464c5200) {
throw new IOException("Not a valid JFR file");
}
int version = buf.getInt(4);
if (version < 0x20000 || version > 0x2ffff) {
throw new IOException("Unsupported JFR version: " + (version >>> 16) + "." + (version & 0xffff));
}
this.startNanos = buf.getLong(32);
this.durationNanos = buf.getLong(40);
this.startTicks = buf.getLong(48);
this.ticksPerSec = buf.getLong(56);
readMeta();
readConstantPool();
readEvents();
}
@Override
@@ -89,144 +84,9 @@ public class JfrReader implements Closeable {
ch.close();
}
public long durationNanos() {
return endNanos - startNanos;
}
public List<Event> readAllEvents() throws IOException {
return readAllEvents(null);
}
public <E extends Event> List<E> readAllEvents(Class<E> cls) throws IOException {
ArrayList<E> events = new ArrayList<>();
for (E event; (event = readEvent(cls)) != null; ) {
events.add(event);
}
Collections.sort(events);
return events;
}
public Event readEvent() throws IOException {
return readEvent(null);
}
@SuppressWarnings("unchecked")
public <E extends Event> E readEvent(Class<E> cls) throws IOException {
while (ensureBytes(CHUNK_HEADER_SIZE)) {
int pos = buf.position();
int size = getVarint();
int type = getVarint();
if (type == 'L' && buf.getInt(pos) == CHUNK_SIGNATURE) {
if (readChunk(pos)) {
continue;
}
break;
}
if (type == executionSample || type == nativeMethodSample) {
if (cls == null || cls == ExecutionSample.class) return (E) readExecutionSample();
} else if (type == allocationInNewTLAB) {
if (cls == null || cls == AllocationSample.class) return (E) readAllocationSample(true);
} else if (type == allocationOutsideTLAB || type == allocationSample) {
if (cls == null || cls == AllocationSample.class) return (E) readAllocationSample(false);
} else if (type == monitorEnter) {
if (cls == null || cls == ContendedLock.class) return (E) readContendedLock(false);
} else if (type == threadPark) {
if (cls == null || cls == ContendedLock.class) return (E) readContendedLock(true);
} else if (type == activeSetting) {
readActiveSetting();
}
if ((pos += size) <= buf.limit()) {
buf.position(pos);
} else {
seek(filePosition + pos);
}
}
return null;
}
private ExecutionSample readExecutionSample() {
long time = getVarlong();
int tid = getVarint();
int stackTraceId = getVarint();
int threadState = getVarint();
return new ExecutionSample(time, tid, stackTraceId, threadState);
}
private AllocationSample readAllocationSample(boolean tlab) {
long time = getVarlong();
int tid = getVarint();
int stackTraceId = getVarint();
int classId = getVarint();
long allocationSize = getVarlong();
long tlabSize = tlab ? getVarlong() : 0;
return new AllocationSample(time, tid, stackTraceId, classId, allocationSize, tlabSize);
}
private ContendedLock readContendedLock(boolean hasTimeout) {
long time = getVarlong();
long duration = getVarlong();
int tid = getVarint();
int stackTraceId = getVarint();
int classId = getVarint();
if (hasTimeout) getVarlong();
long until = getVarlong();
long address = getVarlong();
return new ContendedLock(time, tid, stackTraceId, duration, classId);
}
private void readActiveSetting() {
long time = getVarlong();
long duration = getVarlong();
int tid = getVarint();
if (activeSettingHasStack) getVarint();
long id = getVarlong();
String name = getString();
String value = getString();
settings.put(name, value);
}
private boolean readChunk(int pos) throws IOException {
if (pos + CHUNK_HEADER_SIZE > buf.limit() || buf.getInt(pos) != CHUNK_SIGNATURE) {
throw new IOException("Not a valid JFR file");
}
int version = buf.getInt(pos + 4);
if (version < 0x20000 || version > 0x2ffff) {
throw new IOException("Unsupported JFR version: " + (version >>> 16) + "." + (version & 0xffff));
}
long cpOffset = buf.getLong(pos + 16);
long metaOffset = buf.getLong(pos + 24);
if (cpOffset == 0 || metaOffset == 0) {
incomplete = true;
return false;
}
startNanos = Math.min(startNanos, buf.getLong(pos + 32));
endNanos = Math.max(endNanos, buf.getLong(pos + 32) + buf.getLong(pos + 40));
startTicks = Math.min(startTicks, buf.getLong(pos + 48));
ticksPerSec = buf.getLong(pos + 56);
types.clear();
typesByName.clear();
long chunkStart = filePosition + pos;
readMeta(chunkStart + metaOffset);
readConstantPool(chunkStart + cpOffset);
cacheEventTypes();
seek(chunkStart + CHUNK_HEADER_SIZE);
return true;
}
private void readMeta(long metaOffset) throws IOException {
seek(metaOffset);
ensureBytes(5);
ensureBytes(getVarint() - buf.position());
private void readMeta() {
buf.position(buf.getInt(META_OFFSET + 4));
getVarint();
getVarint();
getVarlong();
getVarlong();
@@ -273,17 +133,15 @@ public class JfrReader implements Closeable {
}
}
private void readConstantPool(long cpOffset) throws IOException {
long delta;
do {
seek(cpOffset);
ensureBytes(5);
ensureBytes(getVarint() - buf.position());
private void readConstantPool() {
int offset = buf.getInt(CPOOL_OFFSET + 4);
while (true) {
buf.position(offset);
getVarint();
getVarint();
getVarlong();
getVarlong();
delta = getVarlong();
long delta = getVarlong();
getVarint();
int poolCount = getVarint();
@@ -291,7 +149,12 @@ public class JfrReader implements Closeable {
int type = getVarint();
readConstants(types.get(type));
}
} while (delta != 0 && (cpOffset += delta) > 0);
if (delta == 0) {
break;
}
offset += delta;
}
}
private void readConstants(JfrClass type) {
@@ -378,15 +241,13 @@ public class JfrReader implements Closeable {
int depth = getVarint();
long[] methods = new long[depth];
byte[] types = new byte[depth];
int[] locations = new int[depth];
for (int i = 0; i < depth; i++) {
methods[i] = getVarlong();
int line = getVarint();
int bci = getVarint();
locations[i] = line << 16 | (bci & 0xffff);
types[i] = buf.get();
}
return new StackTrace(methods, types, locations);
return new StackTrace(methods, types);
}
private void readSymbols() {
@@ -433,16 +294,36 @@ public class JfrReader implements Closeable {
}
}
private void cacheEventTypes() {
executionSample = getTypeId("jdk.ExecutionSample");
nativeMethodSample = getTypeId("jdk.NativeMethodSample");
allocationInNewTLAB = getTypeId("jdk.ObjectAllocationInNewTLAB");
allocationOutsideTLAB = getTypeId("jdk.ObjectAllocationOutsideTLAB");
allocationSample = getTypeId("jdk.ObjectAllocationSample");
monitorEnter = getTypeId("jdk.JavaMonitorEnter");
threadPark = getTypeId("jdk.ThreadPark");
activeSetting = getTypeId("jdk.ActiveSetting");
activeSettingHasStack = activeSetting >= 0 && typesByName.get("jdk.ActiveSetting").field("stackTrace") != null;
private void readEvents() {
int executionSample = getTypeId("jdk.ExecutionSample");
int nativeMethodSample = getTypeId("jdk.NativeMethodSample");
buf.position(CHUNK_HEADER_SIZE);
while (buf.hasRemaining()) {
int position = buf.position();
int size = getVarint();
int type = getVarint();
if (type == executionSample || type == nativeMethodSample) {
readExecutionSample();
} else {
buf.position(position + size);
}
}
Collections.sort(samples);
}
private void readExecutionSample() {
long time = getVarlong();
int tid = getVarint();
int stackTraceId = getVarint();
int threadState = getVarint();
samples.add(new Sample(time, tid, stackTraceId, threadState));
StackTrace stackTrace = stackTraces.get(stackTraceId);
if (stackTrace != null) {
stackTrace.samples++;
}
}
private int getTypeId(String typeName) {
@@ -500,32 +381,4 @@ public class JfrReader implements Closeable {
buf.get(bytes);
return bytes;
}
private void seek(long pos) throws IOException {
filePosition = pos;
ch.position(pos);
buf.rewind().flip();
}
private boolean ensureBytes(int needed) throws IOException {
if (buf.remaining() >= needed) {
return true;
}
filePosition += buf.position();
if (buf.capacity() < needed) {
ByteBuffer newBuf = ByteBuffer.allocateDirect(needed);
newBuf.put(buf);
buf = newBuf;
} else {
buf.compact();
}
while (ch.read(buf) > 0 && buf.position() < needed) {
// keep reading
}
buf.flip();
return buf.limit() > 0;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2021 Andrei Pangin
* 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.
@@ -14,34 +14,23 @@
* limitations under the License.
*/
package one.jfr.event;
package one.jfr;
public abstract class Event implements Comparable<Event> {
public class Sample implements Comparable<Sample> {
public final long time;
public final int tid;
public final int stackTraceId;
public final int threadState;
protected Event(long time, int tid, int stackTraceId) {
public Sample(long time, int tid, int stackTraceId, int threadState) {
this.time = time;
this.tid = tid;
this.stackTraceId = stackTraceId;
this.threadState = threadState;
}
@Override
public int compareTo(Event o) {
public int compareTo(Sample o) {
return Long.compare(time, o.time);
}
@Override
public int hashCode() {
return stackTraceId;
}
public boolean sameGroup(Event o) {
return getClass() == o.getClass();
}
public long value() {
return 1;
}
}

View File

@@ -19,11 +19,10 @@ package one.jfr;
public class StackTrace {
public final long[] methods;
public final byte[] types;
public final int[] locations;
public long samples;
public StackTrace(long[] methods, byte[] types, int[] locations) {
public StackTrace(long[] methods, byte[] types) {
this.methods = methods;
this.types = types;
this.locations = locations;
}
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package one.jfr.event;
public class AllocationSample extends Event {
public final int classId;
public final long allocationSize;
public final long tlabSize;
public AllocationSample(long time, int tid, int stackTraceId, int classId, long allocationSize, long tlabSize) {
super(time, tid, stackTraceId);
this.classId = classId;
this.allocationSize = allocationSize;
this.tlabSize = tlabSize;
}
@Override
public int hashCode() {
return classId * 127 + stackTraceId + (tlabSize == 0 ? 17 : 0);
}
@Override
public boolean sameGroup(Event o) {
if (o instanceof AllocationSample) {
AllocationSample a = (AllocationSample) o;
return classId == a.classId && (tlabSize == 0) == (a.tlabSize == 0);
}
return false;
}
@Override
public long value() {
return tlabSize != 0 ? tlabSize : allocationSize;
}
}

View File

@@ -1,47 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package one.jfr.event;
public class ContendedLock extends Event {
public final long duration;
public final int classId;
public ContendedLock(long time, int tid, int stackTraceId, long duration, int classId) {
super(time, tid, stackTraceId);
this.duration = duration;
this.classId = classId;
}
@Override
public int hashCode() {
return classId * 127 + stackTraceId;
}
@Override
public boolean sameGroup(Event o) {
if (o instanceof ContendedLock) {
ContendedLock c = (ContendedLock) o;
return classId == c.classId;
}
return false;
}
@Override
public long value() {
return duration;
}
}

View File

@@ -1,103 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package one.jfr.event;
public class EventAggregator {
private static final int INITIAL_CAPACITY = 1024;
private final boolean threads;
private final boolean total;
private Event[] keys;
private long[] values;
private int size;
public EventAggregator(boolean threads, boolean total) {
this.threads = threads;
this.total = total;
this.keys = new Event[INITIAL_CAPACITY];
this.values = new long[INITIAL_CAPACITY];
}
public void collect(Event e) {
int mask = keys.length - 1;
int i = hashCode(e) & mask;
while (keys[i] != null) {
if (sameGroup(keys[i], e)) {
values[i] += total ? e.value() : 1;
return;
}
i = (i + 1) & mask;
}
keys[i] = e;
values[i] = total ? e.value() : 1;
if (++size * 2 > keys.length) {
resize(keys.length * 2);
}
}
public long getValue(Event e) {
int mask = keys.length - 1;
int i = hashCode(e) & mask;
while (keys[i] != null && !sameGroup(keys[i], e)) {
i = (i + 1) & mask;
}
return values[i];
}
public void forEach(Visitor visitor) {
for (int i = 0; i < keys.length; i++) {
if (keys[i] != null) {
visitor.visit(keys[i], values[i]);
}
}
}
private int hashCode(Event e) {
return e.hashCode() + (threads ? e.tid * 31 : 0);
}
private boolean sameGroup(Event e1, Event e2) {
return e1.stackTraceId == e2.stackTraceId && (!threads || e1.tid == e2.tid) && e1.sameGroup(e2);
}
private void resize(int newCapacity) {
Event[] newKeys = new Event[newCapacity];
long[] newValues = new long[newCapacity];
int mask = newKeys.length - 1;
for (int i = 0; i < keys.length; i++) {
if (keys[i] != null) {
for (int j = hashCode(keys[i]) & mask; ; j = (j + 1) & mask) {
if (newKeys[j] == null) {
newKeys[j] = keys[i];
newValues[j] = values[i];
break;
}
}
}
}
keys = newKeys;
values = newValues;
}
public interface Visitor {
void visit(Event event, long value);
}
}

View File

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

View File

@@ -1,353 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdlib.h>
#include "dwarf.h"
#include "log.h"
enum {
DW_CFA_nop = 0x0,
DW_CFA_set_loc = 0x1,
DW_CFA_advance_loc1 = 0x2,
DW_CFA_advance_loc2 = 0x3,
DW_CFA_advance_loc4 = 0x4,
DW_CFA_offset_extended = 0x5,
DW_CFA_restore_extended = 0x6,
DW_CFA_undefined = 0x7,
DW_CFA_same_value = 0x8,
DW_CFA_register = 0x9,
DW_CFA_remember_state = 0xa,
DW_CFA_restore_state = 0xb,
DW_CFA_def_cfa = 0xc,
DW_CFA_def_cfa_register = 0xd,
DW_CFA_def_cfa_offset = 0xe,
DW_CFA_def_cfa_expression = 0xf,
DW_CFA_expression = 0x10,
DW_CFA_offset_extended_sf = 0x11,
DW_CFA_def_cfa_sf = 0x12,
DW_CFA_def_cfa_offset_sf = 0x13,
DW_CFA_val_offset = 0x14,
DW_CFA_val_offset_sf = 0x15,
DW_CFA_val_expression = 0x16,
DW_CFA_GNU_args_size = 0x2e,
DW_CFA_advance_loc = 0x1,
DW_CFA_offset = 0x2,
DW_CFA_restore = 0x3,
};
enum {
DW_OP_breg_pc = 0x70 + DW_REG_PC,
DW_OP_const1u = 0x08,
DW_OP_const1s = 0x09,
DW_OP_const2u = 0x0a,
DW_OP_const2s = 0x0b,
DW_OP_const4u = 0x0c,
DW_OP_const4s = 0x0d,
DW_OP_constu = 0x10,
DW_OP_consts = 0x11,
DW_OP_minus = 0x1c,
DW_OP_plus = 0x22,
};
FrameDesc FrameDesc::default_frame = {0, DW_REG_FP | (2 * DW_STACK_SLOT) << 8, -2 * DW_STACK_SLOT};
DwarfParser::DwarfParser(const char* name, const char* image_base, const char* eh_frame_hdr) {
_name = name;
_image_base = image_base;
_capacity = 128;
_count = 0;
_table = (FrameDesc*)malloc(_capacity * sizeof(FrameDesc));
_prev = NULL;
_code_align = sizeof(instruction_t);
_data_align = -(int)sizeof(void*);
parse(eh_frame_hdr);
}
void DwarfParser::parse(const char* eh_frame_hdr) {
u8 version = eh_frame_hdr[0];
u8 eh_frame_ptr_enc = eh_frame_hdr[1];
u8 fde_count_enc = eh_frame_hdr[2];
u8 table_enc = eh_frame_hdr[3];
if (version != 1 || (eh_frame_ptr_enc & 0x7) != 0x3 || (fde_count_enc & 0x7) != 0x3 || (table_enc & 0xf7) != 0x33) {
Log::warn("Unsupported .eh_frame_hdr [%02x%02x%02x%02x] in %s",
version, eh_frame_ptr_enc, fde_count_enc, table_enc, _name);
return;
}
int fde_count = *(int*)(eh_frame_hdr + 8);
int* table = (int*)(eh_frame_hdr + 16);
for (int i = 0; i < fde_count; i++) {
_ptr = eh_frame_hdr + table[i * 2];
parseFde();
}
}
void DwarfParser::parseCie() {
u32 cie_len = get32();
if (cie_len == 0 || cie_len == 0xffffffff) {
return;
}
const char* cie_start = _ptr;
_ptr += 5;
while (*_ptr++) {}
_code_align = getLeb();
_data_align = getSLeb();
_ptr = cie_start + cie_len;
}
void DwarfParser::parseFde() {
u32 fde_len = get32();
if (fde_len == 0 || fde_len == 0xffffffff) {
return;
}
const char* fde_start = _ptr;
u32 cie_offset = get32();
if (_count == 0) {
_ptr = fde_start - cie_offset;
parseCie();
_ptr = fde_start + 4;
}
u32 range_start = getPtr() - _image_base;
u32 range_len = get32();
_ptr += getLeb();
parseInstructions(range_start, fde_start + fde_len);
addRecord(range_start + range_len, DW_REG_SP, DW_STACK_SLOT, DW_SAME_FP);
}
void DwarfParser::parseInstructions(u32 loc, const char* end) {
const u32 code_align = _code_align;
const int data_align = _data_align;
u32 cfa_reg = DW_REG_SP;
int cfa_off = DW_STACK_SLOT;
int fp_off = DW_SAME_FP;
int pc_off = -DW_STACK_SLOT;
u32 rem_cfa_reg;
int rem_cfa_off;
int rem_fp_off;
int rem_pc_off;
while (_ptr < end) {
u8 op = get8();
switch (op >> 6) {
case 0:
switch (op) {
case DW_CFA_nop:
case DW_CFA_set_loc:
_ptr = end;
break;
case DW_CFA_advance_loc1:
addRecord(loc, cfa_reg, cfa_off, fp_off);
loc += get8() * code_align;
break;
case DW_CFA_advance_loc2:
addRecord(loc, cfa_reg, cfa_off, fp_off);
loc += get16() * code_align;
break;
case DW_CFA_advance_loc4:
addRecord(loc, cfa_reg, cfa_off, fp_off);
loc += get32() * code_align;
break;
case DW_CFA_offset_extended:
switch (getLeb()) {
case DW_REG_FP: fp_off = getLeb() * data_align; break;
case DW_REG_PC: pc_off = getLeb() * data_align; break;
default: skipLeb();
}
break;
case DW_CFA_restore_extended:
case DW_CFA_undefined:
case DW_CFA_same_value:
skipLeb();
break;
case DW_CFA_register:
skipLeb();
skipLeb();
break;
case DW_CFA_remember_state:
rem_cfa_reg = cfa_reg;
rem_cfa_off = cfa_off;
rem_fp_off = fp_off;
rem_pc_off = pc_off;
break;
case DW_CFA_restore_state:
cfa_reg = rem_cfa_reg;
cfa_off = rem_cfa_off;
fp_off = rem_fp_off;
pc_off = rem_pc_off;
break;
case DW_CFA_def_cfa:
cfa_reg = getLeb();
cfa_off = getLeb();
break;
case DW_CFA_def_cfa_register:
cfa_reg = getLeb();
break;
case DW_CFA_def_cfa_offset:
cfa_off = getLeb();
break;
case DW_CFA_def_cfa_expression: {
u32 len = getLeb();
cfa_reg = len == 11 ? DW_REG_PLT : DW_REG_INVALID;
cfa_off = DW_STACK_SLOT;
_ptr += len;
break;
}
case DW_CFA_expression:
skipLeb();
_ptr += getLeb();
break;
case DW_CFA_offset_extended_sf:
switch (getLeb()) {
case DW_REG_FP: fp_off = getSLeb() * data_align; break;
case DW_REG_PC: pc_off = getSLeb() * data_align; break;
default: skipLeb();
}
break;
case DW_CFA_def_cfa_sf:
cfa_reg = getLeb();
cfa_off = getSLeb() * data_align;
break;
case DW_CFA_def_cfa_offset_sf:
cfa_off = getSLeb() * data_align;
break;
case DW_CFA_val_offset:
case DW_CFA_val_offset_sf:
skipLeb();
skipLeb();
break;
case DW_CFA_val_expression:
if (getLeb() == DW_REG_PC) {
int pc_off = parseExpression();
if (pc_off != 0) {
fp_off = DW_PC_OFFSET | (pc_off << 1);
}
} else {
_ptr += getLeb();
}
break;
case DW_CFA_GNU_args_size:
skipLeb();
break;
default:
Log::warn("Unknown DWARF instruction 0x%x in %s", op, _name);
return;
}
break;
case DW_CFA_advance_loc:
addRecord(loc, cfa_reg, cfa_off, fp_off);
loc += (op & 0x3f) * code_align;
break;
case DW_CFA_offset:
switch (op & 0x3f) {
case DW_REG_FP: fp_off = getLeb() * data_align; break;
case DW_REG_PC: pc_off = getLeb() * data_align; break;
default: skipLeb();
}
break;
case DW_CFA_restore:
break;
}
}
addRecord(loc, cfa_reg, cfa_off, fp_off);
}
// Parse a limited subset of DWARF expressions, which is used in DW_CFA_val_expression
// to point to the previous PC relative to the current PC.
// Returns the offset of the previous PC from the current PC.
int DwarfParser::parseExpression() {
int pc_off = 0;
int tos = 0;
u32 len = getLeb();
const char* end = _ptr + len;
while (_ptr < end) {
u8 op = get8();
switch (op) {
case DW_OP_breg_pc:
pc_off = getSLeb();
break;
case DW_OP_const1u:
tos = get8();
break;
case DW_OP_const1s:
tos = (signed char)get8();
break;
case DW_OP_const2u:
tos = get16();
break;
case DW_OP_const2s:
tos = (short)get16();
break;
case DW_OP_const4u:
case DW_OP_const4s:
tos = get32();
break;
case DW_OP_constu:
tos = getLeb();
break;
case DW_OP_consts:
tos = getSLeb();
break;
case DW_OP_minus:
pc_off -= tos;
break;
case DW_OP_plus:
pc_off += tos;
break;
default:
Log::warn("Unknown DWARF opcode 0x%x in %s", op, _name);
_ptr = end;
return 0;
}
}
return pc_off;
}
void DwarfParser::addRecord(u32 loc, u32 cfa_reg, int cfa_off, int fp_off) {
int cfa = cfa_reg | cfa_off << 8;
if (_prev == NULL || (_prev->loc == loc && --_count >= 0) || _prev->cfa != cfa || _prev->fp_off != fp_off) {
_prev = addRecordRaw(loc, cfa, fp_off);
}
}
FrameDesc* DwarfParser::addRecordRaw(u32 loc, int cfa, int fp_off) {
if (_count >= _capacity) {
_capacity *= 2;
_table = (FrameDesc*)realloc(_table, _capacity * sizeof(FrameDesc));
}
FrameDesc* f = &_table[_count++];
f->loc = loc;
f->cfa = cfa;
f->fp_off = fp_off;
return f;
}

View File

@@ -1,160 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _DWARF_H
#define _DWARF_H
#include <stddef.h>
#include "arch.h"
#if defined(__x86_64__)
#define DWARF_SUPPORTED true
const int DW_REG_FP = 6;
const int DW_REG_SP = 7;
const int DW_REG_PC = 16;
#elif defined(__i386__)
#define DWARF_SUPPORTED true
const int DW_REG_FP = 5;
const int DW_REG_SP = 4;
const int DW_REG_PC = 8;
#else
#define DWARF_SUPPORTED false
const int DW_REG_FP = 0;
const int DW_REG_SP = 1;
const int DW_REG_PC = 2;
#endif
const int DW_REG_PLT = 128; // denotes special rule for PLT entries
const int DW_REG_INVALID = 255; // denotes unsupported configuration
const int DW_PC_OFFSET = 1;
const int DW_SAME_FP = 0x80000000;
const int DW_STACK_SLOT = sizeof(void*);
struct FrameDesc {
u32 loc;
int cfa;
int fp_off;
static FrameDesc default_frame;
static int comparator(const void* p1, const void* p2) {
FrameDesc* fd1 = (FrameDesc*)p1;
FrameDesc* fd2 = (FrameDesc*)p2;
return (int)(fd1->loc - fd2->loc);
}
};
class DwarfParser {
private:
const char* _name;
const char* _image_base;
const char* _ptr;
int _capacity;
int _count;
FrameDesc* _table;
FrameDesc* _prev;
u32 _code_align;
int _data_align;
const char* add(size_t size) {
const char* ptr = _ptr;
_ptr = ptr + size;
return ptr;
}
u8 get8() {
return *_ptr++;
}
u16 get16() {
return *(u16*)add(2);
}
u32 get32() {
return *(u32*)add(4);
}
u32 getLeb() {
u32 result = 0;
for (u32 shift = 0; ; shift += 7) {
u8 b = *_ptr++;
result |= (b & 0x7f) << shift;
if ((b & 0x80) == 0) {
return result;
}
}
}
int getSLeb() {
int result = 0;
for (u32 shift = 0; ; shift += 7) {
u8 b = *_ptr++;
result |= (b & 0x7f) << shift;
if ((b & 0x80) == 0) {
if ((b & 0x40) != 0 && (shift += 7) < 32) {
result |= -1 << shift;
}
return result;
}
}
}
void skipLeb() {
while (*_ptr++ & 0x80) {}
}
const char* getPtr() {
const char* ptr = _ptr;
return ptr + *(int*)add(4);
}
void parse(const char* eh_frame_hdr);
void parseCie();
void parseFde();
void parseInstructions(u32 loc, const char* end);
int parseExpression();
void addRecord(u32 loc, u32 cfa_reg, int cfa_off, int fp_off);
FrameDesc* addRecordRaw(u32 loc, int cfa, int fp_off);
public:
DwarfParser(const char* name, const char* image_base, const char* eh_frame_hdr);
FrameDesc* table() const {
return _table;
}
int count() const {
return _count;
}
};
#endif // _DWARF_H

View File

@@ -15,17 +15,60 @@
*/
#include "engine.h"
#include "stackFrame.h"
volatile bool Engine::_enabled = false;
volatile bool Engine::_enabled;
Error Engine::check(Arguments& args) {
return Error::OK;
}
Error Engine::start(Arguments& args) {
return Error::OK;
CStack Engine::cstack() {
return CSTACK_FP;
}
void Engine::stop() {
int Engine::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
CodeCache* java_methods, CodeCache* runtime_stubs) {
const void* pc;
uintptr_t fp;
uintptr_t prev_fp = (uintptr_t)&fp;
uintptr_t bottom = prev_fp + 0x100000;
if (ucontext == NULL) {
pc = __builtin_return_address(0);
fp = (uintptr_t)__builtin_frame_address(1);
} else {
StackFrame frame(ucontext);
pc = (const void*)frame.pc();
fp = frame.fp();
}
int depth = 0;
const void* const valid_pc = (const void* const)0x1000;
// Walk until the bottom of the stack or until the first Java frame
while (depth < max_depth && pc >= valid_pc) {
if (java_methods->contains(pc) || runtime_stubs->contains(pc)) {
break;
}
callchain[depth++] = pc;
// Check if the next frame is below on the current stack
if (fp <= prev_fp || fp >= prev_fp + 0x40000 || fp >= bottom) {
break;
}
// Frame pointer must be word aligned
if ((fp & (sizeof(uintptr_t) - 1)) != 0) {
break;
}
prev_fp = fp;
pc = ((const void**)fp)[1];
fp = ((uintptr_t*)fp)[0];
}
return depth;
}

View File

@@ -18,48 +18,50 @@
#define _ENGINE_H
#include "arguments.h"
#include "codeCache.h"
class Engine {
protected:
static volatile bool _enabled;
static bool updateCounter(volatile unsigned long long& counter, unsigned long long value, unsigned long long interval) {
if (interval <= 1) {
return true;
}
while (true) {
unsigned long long prev = counter;
unsigned long long next = prev + value;
if (next < interval) {
if (__sync_bool_compare_and_swap(&counter, prev, next)) {
return false;
}
} else {
if (__sync_bool_compare_and_swap(&counter, prev, next % interval)) {
return true;
}
}
}
}
public:
virtual const char* title() {
return "Flame Graph";
}
virtual const char* units() {
return "total";
}
virtual const char* name() = 0;
virtual const char* units() = 0;
virtual Error check(Arguments& args);
virtual Error start(Arguments& args);
virtual void stop();
virtual Error start(Arguments& args) = 0;
virtual void stop() = 0;
virtual CStack cstack();
virtual int getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
CodeCache* java_methods, CodeCache* runtime_stubs);
void enableEvents(bool enabled) {
_enabled = enabled;
}
};
class NoopEngine : public Engine {
public:
const char* name() {
return "noop";
}
const char* units() {
return "ns";
}
Error start(Arguments& args) {
return Error::OK;
}
void stop() {
}
CStack cstack() {
return CSTACK_NO;
}
};
#endif // _ENGINE_H

View File

@@ -1,90 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _FDTRANSFER_H
#define _FDTRANSFER_H
#ifdef __linux__
#include <linux/perf_event.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
// base header for all requests
enum request_type {
PERF_FD,
KALLSYMS_FD,
};
struct fd_request {
// of type "enum request_type"
unsigned int type;
};
struct perf_fd_request {
struct fd_request header;
int tid;
struct perf_event_attr attr;
};
struct fd_response {
// of type "enum request_type"
unsigned int type;
// 0 on success, otherwise errno
int error;
};
struct perf_fd_response {
struct fd_response header;
int tid;
};
static inline bool socketPathForPid(int pid, struct sockaddr_un *sun, socklen_t *addrlen) {
sun->sun_path[0] = '\0';
const int max_size = sizeof(sun->sun_path) - 1;
const int path_len = snprintf(sun->sun_path + 1, max_size, "async-profiler-%d", pid);
if (path_len > max_size) {
return false;
}
sun->sun_family = AF_UNIX;
// +1 for the first \0 byte
*addrlen = sizeof(*sun) - (sizeof(sun->sun_path) - (path_len + 1));
return true;
}
static inline bool socketPath(const char *path, struct sockaddr_un *sun, socklen_t *addrlen) {
const int path_len = strlen(path);
if (path_len > sizeof(sun->sun_path)) {
return false;
}
memcpy(sun->sun_path, path, path_len);
sun->sun_family = AF_UNIX;
*addrlen = sizeof(*sun) - (sizeof(sun->sun_path) - path_len);
return true;
}
#endif // __linux__
#endif // _FDTRANSFER_H

View File

@@ -1,378 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include "fdtransfer.h"
#include "../jattach/psutil.h"
class FdTransferServer {
private:
static int _server;
static int _peer;
static int copyFile(const char* src_name, const char* dst_name, mode_t mode);
static bool sendFd(int fd, struct fd_response *resp, size_t resp_size);
public:
static void closeServer() { close(_server); }
static void closePeer() { close(_peer); }
static bool bindServer(struct sockaddr_un *sun, socklen_t addrlen, int accept_timeout);
static bool acceptPeer(int *peer_pid);
static bool serveRequests(int peer_pid);
};
int FdTransferServer::_server;
int FdTransferServer::_peer;
bool FdTransferServer::bindServer(struct sockaddr_un *sun, socklen_t addrlen, int accept_timeout) {
_server = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (_server == -1) {
perror("FdTransfer socket()");
return false;
}
// Arbitrary timeout, to prevent it from listening forever.
if (accept_timeout > 0) {
const struct timeval timeout = {accept_timeout, 0};
if (setsockopt(_server, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
perror("FdTransfer setsockopt(SO_RCVTIMEO)");
close(_server);
return false;
}
}
if (bind(_server, (const struct sockaddr*)sun, addrlen) < 0 || listen(_server, 1) < 0) {
perror("FdTransfer listen()");
close(_server);
return false;
}
return true;
}
bool FdTransferServer::acceptPeer(int *peer_pid) {
_peer = accept(_server, NULL, NULL);
if (_peer == -1) {
perror("FdTransfer accept()");
return false;
}
struct ucred cred;
socklen_t len = sizeof(cred);
if (getsockopt(_peer, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) {
perror("getsockopt(SO_PEERCRED)");
close(_peer);
return false;
}
if (*peer_pid != 0) {
if (cred.pid != *peer_pid) {
fprintf(stderr, "Unexpected connection from PID %d, expected from %d\n", cred.pid, *peer_pid);
close(_peer);
return false;
}
} else {
*peer_pid = cred.pid;
}
return true;
}
bool FdTransferServer::serveRequests(int peer_pid) {
// Close the server side, don't need it anymore.
FdTransferServer::closeServer();
void *perf_mmap_ringbuf[1024] = {};
size_t ringbuf_index = 0;
const size_t perf_mmap_size = 2 * sysconf(_SC_PAGESIZE);
while (1) {
unsigned char request_buf[1024];
struct fd_request *req = (struct fd_request *)request_buf;
ssize_t ret = recv(_peer, req, sizeof(request_buf), 0);
if (ret == 0) {
// EOF means done
return true;
} else if (ret < 0) {
perror("recv()");
break;
}
switch (req->type) {
case PERF_FD: {
struct perf_fd_request *request = (struct perf_fd_request*)req;
int perf_fd = -1;
int error;
// In pid == 0 mode, allow all perf_event_open requests.
// Otherwise, verify the thread belongs to PID.
if (peer_pid == 0 || syscall(__NR_tgkill, peer_pid, request->tid, 0) == 0) {
perf_fd = syscall(__NR_perf_event_open, &request->attr, request->tid, -1, -1, 0);
error = perf_fd < 0 ? errno : 0;
} else {
fprintf(stderr, "Target has requested perf_event_open for TID %d which is not a thread of process %d\n", request->tid, peer_pid);
error = ESRCH;
}
// Map the perf buffer here (mapping perf fds may require privileges, and fdtransfer has them while the target application does not
// necessarily; if pages are already mapped, the same physical pages will be used when the profiler agent maps them again, requiring
// no privileges this time)
if (error == 0) {
// Settings match the mmap() done in PerfEvents::createForThread().
void *map_result = mmap(NULL, perf_mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, perf_fd, 0);
// Ignore errors - if this fails, let it fail again in the profiler again & produce a proper error for the user.
// Free next entry in the ring buffer, if it was previously allocated.
if (perf_mmap_ringbuf[ringbuf_index] != NULL && perf_mmap_ringbuf[ringbuf_index] != MAP_FAILED) {
(void)munmap(perf_mmap_ringbuf[ringbuf_index], perf_mmap_size);
}
// Store it in the ring buffer so we can free it later.
perf_mmap_ringbuf[ringbuf_index] = map_result;
ringbuf_index++;
ringbuf_index = ringbuf_index % ARRAY_SIZE(perf_mmap_ringbuf);
}
struct perf_fd_response resp;
resp.header.type = request->header.type;
resp.header.error = error;
resp.tid = request->tid;
sendFd(perf_fd, &resp.header, sizeof(resp));
close(perf_fd);
break;
}
case KALLSYMS_FD: {
// can't directly pass the fd of /proc/kallsyms, because before Linux 4.15 the permission check
// was conducted on each read.
// it's simpler to copy the file to a temporary location and pass the fd of it (compared to passing the
// entire contents over the peer socket)
char tmp_path[256];
snprintf(tmp_path, sizeof(tmp_path), "/tmp/async-profiler-kallsyms.%d", getpid());
int kallsyms_fd = -1;
int error = copyFile("/proc/kallsyms", tmp_path, 0600);
if (error == 0) {
kallsyms_fd = open(tmp_path, O_RDONLY);
if (kallsyms_fd == -1) {
error = errno;
} else {
unlink(tmp_path);
}
}
struct fd_response resp;
resp.type = req->type;
resp.error = error;
sendFd(kallsyms_fd, &resp, sizeof(resp));
close(kallsyms_fd);
break;
}
default:
fprintf(stderr, "Unknown request type %u\n", req->type);
break;
}
}
return false;
}
int FdTransferServer::copyFile(const char* src_name, const char* dst_name, mode_t mode) {
int src = open(src_name, O_RDONLY);
if (src == -1) {
return errno;
}
int dst = creat(dst_name, mode);
if (dst == -1) {
int result = errno;
close(src);
return result;
}
// copy_file_range() doesn't exist in older kernels, sendfile() no longer works in newer ones
char buf[65536];
ssize_t r;
while ((r = read(src, buf, sizeof(buf))) > 0) {
ssize_t w = write(dst, buf, r);
(void)w;
}
close(dst);
close(src);
return 0;
}
bool FdTransferServer::sendFd(int fd, struct fd_response *resp, size_t resp_size) {
struct msghdr msg = {0};
struct iovec iov[1];
iov[0].iov_base = resp;
iov[0].iov_len = resp_size;
msg.msg_iov = iov;
msg.msg_iovlen = ARRAY_SIZE(iov);
union {
char buf[CMSG_SPACE(sizeof(fd))];
struct cmsghdr align;
} u;
if (fd != -1) {
msg.msg_control = u.buf;
msg.msg_controllen = sizeof(u.buf);
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
}
ssize_t ret = sendmsg(_peer, &msg, 0);
if (ret < 0) {
perror("sendmsg()");
return false;
}
return true;
}
static int single_pid_server(int pid) {
// get its nspid prior to moving to its PID namespace.
int nspid;
uid_t _target_uid;
gid_t _target_gid;
if (get_process_info(pid, &_target_uid, &_target_gid, &nspid)) {
fprintf(stderr, "Process %d not found\n", pid);
return 1;
}
struct sockaddr_un sun;
socklen_t addrlen;
if (!socketPathForPid(nspid, &sun, &addrlen)) {
fprintf(stderr, "Path too long\n");
return 1;
}
// Create the server before forking, so w're ready to accept connections once our parent
// exits.
if (enter_ns(pid, "net") == -1) {
fprintf(stderr, "Failed to enter the net NS of target process %d\n", pid);
return 1;
}
if (!FdTransferServer::bindServer(&sun, addrlen, 10)) {
return 1;
}
if (!enter_ns(pid, "pid") == -1) {
fprintf(stderr, "Failed to enter the PID NS of target process %d\n", pid);
return 1;
}
// CLONE_NEWPID affects children only - so we fork here.
if (0 == fork()) {
return FdTransferServer::acceptPeer(&nspid) && FdTransferServer::serveRequests(nspid) ? 0 : 1;
} else {
// Exit now, let our caller continue.
return 0;
}
}
static int path_server(const char *path) {
struct sockaddr_un sun;
socklen_t addrlen;
struct sigaction sigchld_action;
sigchld_action.sa_handler = SIG_DFL;
sigchld_action.sa_flags = SA_NOCLDWAIT;
sigaction(SIGCHLD, &sigchld_action, NULL);
if (!socketPath(path, &sun, &addrlen)) {
fprintf(stderr, "Path '%s' is too long\n", path);
return 1;
}
if (!FdTransferServer::bindServer(&sun, addrlen, 0)) {
return 1;
}
printf("Server ready at '%s'\n", path);
while (1) {
int peer_pid = 0;
if (!FdTransferServer::acceptPeer(&peer_pid)) {
return 1;
}
// Enter its PID namespace.
if (enter_ns(peer_pid, "pid") == -1) {
fprintf(stderr, "Failed to enter the PID NS of target process %d\n", peer_pid);
return 1;
}
printf("Serving PID %d\n", peer_pid);
// We fork(), to actually move a PID namespace.
if (0 == fork()) {
return FdTransferServer::serveRequests(0) ? 0 : 1;
} else {
FdTransferServer::closePeer();
}
// Move back to our original PID namespace (reverts pid_for_children)
if (enter_ns(getpid(), "pid") == -1) {
fprintf(stderr, "Failed to exit the PID NS of target process %d\n", peer_pid);
return 1;
}
}
}
int main(int argc, const char** argv) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <pid>|<path>\n", argv[0]);
return 1;
}
int pid = atoi(argv[1]);
// 2 modes:
// pid == 0 - bind on a path and accept requests forever, from any PID, until being killed
// pid != 0 - bind on an abstract namespace UDS for that PID, accept requests only from that PID
// until the single connection is closed.
if (pid != 0) {
return single_pid_server(pid);
} else {
return path_server(argv[1]);
}
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _FDTRANSFER_CLIENT_H
#define _FDTRANSFER_CLIENT_H
#ifdef __linux__
#include "fdtransfer/fdtransfer.h"
class FdTransferClient {
private:
static int _peer;
static int recvFd(unsigned int request_id, struct fd_response *resp, size_t resp_size);
public:
static bool connectToServer(const char *path, int pid);
static bool hasPeer() { return _peer != -1; }
static void closePeer() {
if (_peer != -1) {
close(_peer);
_peer = -1;
}
}
static int requestPerfFd(int *tid, struct perf_event_attr *attr);
static int requestKallsymsFd();
};
#else
class FdTransferClient {
public:
static bool connectToServer(const char *path, int pid) { return false; }
static bool hasPeer() { return false; }
static void closePeer() { }
};
#endif // __linux__
#endif // _FDTRANSFER_CLIENT_H

View File

@@ -1,149 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef __linux__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include "fdtransferClient.h"
#include "log.h"
int FdTransferClient::_peer = -1;
bool FdTransferClient::connectToServer(const char *path, int pid) {
_peer = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (_peer == -1) {
Log::warn("FdTransferClient socket(): %s", strerror(errno));
return false;
}
struct sockaddr_un sun;
socklen_t addrlen;
if (path != NULL) {
if (!socketPath(path, &sun, &addrlen)) {
return false;
}
} else {
if (!socketPathForPid(pid, &sun, &addrlen)) {
return false;
}
}
if (connect(_peer, (const struct sockaddr *)&sun, addrlen) == -1) {
Log::warn("FdTransferClient connect(): %s", strerror(errno));
return false;
}
return true;
}
int FdTransferClient::requestPerfFd(int *tid, struct perf_event_attr *attr) {
struct perf_fd_request request;
request.header.type = PERF_FD;
request.tid = *tid;
memcpy(&request.attr, attr, sizeof(request.attr));
if (send(_peer, &request, sizeof(request), 0) != sizeof(request)) {
Log::warn("FdTransferClient send(): %s", strerror(errno));
return -1;
}
struct perf_fd_response resp;
int fd = recvFd(request.header.type, &resp.header, sizeof(resp));
if (fd == -1) {
// Update errno for our caller.
errno = resp.header.error;
} else {
// Update the TID of createForThread, in case the multiple threads' requests got mixed up and we're
// now handling the response destined to another. It's alright - the other thread(s) will finish the
// handling of our TID perf fd.
*tid = resp.tid;
}
return fd;
}
int FdTransferClient::requestKallsymsFd() {
struct fd_request request;
request.type = KALLSYMS_FD;
if (send(_peer, &request, sizeof(request), 0) != sizeof(request)) {
Log::warn("FdTransferClient send(): %s", strerror(errno));
return -1;
}
struct fd_response resp;
int fd = recvFd(request.type, &resp, sizeof(resp));
if (fd == -1) {
errno = resp.error;
}
return fd;
}
int FdTransferClient::recvFd(unsigned int type, struct fd_response *resp, size_t resp_size) {
struct msghdr msg = {0};
struct iovec iov[1];
iov[0].iov_base = resp;
iov[0].iov_len = resp_size;
msg.msg_iov = iov;
msg.msg_iovlen = ARRAY_SIZE(iov);
int newfd;
union {
char buf[CMSG_SPACE(sizeof(newfd))];
struct cmsghdr align;
} u;
msg.msg_control = u.buf;
msg.msg_controllen = sizeof(u.buf);
ssize_t ret = recvmsg(_peer, &msg, 0);
if (ret < 0) {
Log::warn("FdTransferClient recvmsg(): %s", strerror(errno));
return -1;
}
if (resp->type != type) {
Log::warn("FdTransferClient recvmsg(): bad response type");
return -1;
}
if (resp->error == 0) {
struct cmsghdr *cmptr = CMSG_FIRSTHDR(&msg);
if (cmptr != NULL && cmptr->cmsg_len == CMSG_LEN(sizeof(newfd))
&& cmptr->cmsg_level == SOL_SOCKET && cmptr->cmsg_type == SCM_RIGHTS) {
newfd = *((int*)CMSG_DATA(cmptr));
} else {
Log::warn("FdTransferClient recvmsg(): unexpected response with no SCM_RIGHTS: %s", strerror(errno));
newfd = -1;
}
} else {
newfd = -1;
}
return newfd;
}
#endif // __linux__

View File

@@ -18,12 +18,8 @@
#include <vector>
#include <stdio.h>
#include "flameGraph.h"
#include "vmEntry.h"
// Browsers refuse to draw on canvas larger than 32767 px
const int MAX_CANVAS_HEIGHT = 32767;
static const char FLAMEGRAPH_HEADER[] =
"<!DOCTYPE html>\n"
"<html lang='en'>\n"
@@ -76,12 +72,11 @@ static const char FLAMEGRAPH_HEADER[] =
"\tc.font = document.body.style.font;\n"
"\n"
"\tconst palette = [\n"
"\t\t[0xa6e1a6, 20, 20, 20],\n"
"\t\t[0x50e150, 30, 30, 30],\n"
"\t\t[0x50cccc, 30, 30, 30],\n"
"\t\t[0xe15a5a, 30, 40, 40],\n"
"\t\t[0xc8c83c, 30, 30, 10],\n"
"\t\t[0x50bebe, 30, 30, 30],\n"
"\t\t[0xe17d00, 30, 30, 0],\n"
"\t\t[0xc8c83c, 30, 30, 10],\n"
"\t\t[0xe15a5a, 30, 40, 40],\n"
"\t];\n"
"\n"
"\tfunction getColor(p) {\n"
@@ -156,12 +151,12 @@ static const char FLAMEGRAPH_HEADER[] =
"\t\tfunction totalMarked() {\n"
"\t\t\tlet total = 0;\n"
"\t\t\tlet left = 0;\n"
"\t\t\tObject.keys(marked).sort(function(a, b) { return a - b; }).forEach(function(x) {\n"
"\t\t\tfor (let x in marked) {\n"
"\t\t\t\tif (+x >= left) {\n"
"\t\t\t\t\ttotal += marked[x];\n"
"\t\t\t\t\tleft = +x + marked[x];\n"
"\t\t\t\t}\n"
"\t\t\t});\n"
"\t\t\t}\n"
"\t\t\treturn total;\n"
"\t\t}\n"
"\n"
@@ -418,9 +413,9 @@ class StringUtils {
return len >= suffixlen && s.compare(len - suffixlen, suffixlen, suffix) == 0;
}
static void replace(std::string& s, char c, const char* replacement, size_t rlen) {
for (size_t i = 0; (i = s.find(c, i)) != std::string::npos; i += rlen) {
s.replace(i, 1, replacement, rlen);
static void replace(std::string& s, char c, const char* replacement) {
for (size_t i = 0; (i = s.find(c, i)) != std::string::npos; i++) {
s.replace(i, 1, replacement);
}
}
};
@@ -484,8 +479,7 @@ void FlameGraph::dump(std::ostream& out, bool tree) {
out << TREE_FOOTER;
} else {
char buf[sizeof(FLAMEGRAPH_HEADER) + 256];
snprintf(buf, sizeof(buf) - 1, FLAMEGRAPH_HEADER, _title,
std::min(depth * 16, MAX_CANVAS_HEIGHT), _reverse ? "true" : "false", depth);
snprintf(buf, sizeof(buf) - 1, FLAMEGRAPH_HEADER, _title, depth * 16, _reverse ? "true" : "false", depth);
out << buf;
printFrame(out, "all", _root, 0, 0);
@@ -497,7 +491,7 @@ void FlameGraph::dump(std::ostream& out, bool tree) {
void FlameGraph::printFrame(std::ostream& out, const std::string& name, const Trie& f, int level, u64 x) {
std::string name_copy = name;
int type = frameType(name_copy);
StringUtils::replace(name_copy, '\'', "\\'", 2);
StringUtils::replace(name_copy, '\'', "\\'");
snprintf(_buf, sizeof(_buf) - 1, "f(%d,%llu,%llu,%d,'%s')\n", level, x, f._total, type, name_copy.c_str());
out << _buf;
@@ -524,9 +518,9 @@ void FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int level) {
const Trie* trie = subnodes[i]._trie;
int type = frameType(name);
StringUtils::replace(name, '&', "&amp;", 5);
StringUtils::replace(name, '<', "&lt;", 4);
StringUtils::replace(name, '>', "&gt;", 4);
StringUtils::replace(name, '&', "&amp;");
StringUtils::replace(name, '<', "&lt;");
StringUtils::replace(name, '>', "&gt;");
if (_reverse) {
snprintf(_buf, sizeof(_buf) - 1,
@@ -556,23 +550,27 @@ void FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int level) {
}
}
// TODO: Reuse frame type embedded in ASGCT_CallFrame
int FlameGraph::frameType(std::string& name) {
if (StringUtils::endsWith(name, "_[j]", 4)) {
// Java compiled frame
name = name.substr(0, name.length() - 4);
return FRAME_JIT_COMPILED;
return 0;
} else if (StringUtils::endsWith(name, "_[i]", 4)) {
// Java inlined frame
name = name.substr(0, name.length() - 4);
return FRAME_INLINED;
return 1;
} else if (StringUtils::endsWith(name, "_[k]", 4)) {
// Kernel function
name = name.substr(0, name.length() - 4);
return FRAME_KERNEL;
return 2;
} else if (name.find("::") != std::string::npos || name.compare(0, 2, "-[") == 0 || name.compare(0, 2, "+[") == 0) {
return FRAME_CPP;
} else if (((int)name.find('/') > 0 && name[0] != '[')
|| ((int)name.find('.') > 0 && name[0] >= 'A' && name[0] <= 'Z')) {
return FRAME_INTERPRETED;
// C++ function or Objective C method
return 3;
} else if ((int)name.find('/') > 0 || ((int)name.find('.') > 0 && name[0] >= 'A' && name[0] <= 'Z')) {
// Java regular method
return 0;
} else {
return FRAME_NATIVE;
// Other native code
return 4;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -20,7 +20,6 @@
#include "arch.h"
#include "arguments.h"
#include "event.h"
#include "log.h"
class Recording;
@@ -28,26 +27,19 @@ class FlightRecorder {
private:
Recording* _rec;
Error startMasterRecording(Arguments& args);
void stopMasterRecording();
public:
FlightRecorder() : _rec(NULL) {
}
Error start(Arguments& args, bool reset);
void stop();
void flush();
bool timerTick(u64 wall_time);
bool active() const {
bool active() {
return _rec != NULL;
}
void recordEvent(int lock_index, int tid, u32 call_trace_id,
int event_type, Event* event, u64 counter);
void recordLog(LogLevel level, const char* message, size_t len);
};
#endif // _FLIGHTRECORDER_H

View File

@@ -23,11 +23,6 @@
#include "vmStructs.h"
static inline bool isDigit(char c) {
return c >= '0' && c <= '9';
}
Matcher::Matcher(const char* pattern) {
if (pattern[0] == '*') {
_type = MATCH_ENDS_WITH;
@@ -96,7 +91,7 @@ FrameName::FrameName(Arguments& args, int style, Mutex& thread_names_lock, Threa
buildFilter(_include, args._buf, args._include);
buildFilter(_exclude, args._buf, args._exclude);
Profiler::instance()->classMap()->collect(_class_names);
Profiler::_instance.classMap()->collect(_class_names);
}
FrameName::~FrameName() {
@@ -117,40 +112,17 @@ char* FrameName::truncate(char* name, int max_length) {
return name;
}
const char* FrameName::decodeNativeSymbol(const char* name) {
const char* lib_name = (_style & STYLE_LIB_NAMES) ? Profiler::instance()->getLibraryName(name) : NULL;
if (name[0] == '_' && name[1] == 'Z') {
const char* FrameName::cppDemangle(const char* name) {
if (name != NULL && name[0] == '_' && name[1] == 'Z') {
int status;
char* demangled = abi::__cxa_demangle(name, NULL, NULL, &status);
if (demangled != NULL) {
if (lib_name != NULL) {
snprintf(_buf, sizeof(_buf) - 1, "%s`%s", lib_name, demangled);
} else {
strncpy(_buf, demangled, sizeof(_buf) - 1);
}
strncpy(_buf, demangled, sizeof(_buf) - 1);
free(demangled);
return _buf;
}
}
if (lib_name != NULL) {
snprintf(_buf, sizeof(_buf) - 1, "%s`%s", lib_name, name);
return _buf;
} else {
return name;
}
}
const char* FrameName::typeSuffix(int bci) {
switch (bci >> 24) {
case FRAME_JIT_COMPILED:
return "_[j]";
case FRAME_INLINED:
return "_[i]";
default:
return _style & STYLE_ANNOTATE ? "_[j]" : "";
}
return name;
}
char* FrameName::javaMethodName(jmethodID method) {
@@ -171,6 +143,7 @@ char* FrameName::javaMethodName(jmethodID method) {
strcat(result, ".");
strcat(result, method_name);
if (_style & STYLE_SIGNATURES) strcat(result, truncate(method_sig, 255));
if (_style & STYLE_ANNOTATE) strcat(result, "_[j]");
} else {
snprintf(_buf, sizeof(_buf) - 1, "[jvmtiError %d]", err);
result = _buf;
@@ -218,13 +191,13 @@ char* FrameName::javaClassName(const char* symbol, int length, int style) {
if (style & STYLE_SIMPLE) {
for (char* s = result; *s; s++) {
if (*s == '/' && !isDigit(s[1])) result = s + 1;
if (*s == '/') result = s + 1;
}
}
if (style & STYLE_DOTTED) {
for (char* s = result; *s; s++) {
if (*s == '/' && !isDigit(s[1])) *s = '.';
if (*s == '/') *s = '.';
}
}
@@ -238,7 +211,7 @@ const char* FrameName::name(ASGCT_CallFrame& frame, bool for_matching) {
switch (frame.bci) {
case BCI_NATIVE_FRAME:
return decodeNativeSymbol((const char*)frame.method_id);
return cppDemangle((const char*)frame.method_id);
case BCI_ALLOC:
case BCI_ALLOC_OUTSIDE_TLAB:
@@ -272,20 +245,14 @@ const char* FrameName::name(ASGCT_CallFrame& frame, bool for_matching) {
}
default: {
const char* type_suffix = typeSuffix(frame.bci);
JMethodCache::iterator it = _cache.lower_bound(frame.method_id);
if (it != _cache.end() && it->first == frame.method_id) {
if (type_suffix[0]) {
snprintf(_buf, sizeof(_buf) - 1, "%s%s", it->second.c_str(), type_suffix);
return _buf;
}
return it->second.c_str();
}
char* newName = javaMethodName(frame.method_id);
const char* newName = javaMethodName(frame.method_id);
_cache.insert(it, JMethodCache::value_type(frame.method_id, newName));
return type_suffix[0] ? strcat(newName, type_suffix) : newName;
return newName;
}
}
}

View File

@@ -75,8 +75,7 @@ class FrameName {
void buildFilter(std::vector<Matcher>& vector, const char* base, int offset);
char* truncate(char* name, int max_length);
const char* decodeNativeSymbol(const char* name);
const char* typeSuffix(int bci);
const char* cppDemangle(const char* name);
char* javaMethodName(jmethodID method);
char* javaClassName(const char* symbol, int length, int style);

View File

@@ -1 +0,0 @@
202,254,186,190,0,0,0,50,0,11,10,0,3,0,8,7,0,9,7,0,10,1,0,6,60,105,110,105,116,62,1,0,3,40,41,86,1,0,4,67,111,100,101,1,0,12,114,101,99,111,114,100,83,97,109,112,108,101,12,0,4,0,5,1,0,23,111,110,101,47,112,114,111,102,105,108,101,114,47,73,110,115,116,114,117,109,101,110,116,1,0,16,106,97,118,97,47,108,97,110,103,47,79,98,106,101,99,116,0,33,0,2,0,3,0,0,0,0,0,2,0,2,0,4,0,5,0,1,0,6,0,0,0,17,0,1,0,1,0,0,0,5,42,183,0,1,177,0,0,0,0,1,9,0,7,0,5,0,0,0,0,

View File

@@ -1,28 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package one.profiler;
/**
* Instrumentation helper for Java method profiling.
*/
public class Instrument {
private Instrument() {
}
public static native void recordSample();
}

File diff suppressed because one or more lines are too long

View File

@@ -1,101 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package one.profiler;
import jdk.jfr.Configuration;
import jdk.jfr.FlightRecorder;
import jdk.jfr.FlightRecorderListener;
import jdk.jfr.Recording;
import jdk.jfr.RecordingState;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Paths;
import java.text.ParseException;
/**
* Synchronize async-profiler recording with an existing JFR recording.
*/
class JfrSync implements FlightRecorderListener {
private static volatile Recording masterRecording;
private JfrSync() {
}
static {
FlightRecorder.addListener(new JfrSync());
}
@Override
public void recordingStateChanged(Recording recording) {
if (recording == masterRecording && recording.getState() == RecordingState.STOPPED) {
masterRecording = null;
stopProfiler();
}
}
public static void start(String fileName, String settings, int eventMask) throws IOException, ParseException {
Configuration config;
try {
config = Configuration.getConfiguration(settings);
} catch (NoSuchFileException e) {
config = Configuration.create(Paths.get(settings));
}
Recording recording = new Recording(config);
masterRecording = recording;
disableBuiltinEvents(recording, eventMask);
recording.setDestination(Paths.get(fileName));
recording.setToDisk(true);
recording.setDumpOnExit(true);
recording.start();
}
public static void stop() {
Recording recording = masterRecording;
if (recording != null) {
// Disable state change notification before stopping
masterRecording = null;
recording.stop();
}
}
private static void disableBuiltinEvents(Recording recording, int eventMask) {
if ((eventMask & 1) != 0) {
recording.disable("jdk.ExecutionSample");
recording.disable("jdk.NativeMethodSample");
}
if ((eventMask & 2) != 0) {
recording.disable("jdk.ObjectAllocationInNewTLAB");
recording.disable("jdk.ObjectAllocationOutsideTLAB");
recording.disable("jdk.ObjectAllocationSample");
}
if ((eventMask & 4) != 0) {
recording.disable("jdk.JavaMonitorEnter");
recording.disable("jdk.ThreadPark");
}
}
private static native void stopProfiler();
// JNI helper
static Integer box(int n) {
return n;
}
}

View File

@@ -24,9 +24,28 @@
#include "instrument.h"
static const unsigned char INSTRUMENT_CLASS[] = {
#include "helper/one/profiler/Instrument.class.h"
};
// A class with a single native recordSample() method
static const char INSTRUMENT_CLASS[] =
"\xCA\xFE\xBA\xBE" // magic
"\x00\x00\x00\x32" // version: 50
"\x00\x07" // constant_pool_count: 7
"\x07\x00\x02" // #1 = CONSTANT_Class: #2
"\x01\x00\x17one/profiler/Instrument" // #2 = CONSTANT_Utf8: "one/profiler/Instrument"
"\x07\x00\x04" // #3 = CONSTANT_Class: #4
"\x01\x00\x10java/lang/Object" // #4 = CONSTANT_Utf8: "java/lang/Object"
"\x01\x00\x0CrecordSample" // #5 = CONSTANT_Utf8: "recordSample"
"\x01\x00\x03()V" // #6 = CONSTANT_Utf8: "()V"
"\x00\x21" // access_flags: public super
"\x00\x01" // this_class: #1
"\x00\x03" // super_class: #3
"\x00\x00" // interfaces_count: 0
"\x00\x00" // fields_count: 0
"\x00\x01" // methods_count: 1
"\x01\x09" // access_flags: public static native
"\x00\x05" // name_index: #5
"\x00\x06" // descriptor_index: #6
"\x00\x00" // attributes_count: 0
"\x00"; // attributes_count: 0
enum ConstantTag {
@@ -236,7 +255,6 @@ class BytecodeRewriter {
void rewriteCode();
void rewriteBytecodeTable(int data_len);
void rewriteStackMapTable();
void rewriteVerificationTypeInfo();
void rewriteAttributes(Scope scope);
void rewriteMembers(Scope scope);
bool rewriteClass();
@@ -297,7 +315,7 @@ void BytecodeRewriter::rewriteCode() {
put32(code_length + EXTRA_BYTECODES);
// invokestatic "one/profiler/Instrument.recordSample()V"
// nop ensures that tableswitch/lookupswitch needs no realignment
// nop after invoke helps to prepend StackMapTable without rewriting
put8(0xb8);
put16(_cpool_len);
put8(0);
@@ -348,53 +366,7 @@ void BytecodeRewriter::rewriteStackMapTable() {
// Prepend same_frame
put8(EXTRA_BYTECODES - 1);
for (int i = 0; i < number_of_entries; i++) {
u8 frame_type = get8();
put8(frame_type);
if (frame_type <= 63) {
// same_frame
} else if (frame_type <= 127) {
// same_locals_1_stack_item_frame
rewriteVerificationTypeInfo();
} else if (frame_type == 247) {
// same_locals_1_stack_item_frame_extended
put16(get16());
rewriteVerificationTypeInfo();
} else if (frame_type <= 251) {
// chop_frame or same_frame_extended
put16(get16());
} else if (frame_type <= 254) {
// append_frame
put16(get16());
for (int j = 0; j < frame_type - 251; j++) {
rewriteVerificationTypeInfo();
}
} else {
// full_frame
put16(get16());
u16 number_of_locals = get16();
put16(number_of_locals);
for (int j = 0; j < number_of_locals; j++) {
rewriteVerificationTypeInfo();
}
u16 number_of_stack_items = get16();
put16(number_of_stack_items);
for (int j = 0; j < number_of_stack_items; j++) {
rewriteVerificationTypeInfo();
}
}
}
}
void BytecodeRewriter::rewriteVerificationTypeInfo() {
u8 tag = get8();
put8(tag);
if (tag >= 7) {
// Adjust ITEM_Uninitialized offset
put16(tag == 8 ? EXTRA_BYTECODES + get16() : get16());
}
put(get(attribute_length - 2), attribute_length - 2);
}
void BytecodeRewriter::rewriteAttributes(Scope scope) {
@@ -517,7 +489,7 @@ Error Instrument::check(Arguments& args) {
jclass cls = jni->DefineClass(NULL, NULL, (const jbyte*)INSTRUMENT_CLASS, sizeof(INSTRUMENT_CLASS));
if (cls == NULL || jni->RegisterNatives(cls, &native_method, 1) != 0) {
jni->ExceptionDescribe();
jni->ExceptionClear();
return Error("Could not load Instrument class");
}
@@ -537,7 +509,7 @@ Error Instrument::start(Arguments& args) {
return Error("interval must be positive");
}
setupTargetClassAndMethod(args._event);
setupTargetClassAndMethod(args._event_desc);
_interval = args._interval ? args._interval : 1;
_calls = 0;
_running = true;
@@ -616,6 +588,6 @@ void JNICALL Instrument::recordSample(JNIEnv* jni, jobject unused) {
if (_interval <= 1 || ((atomicInc(_calls) + 1) % _interval) == 0) {
ExecutionEvent event;
Profiler::instance()->recordSample(NULL, _interval, BCI_INSTRUMENT, &event);
Profiler::_instance.recordSample(NULL, _interval, BCI_INSTRUMENT, &event);
}
}

View File

@@ -30,14 +30,18 @@ class Instrument : public Engine {
static volatile bool _running;
public:
const char* title() {
return "Java method profile";
const char* name() {
return "instrument";
}
const char* units() {
return "calls";
}
CStack cstack() {
return CSTACK_NO;
}
Error check(Arguments& args);
Error start(Arguments& args);
void stop();

View File

@@ -16,31 +16,18 @@
#include <sys/time.h>
#include "itimer.h"
#include "j9StackTraces.h"
#include "os.h"
#include "profiler.h"
#include "stackWalker.h"
long ITimer::_interval;
CStack ITimer::_cstack;
void ITimer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
if (!_enabled) return;
ExecutionEvent event;
Profiler::instance()->recordSample(ucontext, _interval, 0, &event);
}
void ITimer::signalHandlerJ9(int signo, siginfo_t* siginfo, void* ucontext) {
if (!_enabled) return;
J9StackTraceNotification notif;
notif.num_frames = _cstack == CSTACK_NO ? 0 : _cstack == CSTACK_DWARF
? StackWalker::walkDwarf(ucontext, notif.addr, MAX_J9_NATIVE_FRAMES)
: StackWalker::walkFP(ucontext, notif.addr, MAX_J9_NATIVE_FRAMES);
J9StackTraces::checkpoint(_interval, &notif);
Profiler::_instance.recordSample(ucontext, _interval, 0, &event);
}
Error ITimer::check(Arguments& args) {
@@ -62,21 +49,11 @@ Error ITimer::start(Arguments& args) {
return Error("interval must be positive");
}
_interval = args._interval ? args._interval : DEFAULT_INTERVAL;
_cstack = args._cstack;
if (VM::isOpenJ9()) {
if (_cstack == CSTACK_DEFAULT) _cstack = CSTACK_DWARF;
OS::installSignalHandler(SIGPROF, signalHandlerJ9);
Error error = J9StackTraces::start(args);
if (error) {
return error;
}
} else {
OS::installSignalHandler(SIGPROF, signalHandler);
}
OS::installSignalHandler(SIGPROF, signalHandler);
time_t sec = _interval / 1000000000;
suseconds_t usec = (_interval % 1000000000) / 1000;
long sec = _interval / 1000000000;
long usec = (_interval % 1000000000) / 1000;
struct itimerval tv = {{sec, usec}, {sec, usec}};
if (setitimer(ITIMER_PROF, &tv, NULL) != 0) {
@@ -89,6 +66,4 @@ Error ITimer::start(Arguments& args) {
void ITimer::stop() {
struct itimerval tv = {{0, 0}, {0, 0}};
setitimer(ITIMER_PROF, &tv, NULL);
J9StackTraces::stop();
}

View File

@@ -24,14 +24,12 @@
class ITimer : public Engine {
private:
static long _interval;
static CStack _cstack;
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
static void signalHandlerJ9(int signo, siginfo_t* siginfo, void* ucontext);
public:
const char* title() {
return "CPU profile";
const char* name() {
return "itimer";
}
const char* units() {

View File

@@ -1,71 +0,0 @@
/*
* Copyright 2022 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "j9Ext.h"
#include "objectSampler.h"
#include <string.h>
jvmtiEnv* J9Ext::_jvmti;
void* (*J9Ext::_j9thread_self)() = NULL;
jvmtiExtensionFunction J9Ext::_GetOSThreadID = NULL;
jvmtiExtensionFunction J9Ext::_GetJ9vmThread = NULL;
jvmtiExtensionFunction J9Ext::_GetStackTraceExtended = NULL;
jvmtiExtensionFunction J9Ext::_GetAllStackTracesExtended = NULL;
int J9Ext::InstrumentableObjectAlloc_id = -1;
// Look for OpenJ9-specific JVM TI extension
bool J9Ext::initialize(jvmtiEnv* jvmti, const void* j9thread_self) {
_jvmti = jvmti;
_j9thread_self = (void* (*)())j9thread_self;
jint ext_count;
jvmtiExtensionFunctionInfo* ext_functions;
if (jvmti->GetExtensionFunctions(&ext_count, &ext_functions) == 0) {
for (int i = 0; i < ext_count; i++) {
if (strcmp(ext_functions[i].id, "com.ibm.GetOSThreadID") == 0) {
_GetOSThreadID = ext_functions[i].func;
} else if (strcmp(ext_functions[i].id, "com.ibm.GetJ9vmThread") == 0) {
_GetJ9vmThread = ext_functions[i].func;
} else if (strcmp(ext_functions[i].id, "com.ibm.GetStackTraceExtended") == 0) {
_GetStackTraceExtended = ext_functions[i].func;
} else if (strcmp(ext_functions[i].id, "com.ibm.GetAllStackTracesExtended") == 0) {
_GetAllStackTracesExtended = ext_functions[i].func;
}
}
jvmti->Deallocate((unsigned char*)ext_functions);
}
jvmtiExtensionEventInfo* ext_events;
if (jvmti->GetExtensionEvents(&ext_count, &ext_events) == 0) {
for (int i = 0; i < ext_count; i++) {
if (strcmp(ext_events[i].id, "com.ibm.InstrumentableObjectAlloc") == 0) {
InstrumentableObjectAlloc_id = ext_events[i].extension_event_index;
// If we don't set a callback now, we won't be able to enable it later in runtime
jvmti->SetExtensionEventCallback(InstrumentableObjectAlloc_id, (jvmtiExtensionEvent)ObjectSampler::JavaObjectAlloc);
jvmti->SetExtensionEventCallback(InstrumentableObjectAlloc_id, NULL);
break;
}
}
jvmti->Deallocate((unsigned char*)ext_events);
}
return _GetOSThreadID != NULL && _GetStackTraceExtended != NULL && _GetAllStackTracesExtended != NULL;
}

View File

@@ -1,91 +0,0 @@
/*
* Copyright 2022 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _J9EXT_H
#define _J9EXT_H
#include <jvmti.h>
#define JVMTI_EXT(f, ...) ((jvmtiError (*)(jvmtiEnv*, __VA_ARGS__))f)
struct jvmtiFrameInfoExtended {
jmethodID method;
jlocation location;
jlocation machinepc;
jint type;
void* native_frame_address;
};
struct jvmtiStackInfoExtended {
jthread thread;
jint state;
jvmtiFrameInfoExtended* frame_buffer;
jint frame_count;
};
enum {
SHOW_COMPILED_FRAMES = 4,
SHOW_INLINED_FRAMES = 8
};
class J9Ext {
private:
static jvmtiEnv* _jvmti;
static void* (*_j9thread_self)();
static jvmtiExtensionFunction _GetOSThreadID;
static jvmtiExtensionFunction _GetJ9vmThread;
static jvmtiExtensionFunction _GetStackTraceExtended;
static jvmtiExtensionFunction _GetAllStackTracesExtended;
public:
static bool initialize(jvmtiEnv* jvmti, const void* j9thread_self);
static int GetOSThreadID(jthread thread) {
jlong thread_id;
return JVMTI_EXT(_GetOSThreadID, jthread, jlong*)(_jvmti, thread, &thread_id) == 0 ? (int)thread_id : -1;
}
static JNIEnv* GetJ9vmThread(jthread thread) {
JNIEnv* result;
return JVMTI_EXT(_GetJ9vmThread, jthread, JNIEnv**)(_jvmti, thread, &result) == 0 ? result : NULL;
}
static jvmtiError GetStackTraceExtended(jthread thread, jint start_depth, jint max_frame_count,
void* frame_buffer, jint* count_ptr) {
return JVMTI_EXT(_GetStackTraceExtended, jint, jthread, jint, jint, void*, jint*)(
_jvmti, SHOW_COMPILED_FRAMES | SHOW_INLINED_FRAMES,
thread, start_depth, max_frame_count, frame_buffer, count_ptr);
}
static jvmtiError GetAllStackTracesExtended(jint max_frame_count, void** stack_info_ptr, jint* thread_count_ptr) {
return JVMTI_EXT(_GetAllStackTracesExtended, jint, jint, void**, jint*)(
_jvmti, SHOW_COMPILED_FRAMES | SHOW_INLINED_FRAMES,
max_frame_count, stack_info_ptr, thread_count_ptr);
}
static void* j9thread_self() {
return _j9thread_self != NULL ? _j9thread_self() : NULL;
}
static int InstrumentableObjectAlloc_id;
};
#endif // _J9EXT_H

View File

@@ -1,185 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdlib.h>
#include <sys/time.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <map>
#include "j9StackTraces.h"
#include "j9Ext.h"
#include "profiler.h"
#include "perfEvents.h"
enum {
J9_STOPPED = 0x40,
J9_HALT_THREAD_INSPECTION = 0x8000
};
class J9VMThread {
private:
uintptr_t _unused1[10];
uintptr_t _overflow_mark;
uintptr_t _unused2[8];
uintptr_t _flags;
public:
uintptr_t getAndSetFlag(uintptr_t flag) {
return __sync_fetch_and_or(&_flags, flag);
}
void clearFlag(uintptr_t flag) {
__sync_fetch_and_and(&_flags, ~flag);
}
void setOverflowMark() {
__atomic_store_n(&_overflow_mark, (uintptr_t)-1, __ATOMIC_RELEASE);
}
};
pthread_t J9StackTraces::_thread = 0;
int J9StackTraces::_max_stack_depth;
int J9StackTraces::_pipe[2];
static JNIEnv* _self_env = NULL;
Error J9StackTraces::start(Arguments& args) {
_max_stack_depth = args._jstackdepth;
if (pipe(_pipe) != 0) {
return Error("Failed to create pipe");
}
fcntl(_pipe[1], F_SETFL, O_NONBLOCK);
if (pthread_create(&_thread, NULL, threadEntry, NULL) != 0) {
close(_pipe[0]);
close(_pipe[1]);
return Error("Unable to create sampler thread");
}
return Error::OK;
}
void J9StackTraces::stop() {
if (_thread != 0) {
close(_pipe[1]);
pthread_join(_thread, NULL);
close(_pipe[0]);
_thread = 0;
}
}
void J9StackTraces::timerLoop() {
JNIEnv* jni = VM::attachThread("Async-profiler Sampler");
__atomic_store_n(&_self_env, jni, __ATOMIC_RELEASE);
jni->PushLocalFrame(64);
jvmtiEnv* jvmti = VM::jvmti();
char notification_buf[65536];
std::map<void*, jthread> known_threads;
int max_frames = _max_stack_depth + MAX_J9_NATIVE_FRAMES + RESERVED_FRAMES;
ASGCT_CallFrame* frames = (ASGCT_CallFrame*)malloc(max_frames * sizeof(ASGCT_CallFrame));
jvmtiFrameInfoExtended* jvmti_frames = (jvmtiFrameInfoExtended*)malloc(max_frames * sizeof(jvmtiFrameInfoExtended));
while (true) {
ssize_t bytes = read(_pipe[0], notification_buf, sizeof(notification_buf));
if (bytes <= 0) {
if (bytes < 0 && errno == EAGAIN) {
continue;
}
break;
}
ssize_t ptr = 0;
while (ptr < bytes) {
J9StackTraceNotification* notif = (J9StackTraceNotification*)(notification_buf + ptr);
jthread thread = known_threads[notif->env];
jint num_jvmti_frames;
if (thread == NULL || J9Ext::GetStackTraceExtended(thread, 0, _max_stack_depth, jvmti_frames, &num_jvmti_frames) != 0) {
jni->PopLocalFrame(NULL);
jni->PushLocalFrame(64);
jint thread_count;
jthread* threads;
if (jvmti->GetAllThreads(&thread_count, &threads) == 0) {
known_threads.clear();
for (jint i = 0; i < thread_count; i++) {
known_threads[J9Ext::GetJ9vmThread(threads[i])] = threads[i];
}
jvmti->Deallocate((unsigned char*)threads);
}
if ((thread = known_threads[notif->env]) == NULL ||
J9Ext::GetStackTraceExtended(thread, 0, _max_stack_depth, jvmti_frames, &num_jvmti_frames) != 0) {
continue;
}
}
int num_frames = Profiler::instance()->convertNativeTrace(notif->num_frames, notif->addr, frames);
for (int j = 0; j < num_jvmti_frames; j++) {
frames[num_frames].method_id = jvmti_frames[j].method;
frames[num_frames].bci = (jvmti_frames[j].type << 24) | jvmti_frames[j].location;
num_frames++;
}
int tid = J9Ext::GetOSThreadID(thread);
Profiler::instance()->recordExternalSample(notif->counter, tid, num_frames, frames);
ptr += notif->size();
}
}
free(jvmti_frames);
free(frames);
__atomic_store_n(&_self_env, NULL, __ATOMIC_RELEASE);
VM::detachThread();
}
void J9StackTraces::checkpoint(u64 counter, J9StackTraceNotification* notif) {
JNIEnv* self_env = __atomic_load_n(&_self_env, __ATOMIC_ACQUIRE);
if (self_env == NULL) {
// Sampler thread is not ready
return;
}
JNIEnv* env = VM::jni();
if (env != NULL && env != self_env) {
J9VMThread* vm_thread = (J9VMThread*)env;
uintptr_t flags = vm_thread->getAndSetFlag(J9_HALT_THREAD_INSPECTION);
if (flags & J9_HALT_THREAD_INSPECTION) {
// Thread is already scheduled for inspection, no need to notify again
return;
} else if (!(flags & J9_STOPPED)) {
vm_thread->setOverflowMark();
notif->env = env;
notif->counter = counter;
if (write(_pipe[1], notif, notif->size()) > 0) {
return;
}
}
// Something went wrong - rollback
vm_thread->clearFlag(J9_HALT_THREAD_INSPECTION);
}
}

View File

@@ -1,60 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _J9STACKTRACES_H
#define _J9STACKTRACES_H
#include <pthread.h>
#include "arch.h"
#include "arguments.h"
const int MAX_J9_NATIVE_FRAMES = 128;
struct J9StackTraceNotification {
void* env;
u64 counter;
int num_frames;
int reserved;
const void* addr[MAX_J9_NATIVE_FRAMES];
size_t size() {
return sizeof(*this) - sizeof(this->addr) + num_frames * sizeof(const void*);
}
};
class J9StackTraces {
private:
static pthread_t _thread;
static int _max_stack_depth;
static int _pipe[2];
static void* threadEntry(void* unused) {
timerLoop();
return NULL;
}
static void timerLoop();
public:
static Error start(Arguments& args);
static void stop();
static void checkpoint(u64 counter, J9StackTraceNotification* notif);
};
#endif // _J9STACKTRACES_H

View File

@@ -1,83 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdlib.h>
#include "j9WallClock.h"
#include "j9Ext.h"
#include "profiler.h"
long J9WallClock::_interval;
Error J9WallClock::start(Arguments& args) {
_interval = args._interval ? args._interval : DEFAULT_INTERVAL * 5;
_max_stack_depth = args._jstackdepth;
_running = true;
if (pthread_create(&_thread, NULL, threadEntry, this) != 0) {
return Error("Unable to create timer thread");
}
return Error::OK;
}
void J9WallClock::stop() {
_running = false;
pthread_kill(_thread, WAKEUP_SIGNAL);
pthread_join(_thread, NULL);
}
void J9WallClock::timerLoop() {
JNIEnv* jni = VM::attachThread("Async-profiler Sampler");
jvmtiEnv* jvmti = VM::jvmti();
int max_frames = _max_stack_depth + MAX_NATIVE_FRAMES + RESERVED_FRAMES;
ASGCT_CallFrame* frames = (ASGCT_CallFrame*)malloc(max_frames * sizeof(ASGCT_CallFrame));
while (_running) {
if (!_enabled) {
OS::sleep(_interval);
continue;
}
jni->PushLocalFrame(64);
jvmtiStackInfoExtended* stack_infos;
jint thread_count;
if (J9Ext::GetAllStackTracesExtended(_max_stack_depth, (void**)&stack_infos, &thread_count) == 0) {
for (int i = 0; i < thread_count; i++) {
jvmtiStackInfoExtended* si = &stack_infos[i];
for (int j = 0; j < si->frame_count; j++) {
jvmtiFrameInfoExtended* fi = &si->frame_buffer[j];
frames[j].method_id = fi->method;
frames[j].bci = (fi->type << 24) | fi->location;
}
int tid = J9Ext::GetOSThreadID(si->thread);
Profiler::instance()->recordExternalSample(_interval, tid, si->frame_count, frames);
}
jvmti->Deallocate((unsigned char*)stack_infos);
}
jni->PopLocalFrame(NULL);
OS::sleep(_interval);
}
free(frames);
VM::detachThread();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2021 Andrei Pangin
* Copyright 2016 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,35 +16,388 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/syscall.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include "psutil.h"
#define MAX_PATH 1024
#define TMP_PATH (MAX_PATH - 64)
static char tmp_path[TMP_PATH] = {0};
extern int is_openj9_process(int pid);
extern int jattach_openj9(int pid, int nspid, int argc, char** argv);
extern int jattach_hotspot(int pid, int nspid, int argc, char** argv);
#ifdef __linux__
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
// Parse /proc/pid/status to find process credentials
char path[64];
snprintf(path, sizeof(path), "/proc/%d/status", pid);
FILE* status_file = fopen(path, "r");
if (status_file == NULL) {
return 0;
}
char* line = NULL;
size_t size;
while (getline(&line, &size, status_file) != -1) {
if (strncmp(line, "Uid:", 4) == 0) {
// Get the effective UID, which is the second value in the line
*uid = (uid_t)atoi(strchr(line + 5, '\t'));
} else if (strncmp(line, "Gid:", 4) == 0) {
// Get the effective GID, which is the second value in the line
*gid = (gid_t)atoi(strchr(line + 5, '\t'));
} else if (strncmp(line, "NStgid:", 7) == 0) {
// PID namespaces can be nested; the last one is the innermost one
*nspid = atoi(strrchr(line, '\t'));
}
}
free(line);
fclose(status_file);
return 1;
}
int get_tmp_path(int pid) {
// A process may have its own root path (when running in chroot environment)
char path[64];
snprintf(path, sizeof(path), "/proc/%d/root", pid);
// Append /tmp to the resolved root symlink
ssize_t path_size = readlink(path, tmp_path, sizeof(tmp_path) - 10);
strcpy(tmp_path + (path_size > 1 ? path_size : 0), "/tmp");
return 1;
}
int enter_mount_ns(int pid) {
#ifdef __NR_setns
char path[128];
snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid);
struct stat oldns_stat, newns_stat;
if (stat("/proc/self/ns/mnt", &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
// Don't try to call setns() if we're in the same namespace already
if (oldns_stat.st_ino != newns_stat.st_ino) {
int newns = open(path, O_RDONLY);
if (newns < 0) {
return 0;
}
// Some ancient Linux distributions do not have setns() function
int result = syscall(__NR_setns, newns, 0);
close(newns);
return result < 0 ? 0 : 1;
}
}
#endif // __NR_setns
return 1;
}
// The first line of /proc/pid/sched looks like
// java (1234, #threads: 12)
// where 1234 is the required host PID
int sched_get_host_pid(const char* path) {
static char* line = NULL;
size_t size;
int result = -1;
FILE* sched_file = fopen(path, "r");
if (sched_file != NULL) {
if (getline(&line, &size, sched_file) != -1) {
char* c = strrchr(line, '(');
if (c != NULL) {
result = atoi(c + 1);
}
}
fclose(sched_file);
}
return result;
}
// Linux kernels < 4.1 do not export NStgid field in /proc/pid/status.
// Fortunately, /proc/pid/sched in a container exposes a host PID,
// so the idea is to scan all container PIDs to find which one matches the host PID.
int alt_lookup_nspid(int pid) {
int namespace_differs = 0;
char path[300];
snprintf(path, sizeof(path), "/proc/%d/ns/pid", pid);
// Don't bother looking for container PID if we are already in the same PID namespace
struct stat oldns_stat, newns_stat;
if (stat("/proc/self/ns/pid", &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
if (oldns_stat.st_ino == newns_stat.st_ino) {
return pid;
}
namespace_differs = 1;
}
// Otherwise browse all PIDs in the namespace of the target process
// trying to find which one corresponds to the host PID
snprintf(path, sizeof(path), "/proc/%d/root/proc", pid);
DIR* dir = opendir(path);
if (dir != NULL) {
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9') {
// Check if /proc/<container-pid>/sched points back to <host-pid>
snprintf(path, sizeof(path), "/proc/%d/root/proc/%s/sched", pid, entry->d_name);
if (sched_get_host_pid(path) == pid) {
closedir(dir);
return atoi(entry->d_name);
}
}
}
closedir(dir);
}
if (namespace_differs) {
printf("WARNING: couldn't find container pid of the target process\n");
}
return pid;
}
#elif defined(__APPLE__)
#include <sys/sysctl.h>
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
struct kinfo_proc info;
size_t len = sizeof(info);
if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
return 0;
}
*uid = info.kp_eproc.e_ucred.cr_uid;
*gid = info.kp_eproc.e_ucred.cr_gid;
*nspid = pid;
return 1;
}
// macOS has a secure per-user temporary directory
int get_tmp_path(int pid) {
int path_size = confstr(_CS_DARWIN_USER_TEMP_DIR, tmp_path, sizeof(tmp_path));
return path_size > 0 && path_size <= sizeof(tmp_path);
}
// This is a Linux-specific API; nothing to do on macOS and FreeBSD
int enter_mount_ns(int pid) {
return 1;
}
// Not used on macOS and FreeBSD
int alt_lookup_nspid(int pid) {
return pid;
}
#else // __FreeBSD__
#include <sys/sysctl.h>
#include <sys/user.h>
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
struct kinfo_proc info;
size_t len = sizeof(info);
if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
return 0;
}
*uid = info.ki_uid;
*gid = info.ki_groups[0];
*nspid = pid;
return 1;
}
// Use default /tmp path on FreeBSD
int get_tmp_path(int pid) {
return 0;
}
// This is a Linux-specific API; nothing to do on macOS and FreeBSD
int enter_mount_ns(int pid) {
return 1;
}
// Not used on macOS and FreeBSD
int alt_lookup_nspid(int pid) {
return pid;
}
#endif
__attribute__((visibility("default")))
int jattach(int pid, int argc, char** argv) {
// Check if remote JVM has already opened socket for Dynamic Attach
static int check_socket(int pid) {
char path[MAX_PATH];
snprintf(path, sizeof(path), "%s/.java_pid%d", tmp_path, pid);
struct stat stats;
return stat(path, &stats) == 0 && S_ISSOCK(stats.st_mode);
}
// Check if a file is owned by current user
static int check_file_owner(const char* path) {
struct stat stats;
if (stat(path, &stats) == 0 && stats.st_uid == geteuid()) {
return 1;
}
// Some mounted filesystems may change the ownership of the file.
// JVM will not trust such file, so it's better to remove it and try a different path
unlink(path);
return 0;
}
// Force remote JVM to start Attach listener.
// HotSpot will start Attach listener in response to SIGQUIT if it sees .attach_pid file
static int start_attach_mechanism(int pid, int nspid) {
char path[MAX_PATH];
snprintf(path, sizeof(path), "/proc/%d/cwd/.attach_pid%d", nspid, nspid);
int fd = creat(path, 0660);
if (fd == -1 || (close(fd) == 0 && !check_file_owner(path))) {
// Failed to create attach trigger in current directory. Retry in /tmp
snprintf(path, sizeof(path), "%s/.attach_pid%d", tmp_path, nspid);
fd = creat(path, 0660);
if (fd == -1) {
return 0;
}
close(fd);
}
// We have to still use the host namespace pid here for the kill() call
kill(pid, SIGQUIT);
// Start with 20 ms sleep and increment delay each iteration
struct timespec ts = {0, 20000000};
int result;
do {
nanosleep(&ts, NULL);
result = check_socket(nspid);
} while (!result && (ts.tv_nsec += 20000000) < 300000000);
unlink(path);
return result;
}
// Connect to UNIX domain socket created by JVM for Dynamic Attach
static int connect_socket(int pid) {
int fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
return -1;
}
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
int bytes = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/.java_pid%d", tmp_path, pid);
if (bytes >= sizeof(addr.sun_path)) {
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
}
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
close(fd);
return -1;
}
return fd;
}
// Send command with arguments to socket
static int write_command(int fd, int argc, char** argv) {
// Protocol version
if (write(fd, "1", 2) <= 0) {
return 0;
}
int i;
for (i = 0; i < 4; i++) {
const char* arg = i < argc ? argv[i] : "";
if (write(fd, arg, strlen(arg) + 1) <= 0) {
return 0;
}
}
return 1;
}
// Mirror response from remote JVM to stdout
static int read_response(int fd) {
char buf[8192];
ssize_t bytes = read(fd, buf, sizeof(buf) - 1);
if (bytes <= 0) {
perror("Error reading response");
return 1;
}
// First line of response is the command result code
buf[bytes] = 0;
int result = atoi(buf);
do {
fwrite(buf, 1, bytes, stdout);
bytes = read(fd, buf, sizeof(buf));
} while (bytes > 0);
return result;
}
int main(int argc, char** argv) {
if (argc < 3) {
printf("jattach " JATTACH_VERSION " built on " __DATE__ "\n"
"Copyright 2018 Andrei Pangin\n"
"\n"
"Usage: jattach <pid> <cmd> [args ...]\n");
return 1;
}
int pid = atoi(argv[1]);
if (pid == 0) {
perror("Invalid pid provided");
return 1;
}
uid_t my_uid = geteuid();
gid_t my_gid = getegid();
uid_t target_uid = my_uid;
gid_t target_gid = my_gid;
int nspid;
if (get_process_info(pid, &target_uid, &target_gid, &nspid) < 0) {
int nspid = -1;
if (!get_process_info(pid, &target_uid, &target_gid, &nspid)) {
fprintf(stderr, "Process %d not found\n", pid);
return 1;
}
// Container support: switch to the target namespaces.
// Network and IPC namespaces are essential for OpenJ9 connection.
enter_ns(pid, "net");
enter_ns(pid, "ipc");
int mnt_changed = enter_ns(pid, "mnt");
if (nspid < 0) {
nspid = alt_lookup_nspid(pid);
}
// In HotSpot, dynamic attach is allowed only for the clients with the same euid/egid.
// Get attach socket path of the target process (usually /tmp)
char* jattach_path = getenv("JATTACH_PATH");
if (jattach_path != NULL && strlen(jattach_path) < TMP_PATH) {
strcpy(tmp_path, jattach_path);
} else {
// Make sure our /tmp and target /tmp is the same
if (!get_tmp_path(pid)) {
strcpy(tmp_path, "/tmp");
}
if (!enter_mount_ns(pid)) {
printf("WARNING: couldn't enter target process mnt namespace\n");
}
}
// Dynamic attach is allowed only for the clients with the same euid/egid.
// If we are running under root, switch to the required euid/egid automatically.
if ((my_gid != target_gid && setegid(target_gid) != 0) ||
(my_uid != target_uid && seteuid(target_uid) != 0)) {
@@ -52,37 +405,33 @@ int jattach(int pid, int argc, char** argv) {
return 1;
}
get_tmp_path(mnt_changed > 0 ? nspid : pid);
// Make write() return EPIPE instead of abnormal process termination
// Make write() return EPIPE instead of silent process termination
signal(SIGPIPE, SIG_IGN);
if (is_openj9_process(nspid)) {
return jattach_openj9(pid, nspid, argc, argv);
} else {
return jattach_hotspot(pid, nspid, argc, argv);
}
}
int main(int argc, char** argv) {
if (argc < 3) {
printf("jattach " JATTACH_VERSION " built on " __DATE__ "\n"
"Copyright 2021 Andrei Pangin\n"
"\n"
"Usage: jattach <pid> <cmd> [args ...]\n"
"\n"
"Commands:\n"
" load threaddump dumpheap setflag properties\n"
" jcmd inspectheap datadump printflag agentProperties\n"
);
if (!check_socket(nspid) && !start_attach_mechanism(pid, nspid)) {
perror("Could not start attach mechanism");
return 1;
}
int pid = atoi(argv[1]);
if (pid <= 0) {
fprintf(stderr, "%s is not a valid process ID\n", argv[1]);
int fd = connect_socket(nspid);
if (fd == -1) {
perror("Could not connect to socket");
return 1;
}
printf("Connected to remote JVM\n");
if (!write_command(fd, argc - 2, argv + 2)) {
perror("Error writing to socket");
close(fd);
return 1;
}
return jattach(pid, argc - 2, argv + 2);
printf("Response code = ");
fflush(stdout);
int result = read_response(fd);
printf("\n");
close(fd);
return result;
}

View File

@@ -1,184 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include "psutil.h"
// Check if remote JVM has already opened socket for Dynamic Attach
static int check_socket(int pid) {
char path[MAX_PATH];
snprintf(path, sizeof(path), "%s/.java_pid%d", tmp_path, pid);
struct stat stats;
return stat(path, &stats) == 0 && S_ISSOCK(stats.st_mode) ? 0 : -1;
}
// Check if a file is owned by current user
static uid_t get_file_owner(const char* path) {
struct stat stats;
return stat(path, &stats) == 0 ? stats.st_uid : (uid_t)-1;
}
// Force remote JVM to start Attach listener.
// HotSpot will start Attach listener in response to SIGQUIT if it sees .attach_pid file
static int start_attach_mechanism(int pid, int nspid) {
char path[MAX_PATH];
snprintf(path, sizeof(path), "/proc/%d/cwd/.attach_pid%d", nspid, nspid);
int fd = creat(path, 0660);
if (fd == -1 || (close(fd) == 0 && get_file_owner(path) != geteuid())) {
// Some mounted filesystems may change the ownership of the file.
// JVM will not trust such file, so it's better to remove it and try a different path
unlink(path);
// Failed to create attach trigger in current directory. Retry in /tmp
snprintf(path, sizeof(path), "%s/.attach_pid%d", tmp_path, nspid);
fd = creat(path, 0660);
if (fd == -1) {
return -1;
}
close(fd);
}
// We have to still use the host namespace pid here for the kill() call
kill(pid, SIGQUIT);
// Start with 20 ms sleep and increment delay each iteration. Total timeout is 6000 ms
struct timespec ts = {0, 20000000};
int result;
do {
nanosleep(&ts, NULL);
result = check_socket(nspid);
} while (result != 0 && (ts.tv_nsec += 20000000) < 500000000);
unlink(path);
return result;
}
// Connect to UNIX domain socket created by JVM for Dynamic Attach
static int connect_socket(int pid) {
int fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
return -1;
}
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
int bytes = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/.java_pid%d", tmp_path, pid);
if (bytes >= sizeof(addr.sun_path)) {
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
}
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
close(fd);
return -1;
}
return fd;
}
// Send command with arguments to socket
static int write_command(int fd, int argc, char** argv) {
// Protocol version
if (write(fd, "1", 2) <= 0) {
return -1;
}
int i;
for (i = 0; i < 4; i++) {
const char* arg = i < argc ? argv[i] : "";
if (write(fd, arg, strlen(arg) + 1) <= 0) {
return -1;
}
}
return 0;
}
// Mirror response from remote JVM to stdout
static int read_response(int fd, int argc, char** argv) {
char buf[8192];
ssize_t bytes = read(fd, buf, sizeof(buf) - 1);
if (bytes == 0) {
fprintf(stderr, "Unexpected EOF reading response\n");
return 1;
} else if (bytes < 0) {
perror("Error reading response");
return 1;
}
// First line of response is the command result code
buf[bytes] = 0;
int result = atoi(buf);
// Special treatment of 'load' command
if (result == 0 && argc > 0 && strcmp(argv[0], "load") == 0) {
size_t total = bytes;
while (total < sizeof(buf) - 1 && (bytes = read(fd, buf + total, sizeof(buf) - 1 - total)) > 0) {
total += (size_t)bytes;
}
bytes = total;
// The second line is the result of 'load' command; since JDK 9 it starts from "return code: "
buf[bytes] = 0;
result = atoi(strncmp(buf + 2, "return code: ", 13) == 0 ? buf + 15 : buf + 2);
}
// Mirror JVM response to stdout
printf("JVM response code = ");
do {
fwrite(buf, 1, bytes, stdout);
bytes = read(fd, buf, sizeof(buf));
} while (bytes > 0);
printf("\n");
return result;
}
int jattach_hotspot(int pid, int nspid, int argc, char** argv) {
if (check_socket(nspid) != 0 && start_attach_mechanism(pid, nspid) != 0) {
perror("Could not start attach mechanism");
return 1;
}
int fd = connect_socket(nspid);
if (fd == -1) {
perror("Could not connect to socket");
return 1;
}
printf("Connected to remote JVM\n");
if (write_command(fd, argc, argv) != 0) {
perror("Error writing to socket");
close(fd);
return 1;
}
int result = read_response(fd, argc, argv);
close(fd);
return result;
}

View File

@@ -1,440 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/file.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include "psutil.h"
#define MAX_NOTIF_FILES 256
static int notif_lock[MAX_NOTIF_FILES];
// Translate HotSpot command to OpenJ9 equivalent
static void translate_command(char* buf, size_t bufsize, int argc, char** argv) {
const char* cmd = argv[0];
if (strcmp(cmd, "load") == 0 && argc >= 2) {
if (argc > 2 && strcmp(argv[2], "true") == 0) {
snprintf(buf, bufsize, "ATTACH_LOADAGENTPATH(%s,%s)", argv[1], argc > 3 ? argv[3] : "");
} else {
snprintf(buf, bufsize, "ATTACH_LOADAGENT(%s,%s)", argv[1], argc > 3 ? argv[3] : "");
}
} else if (strcmp(cmd, "jcmd") == 0) {
snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:%s,%s", argc > 1 ? argv[1] : "help", argc > 2 ? argv[2] : "");
} else if (strcmp(cmd, "threaddump") == 0) {
snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Thread.print,%s", argc > 1 ? argv[1] : "");
} else if (strcmp(cmd, "dumpheap") == 0) {
snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Dump.heap,%s", argc > 1 ? argv[1] : "");
} else if (strcmp(cmd, "inspectheap") == 0) {
snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:GC.class_histogram,%s", argc > 1 ? argv[1] : "");
} else if (strcmp(cmd, "datadump") == 0) {
snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Dump.java,%s", argc > 1 ? argv[1] : "");
} else if (strcmp(cmd, "properties") == 0) {
strcpy(buf, "ATTACH_GETSYSTEMPROPERTIES");
} else if (strcmp(cmd, "agentProperties") == 0) {
strcpy(buf, "ATTACH_GETAGENTPROPERTIES");
} else {
snprintf(buf, bufsize, "%s", cmd);
}
buf[bufsize - 1] = 0;
}
// Unescape a string and print it on stdout
static void print_unescaped(char* str) {
char* p = strchr(str, '\n');
if (p != NULL) {
*p = 0;
}
while ((p = strchr(str, '\\')) != NULL) {
switch (p[1]) {
case 0:
break;
case 'f':
*p = '\f';
break;
case 'n':
*p = '\n';
break;
case 'r':
*p = '\r';
break;
case 't':
*p = '\t';
break;
default:
*p = p[1];
}
fwrite(str, 1, p - str + 1, stdout);
str = p + 2;
}
fwrite(str, 1, strlen(str), stdout);
printf("\n");
}
// Send command with arguments to socket
static int write_command(int fd, const char* cmd) {
size_t len = strlen(cmd) + 1;
size_t off = 0;
while (off < len) {
ssize_t bytes = write(fd, cmd + off, len - off);
if (bytes <= 0) {
return -1;
}
off += bytes;
}
return 0;
}
// Mirror response from remote JVM to stdout
static int read_response(int fd, const char* cmd) {
size_t size = 8192;
char* buf = malloc(size);
size_t off = 0;
while (buf != NULL) {
ssize_t bytes = read(fd, buf + off, size - off);
if (bytes == 0) {
fprintf(stderr, "Unexpected EOF reading response\n");
return 1;
} else if (bytes < 0) {
perror("Error reading response");
return 1;
}
off += bytes;
if (buf[off - 1] == 0) {
break;
}
if (off >= size) {
buf = realloc(buf, size *= 2);
}
}
if (buf == NULL) {
fprintf(stderr, "Failed to allocate memory for response\n");
return 1;
}
int result = 0;
if (strncmp(cmd, "ATTACH_LOADAGENT", 16) == 0) {
if (strncmp(buf, "ATTACH_ACK", 10) != 0) {
// AgentOnLoad error code comes right after AgentInitializationException
result = strncmp(buf, "ATTACH_ERR AgentInitializationException", 39) == 0 ? atoi(buf + 39) : -1;
}
} else if (strncmp(cmd, "ATTACH_DIAGNOSTICS:", 19) == 0) {
char* p = strstr(buf, "openj9_diagnostics.string_result=");
if (p != NULL) {
// The result of a diagnostic command is encoded in Java Properties format
print_unescaped(p + 33);
free(buf);
return result;
}
}
buf[off - 1] = '\n';
fwrite(buf, 1, off, stdout);
free(buf);
return result;
}
static void detach(int fd) {
if (write_command(fd, "ATTACH_DETACHED") != 0) {
return;
}
char buf[256];
ssize_t bytes;
do {
bytes = read(fd, buf, sizeof(buf));
} while (bytes > 0 && buf[bytes - 1] != 0);
}
static void close_with_errno(int fd) {
int saved_errno = errno;
close(fd);
errno = saved_errno;
}
static int acquire_lock(const char* subdir, const char* filename) {
char path[MAX_PATH];
snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%s/%s", tmp_path, subdir, filename);
int lock_fd = open(path, O_WRONLY | O_CREAT, 0666);
if (lock_fd < 0) {
return -1;
}
if (flock(lock_fd, LOCK_EX) < 0) {
close_with_errno(lock_fd);
return -1;
}
return lock_fd;
}
static void release_lock(int lock_fd) {
flock(lock_fd, LOCK_UN);
close(lock_fd);
}
static int create_attach_socket(int* port) {
// Try IPv6 socket first, then fall back to IPv4
int s = socket(AF_INET6, SOCK_STREAM, 0);
if (s != -1) {
struct sockaddr_in6 addr = {AF_INET6, 0};
socklen_t addrlen = sizeof(addr);
if (bind(s, (struct sockaddr*)&addr, addrlen) == 0 && listen(s, 0) == 0
&& getsockname(s, (struct sockaddr*)&addr, &addrlen) == 0) {
*port = ntohs(addr.sin6_port);
return s;
}
} else if ((s = socket(AF_INET, SOCK_STREAM, 0)) != -1) {
struct sockaddr_in addr = {AF_INET, 0};
socklen_t addrlen = sizeof(addr);
if (bind(s, (struct sockaddr*)&addr, addrlen) == 0 && listen(s, 0) == 0
&& getsockname(s, (struct sockaddr*)&addr, &addrlen) == 0) {
*port = ntohs(addr.sin_port);
return s;
}
}
close_with_errno(s);
return -1;
}
static void close_attach_socket(int s, int pid) {
char path[MAX_PATH];
snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/replyInfo", tmp_path, pid);
unlink(path);
close(s);
}
static unsigned long long random_key() {
unsigned long long key = time(NULL) * 0xc6a4a7935bd1e995ULL;
int fd = open("/dev/urandom", O_RDONLY);
if (fd >= 0) {
ssize_t r = read(fd, &key, sizeof(key));
(void)r;
close(fd);
}
return key;
}
static int write_reply_info(int pid, int port, unsigned long long key) {
char path[MAX_PATH];
snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/replyInfo", tmp_path, pid);
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (fd < 0) {
return -1;
}
int chars = snprintf(path, sizeof(path), "%016llx\n%d\n", key, port);
ssize_t r = write(fd, path, chars);
(void)r;
close(fd);
return 0;
}
static int notify_semaphore(int value, int notif_count) {
char path[MAX_PATH];
snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/_notifier", tmp_path);
key_t sem_key = ftok(path, 0xa1);
int sem = semget(sem_key, 1, IPC_CREAT | 0666);
if (sem < 0) {
return -1;
}
struct sembuf op = {0, value, value < 0 ? IPC_NOWAIT : 0};
while (notif_count-- > 0) {
semop(sem, &op, 1);
}
return 0;
}
static int accept_client(int s, unsigned long long key) {
struct timeval tv = {5, 0};
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
int client = accept(s, NULL, NULL);
if (client < 0) {
perror("JVM did not respond");
return -1;
}
char buf[35];
size_t off = 0;
while (off < sizeof(buf)) {
ssize_t bytes = recv(client, buf + off, sizeof(buf) - off, 0);
if (bytes <= 0) {
fprintf(stderr, "The JVM connection was prematurely closed\n");
close(client);
return -1;
}
off += bytes;
}
char expected[35];
snprintf(expected, sizeof(expected), "ATTACH_CONNECTED %016llx ", key);
if (memcmp(buf, expected, sizeof(expected) - 1) != 0) {
fprintf(stderr, "Unexpected JVM response\n");
close(client);
return -1;
}
return client;
}
static int lock_notification_files() {
int count = 0;
char path[MAX_PATH];
snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach", tmp_path);
DIR* dir = opendir(path);
if (dir != NULL) {
struct dirent* entry;
while ((entry = readdir(dir)) != NULL && count < MAX_NOTIF_FILES) {
if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9' &&
(entry->d_type == DT_DIR || entry->d_type == DT_UNKNOWN)) {
notif_lock[count++] = acquire_lock(entry->d_name, "attachNotificationSync");
}
}
closedir(dir);
}
return count;
}
static void unlock_notification_files(int count) {
int i;
for (i = 0; i < count; i++) {
if (notif_lock[i] >= 0) {
release_lock(notif_lock[i]);
}
}
}
int is_openj9_process(int pid) {
char path[MAX_PATH];
snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/attachInfo", tmp_path, pid);
struct stat stats;
return stat(path, &stats) == 0;
}
int jattach_openj9(int pid, int nspid, int argc, char** argv) {
int attach_lock = acquire_lock("", "_attachlock");
if (attach_lock < 0) {
perror("Could not acquire attach lock");
return 1;
}
int notif_count = 0;
int port;
int s = create_attach_socket(&port);
if (s < 0) {
perror("Failed to listen to attach socket");
goto error;
}
unsigned long long key = random_key();
if (write_reply_info(nspid, port, key) != 0) {
perror("Could not write replyInfo");
goto error;
}
notif_count = lock_notification_files();
if (notify_semaphore(1, notif_count) != 0) {
perror("Could not notify semaphore");
goto error;
}
int fd = accept_client(s, key);
if (fd < 0) {
// The error message has been already printed
goto error;
}
close_attach_socket(s, nspid);
unlock_notification_files(notif_count);
notify_semaphore(-1, notif_count);
release_lock(attach_lock);
printf("Connected to remote JVM\n");
char cmd[8192];
translate_command(cmd, sizeof(cmd), argc, argv);
if (write_command(fd, cmd) != 0) {
perror("Error writing to socket");
close(fd);
return 1;
}
int result = read_response(fd, cmd);
if (result != 1) {
detach(fd);
}
close(fd);
return result;
error:
if (s >= 0) {
close_attach_socket(s, nspid);
}
if (notif_count > 0) {
unlock_notification_files(notif_count);
notify_semaphore(-1, notif_count);
}
release_lock(attach_lock);
return 1;
}

View File

@@ -1,241 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include "psutil.h"
// Less than MAX_PATH to leave some space for appending
char tmp_path[MAX_PATH - 100];
// Called just once to fill in tmp_path buffer
void get_tmp_path(int pid) {
// Try user-provided alternative path first
const char* jattach_path = getenv("JATTACH_PATH");
if (jattach_path != NULL && strlen(jattach_path) < sizeof(tmp_path)) {
strcpy(tmp_path, jattach_path);
return;
}
if (get_tmp_path_r(pid, tmp_path, sizeof(tmp_path)) != 0) {
strcpy(tmp_path, "/tmp");
}
}
#ifdef __linux__
// The first line of /proc/pid/sched looks like
// java (1234, #threads: 12)
// where 1234 is the host PID (before Linux 4.1)
static int sched_get_host_pid(const char* path) {
static char* line = NULL;
size_t size;
int result = -1;
FILE* sched_file = fopen(path, "r");
if (sched_file != NULL) {
if (getline(&line, &size, sched_file) != -1) {
char* c = strrchr(line, '(');
if (c != NULL) {
result = atoi(c + 1);
}
}
fclose(sched_file);
}
return result;
}
// Linux kernels < 4.1 do not export NStgid field in /proc/pid/status.
// Fortunately, /proc/pid/sched in a container exposes a host PID,
// so the idea is to scan all container PIDs to find which one matches the host PID.
static int alt_lookup_nspid(int pid) {
char path[300];
snprintf(path, sizeof(path), "/proc/%d/ns/pid", pid);
// Don't bother looking for container PID if we are already in the same PID namespace
struct stat oldns_stat, newns_stat;
if (stat("/proc/self/ns/pid", &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
if (oldns_stat.st_ino == newns_stat.st_ino) {
return pid;
}
}
// Otherwise browse all PIDs in the namespace of the target process
// trying to find which one corresponds to the host PID
snprintf(path, sizeof(path), "/proc/%d/root/proc", pid);
DIR* dir = opendir(path);
if (dir != NULL) {
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9') {
// Check if /proc/<container-pid>/sched points back to <host-pid>
snprintf(path, sizeof(path), "/proc/%d/root/proc/%s/sched", pid, entry->d_name);
if (sched_get_host_pid(path) == pid) {
closedir(dir);
return atoi(entry->d_name);
}
}
}
closedir(dir);
}
// Could not find container pid; return host pid as the last resort
return pid;
}
int get_tmp_path_r(int pid, char* buf, size_t bufsize) {
if (snprintf(buf, bufsize, "/proc/%d/root/tmp", pid) >= bufsize) {
return -1;
}
// Check if the remote /tmp can be accessed via /proc/[pid]/root
struct stat stats;
return stat(buf, &stats);
}
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
// Parse /proc/pid/status to find process credentials
char path[64];
snprintf(path, sizeof(path), "/proc/%d/status", pid);
FILE* status_file = fopen(path, "r");
if (status_file == NULL) {
return -1;
}
char* line = NULL;
size_t size;
int nspid_found = 0;
while (getline(&line, &size, status_file) != -1) {
if (strncmp(line, "Uid:", 4) == 0) {
// Get the effective UID, which is the second value in the line
*uid = (uid_t)atoi(strchr(line + 5, '\t'));
} else if (strncmp(line, "Gid:", 4) == 0) {
// Get the effective GID, which is the second value in the line
*gid = (gid_t)atoi(strchr(line + 5, '\t'));
} else if (strncmp(line, "NStgid:", 7) == 0) {
// PID namespaces can be nested; the last one is the innermost one
*nspid = atoi(strrchr(line, '\t'));
nspid_found = 1;
}
}
free(line);
fclose(status_file);
if (!nspid_found) {
*nspid = alt_lookup_nspid(pid);
}
return 0;
}
int enter_ns(int pid, const char* type) {
#ifdef __NR_setns
char path[64], selfpath[64];
snprintf(path, sizeof(path), "/proc/%d/ns/%s", pid, type);
snprintf(selfpath, sizeof(selfpath), "/proc/self/ns/%s", type);
struct stat oldns_stat, newns_stat;
if (stat(selfpath, &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
// Don't try to call setns() if we're in the same namespace already
if (oldns_stat.st_ino != newns_stat.st_ino) {
int newns = open(path, O_RDONLY);
if (newns < 0) {
return -1;
}
// Some ancient Linux distributions do not have setns() function
int result = syscall(__NR_setns, newns, 0);
close(newns);
return result < 0 ? -1 : 1;
}
}
#endif // __NR_setns
return 0;
}
#elif defined(__APPLE__)
#include <sys/sysctl.h>
// macOS has a secure per-user temporary directory
int get_tmp_path_r(int pid, char* buf, size_t bufsize) {
size_t path_size = confstr(_CS_DARWIN_USER_TEMP_DIR, buf, bufsize);
return path_size > 0 && path_size <= sizeof(tmp_path) ? 0 : -1;
}
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
struct kinfo_proc info;
size_t len = sizeof(info);
if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
return -1;
}
*uid = info.kp_eproc.e_ucred.cr_uid;
*gid = info.kp_eproc.e_ucred.cr_gid;
*nspid = pid;
return 0;
}
// This is a Linux-specific API; nothing to do on macOS and FreeBSD
int enter_ns(int pid, const char* type) {
return 0;
}
#else // __FreeBSD__
#include <sys/sysctl.h>
#include <sys/user.h>
// Use default /tmp path on FreeBSD
int get_tmp_path_r(int pid, char* buf, size_t bufsize) {
return -1;
}
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
struct kinfo_proc info;
size_t len = sizeof(info);
if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
return -1;
}
*uid = info.ki_uid;
*gid = info.ki_groups[0];
*nspid = pid;
return 0;
}
// This is a Linux-specific API; nothing to do on macOS and FreeBSD
int enter_ns(int pid, const char* type) {
return 0;
}
#endif

View File

@@ -1,46 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _PSUTIL_H
#define _PSUTIL_H
#include <sys/types.h>
#define MAX_PATH 1024
extern char tmp_path[];
// Gets /tmp path of the specified process, as it can be accessed from the host.
// The obtained path is stored in the global tmp_path buffer.
void get_tmp_path(int pid);
// The reentrant version of get_tmp_path.
// Stores the process-specific temporary path into the provided buffer.
// Returns 0 on success, -1 on failure.
int get_tmp_path_r(int pid, char* buf, size_t bufsize);
// Gets the owner uid/gid of the target process, and also its pid inside the container.
// Returns 0 on success, -1 on failure.
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid);
// Tries to enter the namespace of the target process.
// type of the namespace can be "mnt", "net", "pid", etc.
// Returns 1, if the namespace has been successfully changed,
// 0, if the target process is in the same namespace as the host,
// -1, if the attempt failed.
int enter_ns(int pid, const char* type);
#endif // _PSUTIL_H

View File

@@ -25,20 +25,13 @@
#include "vmStructs.h"
extern "C" DLLEXPORT void JNICALL
extern "C" JNIEXPORT void JNICALL
Java_one_profiler_AsyncProfiler_start0(JNIEnv* env, jobject unused, jstring event, jlong interval, jboolean reset) {
Arguments args;
const char* event_str = env->GetStringUTFChars(event, NULL);
if (strcmp(event_str, EVENT_ALLOC) == 0) {
args._alloc = interval > 0 ? interval : 1;
} else if (strcmp(event_str, EVENT_LOCK) == 0) {
args._lock = interval > 0 ? interval : 1;
} else {
args._event = event_str;
args._interval = interval;
}
Error error = Profiler::instance()->start(args, reset);
args.addEvent(event_str);
args._interval = interval;
Error error = Profiler::_instance.start(args, reset);
env->ReleaseStringUTFChars(event, event_str);
if (error) {
@@ -46,16 +39,16 @@ Java_one_profiler_AsyncProfiler_start0(JNIEnv* env, jobject unused, jstring even
}
}
extern "C" DLLEXPORT void JNICALL
extern "C" JNIEXPORT void JNICALL
Java_one_profiler_AsyncProfiler_stop0(JNIEnv* env, jobject unused) {
Error error = Profiler::instance()->stop();
Error error = Profiler::_instance.stop();
if (error) {
JavaAPI::throwNew(env, "java/lang/IllegalStateException", error.message());
}
}
extern "C" DLLEXPORT jstring JNICALL
extern "C" JNIEXPORT jstring JNICALL
Java_one_profiler_AsyncProfiler_execute0(JNIEnv* env, jobject unused, jstring command) {
Arguments args;
const char* command_str = env->GetStringUTFChars(command, NULL);
@@ -67,48 +60,44 @@ Java_one_profiler_AsyncProfiler_execute0(JNIEnv* env, jobject unused, jstring co
return NULL;
}
if (!args.hasOutputFile()) {
if (args._file == NULL || args._output == OUTPUT_JFR) {
std::ostringstream out;
error = Profiler::instance()->runInternal(args, out);
if (!error) {
if (out.tellp() >= 0x3fffffff) {
JavaAPI::throwNew(env, "java/lang/IllegalStateException", "Output exceeds string size limit");
return NULL;
}
return env->NewStringUTF(out.str().c_str());
}
Profiler::_instance.runInternal(args, out);
return env->NewStringUTF(out.str().c_str());
} else {
std::ofstream out(args.file(), std::ios::out | std::ios::trunc);
if (!out.is_open()) {
std::ofstream out(args._file, std::ios::out | std::ios::trunc);
if (out.is_open()) {
Profiler::_instance.runInternal(args, out);
out.close();
return env->NewStringUTF("OK");
} else {
JavaAPI::throwNew(env, "java/io/IOException", strerror(errno));
return NULL;
}
error = Profiler::instance()->runInternal(args, out);
out.close();
if (!error) {
return env->NewStringUTF("OK");
}
}
JavaAPI::throwNew(env, "java/lang/IllegalStateException", error.message());
return NULL;
}
extern "C" DLLEXPORT jlong JNICALL
extern "C" JNIEXPORT jlong JNICALL
Java_one_profiler_AsyncProfiler_getSamples(JNIEnv* env, jobject unused) {
return (jlong)Profiler::instance()->total_samples();
return (jlong)Profiler::_instance.total_samples();
}
extern "C" DLLEXPORT void JNICALL
extern "C" JNIEXPORT void JNICALL
Java_one_profiler_AsyncProfiler_filterThread0(JNIEnv* env, jobject unused, jthread thread, jboolean enable) {
int thread_id;
if (thread == NULL) {
thread_id = OS::threadId();
} else if ((thread_id = VMThread::nativeThreadId(env, thread)) < 0) {
} else if (VMThread::hasNativeId()) {
VMThread* vmThread = VMThread::fromJavaThread(env, thread);
if (vmThread == NULL) {
return;
}
thread_id = vmThread->osThreadId();
} else {
return;
}
ThreadFilter* thread_filter = Profiler::instance()->threadFilter();
ThreadFilter* thread_filter = Profiler::_instance.threadFilter();
if (enable) {
thread_filter->add(thread_id);
} else {

View File

@@ -82,9 +82,6 @@ JfrMetadata::JfrMetadata() : Element("root") {
<< (type("jdk.types.Symbol", T_SYMBOL, "Symbol", true)
<< field("string", T_STRING, "String"))
<< (type("profiler.types.LogLevel", T_LOG_LEVEL, "Log Level", true)
<< field("name", T_STRING, "Name"))
<< (type("jdk.ExecutionSample", T_EXECUTION_SAMPLE, "Method Profiling Sample")
<< category("Java Virtual Machine", "Profiling")
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
@@ -116,7 +113,6 @@ JfrMetadata::JfrMetadata() : Element("root") {
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
<< field("monitorClass", T_CLASS, "Monitor Class", F_CPOOL)
<< field("previousOwner", T_THREAD, "Previous Monitor Owner", F_CPOOL)
<< field("address", T_LONG, "Monitor Address", F_ADDRESS))
<< (type("jdk.ThreadPark", T_THREAD_PARK, "Java Thread Park")
@@ -127,7 +123,6 @@ JfrMetadata::JfrMetadata() : Element("root") {
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
<< field("parkedClass", T_CLASS, "Class Parked On", F_CPOOL)
<< field("timeout", T_LONG, "Park Timeout", F_DURATION_NANOS)
<< field("until", T_LONG, "Park Until", F_TIME_MILLIS)
<< field("address", T_LONG, "Address of Object Parked", F_ADDRESS))
<< (type("jdk.CPULoad", T_CPU_LOAD, "CPU Load")
@@ -137,7 +132,7 @@ JfrMetadata::JfrMetadata() : Element("root") {
<< field("jvmSystem", T_FLOAT, "JVM System", F_PERCENTAGE)
<< field("machineTotal", T_FLOAT, "Machine Total", F_PERCENTAGE))
<< (type("jdk.ActiveRecording", T_ACTIVE_RECORDING, "Async-profiler Recording")
<< (type("jdk.ActiveRecording", T_ACTIVE_RECORDING, "Flight Recording")
<< category("Flight Recorder")
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
<< field("duration", T_LONG, "Duration", F_DURATION_TICKS)
@@ -150,12 +145,11 @@ 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, "Async-profiler Setting")
<< (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("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
<< field("id", T_LONG, "Event Id")
<< field("name", T_STRING, "Setting Name")
<< field("value", T_STRING, "Setting Value"))
@@ -191,19 +185,6 @@ JfrMetadata::JfrMetadata() : Element("root") {
<< field("key", T_STRING, "Key")
<< field("value", T_STRING, "Value"))
<< (type("jdk.NativeLibrary", T_NATIVE_LIBRARY, "Native Library")
<< category("Java Virtual Machine", "Runtime")
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
<< field("name", T_STRING, "Name")
<< field("baseAddress", T_LONG, "Base Address", F_ADDRESS)
<< field("topAddress", T_LONG, "Top Address", F_ADDRESS))
<< (type("profiler.Log", T_LOG, "Log Message")
<< category("Profiler")
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
<< field("level", T_LOG_LEVEL, "Level", F_CPOOL)
<< field("message", T_STRING, "Message"))
<< (type("jdk.jfr.Label", T_LABEL, NULL)
<< field("value", T_STRING))

View File

@@ -48,7 +48,6 @@ enum JfrType {
T_METHOD = 28,
T_PACKAGE = 29,
T_SYMBOL = 30,
T_LOG_LEVEL = 31,
T_EVENT = 100,
T_EXECUTION_SAMPLE = 101,
@@ -63,8 +62,6 @@ enum JfrType {
T_CPU_INFORMATION = 110,
T_JVM_INFORMATION = 111,
T_INITIAL_SYSTEM_PROPERTY = 112,
T_NATIVE_LIBRARY = 113,
T_LOG = 114,
T_ANNOTATION = 200,
T_LABEL = 201,

71
src/jstack.cpp Normal file
View 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
}

View File

@@ -14,39 +14,28 @@
* limitations under the License.
*/
#ifndef _J9WALLCLOCK_H
#define _J9WALLCLOCK_H
#ifndef _JSTACK_H
#define _JSTACK_H
#include <pthread.h>
#include <signal.h>
#include "engine.h"
class J9WallClock : public Engine {
class JStack : public Engine {
private:
static long _interval;
int _max_stack_depth;
volatile bool _running;
pthread_t _thread;
static void* threadEntry(void* wall_clock) {
((J9WallClock*)wall_clock)->timerLoop();
return NULL;
}
void timerLoop();
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
public:
const char* title() {
return "Wall clock profile";
const char* name() {
return EVENT_JSTACK;
}
const char* units() {
return "ns";
return "samples";
}
Error start(Arguments& args);
void stop();
};
#endif // _J9WALLCLOCK_H
#endif // _JSTACK_H

View File

@@ -16,37 +16,31 @@
#include <string.h>
#include "lockTracer.h"
#include "os.h"
#include "profiler.h"
#include "tsc.h"
#include "vmStructs.h"
double LockTracer::_ticks_to_nanos;
jlong LockTracer::_threshold;
jlong LockTracer::_start_time = 0;
jclass LockTracer::_UnsafeClass = NULL;
jclass LockTracer::_LockSupport = NULL;
jmethodID LockTracer::_getBlocker = NULL;
RegisterNativesFunc LockTracer::_orig_RegisterNatives = NULL;
UnsafeParkFunc LockTracer::_orig_Unsafe_park = NULL;
bool LockTracer::_initialized = false;
Error LockTracer::start(Arguments& args) {
_ticks_to_nanos = 1e9 / TSC::frequency();
_threshold = (jlong)(args._lock * (TSC::frequency() / 1e9));
if (!_initialized) {
initialize();
}
// Enable Java Monitor events
jvmtiEnv* jvmti = VM::jvmti();
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTER, NULL);
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, NULL);
_start_time = TSC::ticks();
_start_time = OS::nanotime();
// Intercept Unsafe.park() for tracing contended ReentrantLocks
if (_orig_Unsafe_park != NULL) {
bindUnsafePark(UnsafeParkHook);
if (_getBlocker == NULL) {
JNIEnv* env = VM::jni();
_LockSupport = (jclass)env->NewGlobalRef(env->FindClass("java/util/concurrent/locks/LockSupport"));
_getBlocker = env->GetStaticMethodID(_LockSupport, "getBlocker", "(Ljava/lang/Thread;)Ljava/lang/Object;");
}
// Intercent Unsafe.park() for tracing contended ReentrantLocks
if (VMStructs::_unsafe_park != NULL) {
bindUnsafePark(UnsafeParkTrap);
}
return Error::OK;
@@ -59,98 +53,48 @@ void LockTracer::stop() {
jvmti->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, NULL);
// Reset Unsafe.park() trap
if (_orig_Unsafe_park != NULL) {
bindUnsafePark(_orig_Unsafe_park);
if (VMStructs::_unsafe_park != NULL) {
bindUnsafePark(VMStructs::_unsafe_park);
}
}
void LockTracer::initialize() {
jvmtiEnv* jvmti = VM::jvmti();
JNIEnv* env = VM::jni();
// Try JDK 9+ package first, then fallback to JDK 8 package
jclass unsafe = env->FindClass("jdk/internal/misc/Unsafe");
if (unsafe == NULL) {
env->ExceptionClear();
if ((unsafe = env->FindClass("sun/misc/Unsafe")) == NULL) {
env->ExceptionClear();
return;
}
}
_UnsafeClass = (jclass)env->NewGlobalRef(unsafe);
jmethodID register_natives = env->GetStaticMethodID(_UnsafeClass, "registerNatives", "()V");
jniNativeInterface* jni_functions;
if (register_natives != NULL && jvmti->GetJNIFunctionTable(&jni_functions) == 0) {
_orig_RegisterNatives = jni_functions->RegisterNatives;
jni_functions->RegisterNatives = RegisterNativesHook;
jvmti->SetJNIFunctionTable(jni_functions);
// Trace Unsafe.registerNatives() to find the original address of Unsafe.park() native
env->CallStaticVoidMethod(_UnsafeClass, register_natives);
jni_functions->RegisterNatives = _orig_RegisterNatives;
jvmti->SetJNIFunctionTable(jni_functions);
}
_LockSupport = (jclass)env->NewGlobalRef(env->FindClass("java/util/concurrent/locks/LockSupport"));
_getBlocker = env->GetStaticMethodID(_LockSupport, "getBlocker", "(Ljava/lang/Thread;)Ljava/lang/Object;");
env->ExceptionClear();
_initialized = true;
}
void JNICALL LockTracer::MonitorContendedEnter(jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jobject object) {
jlong enter_time = TSC::ticks();
jlong enter_time = OS::nanotime();
jvmti->SetTag(thread, enter_time);
}
void JNICALL LockTracer::MonitorContendedEntered(jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jobject object) {
jlong entered_time = TSC::ticks();
jlong entered_time = OS::nanotime();
jlong enter_time;
jvmti->GetTag(thread, &enter_time);
// Time is meaningless if lock attempt has started before profiling
if (_enabled && entered_time - enter_time >= _threshold && enter_time >= _start_time) {
if (_enabled && enter_time >= _start_time) {
char* lock_name = getLockName(jvmti, env, object);
recordContendedLock(BCI_LOCK, enter_time, entered_time, lock_name, object, 0);
jvmti->Deallocate((unsigned char*)lock_name);
}
}
jint JNICALL LockTracer::RegisterNativesHook(JNIEnv* env, jclass cls, const JNINativeMethod* methods, jint nMethods) {
if (env->IsSameObject(cls, _UnsafeClass)) {
for (jint i = 0; i < nMethods; i++) {
if (strcmp(methods[i].name, "park") == 0 && strcmp(methods[i].signature, "(ZJ)V") == 0) {
_orig_Unsafe_park = (UnsafeParkFunc)methods[i].fnPtr;
break;
}
}
return 0;
}
return _orig_RegisterNatives(env, cls, methods, nMethods);
}
void JNICALL LockTracer::UnsafeParkHook(JNIEnv* env, jobject instance, jboolean isAbsolute, jlong time) {
void JNICALL LockTracer::UnsafeParkTrap(JNIEnv* env, jobject instance, jboolean isAbsolute, jlong time) {
jvmtiEnv* jvmti = VM::jvmti();
jobject park_blocker = _enabled ? getParkBlocker(jvmti, env) : NULL;
jlong park_start_time, park_end_time;
if (park_blocker != NULL) {
park_start_time = TSC::ticks();
park_start_time = OS::nanotime();
}
_orig_Unsafe_park(env, instance, isAbsolute, time);
VMStructs::_unsafe_park(env, instance, isAbsolute, time);
if (park_blocker != NULL) {
park_end_time = TSC::ticks();
if (park_end_time - park_start_time >= _threshold) {
char* lock_name = getLockName(jvmti, env, park_blocker);
if (lock_name == NULL || isConcurrentLock(lock_name)) {
recordContendedLock(BCI_PARK, park_start_time, park_end_time, lock_name, park_blocker, time);
}
jvmti->Deallocate((unsigned char*)lock_name);
park_end_time = OS::nanotime();
char* lock_name = getLockName(jvmti, env, park_blocker);
if (lock_name == NULL || isConcurrentLock(lock_name)) {
recordContendedLock(BCI_PARK, park_start_time, park_end_time, lock_name, park_blocker, time);
}
jvmti->Deallocate((unsigned char*)lock_name);
}
}
@@ -190,20 +134,26 @@ void LockTracer::recordContendedLock(int event_type, u64 start_time, u64 end_tim
if (lock_name != NULL) {
if (lock_name[0] == 'L') {
event._class_id = Profiler::instance()->classMap()->lookup(lock_name + 1, strlen(lock_name) - 2);
event._class_id = Profiler::_instance.classMap()->lookup(lock_name + 1, strlen(lock_name) - 2);
} else {
event._class_id = Profiler::instance()->classMap()->lookup(lock_name);
event._class_id = Profiler::_instance.classMap()->lookup(lock_name);
}
}
u64 duration_nanos = (u64)((end_time - start_time) * _ticks_to_nanos);
Profiler::instance()->recordSample(NULL, duration_nanos, event_type, &event);
Profiler::_instance.recordSample(NULL, end_time - start_time, event_type, &event);
}
void LockTracer::bindUnsafePark(UnsafeParkFunc entry) {
JNIEnv* env = VM::jni();
const JNINativeMethod park = {(char*)"park", (char*)"(ZJ)V", (void*)entry};
if (env->RegisterNatives(_UnsafeClass, &park, 1) != 0) {
env->ExceptionClear();
// Try JDK 9+ package first, then fallback to JDK 8 package
jclass unsafe = env->FindClass("jdk/internal/misc/Unsafe");
if (unsafe == NULL) unsafe = env->FindClass("sun/misc/Unsafe");
if (unsafe != NULL) {
const JNINativeMethod unsafe_park = {(char*)"park", (char*)"(ZJ)V", (void*)entry};
env->RegisterNatives(unsafe, &unsafe_park, 1);
}
env->ExceptionClear();
}

View File

@@ -22,26 +22,13 @@
#include "engine.h"
typedef jint (JNICALL *RegisterNativesFunc)(JNIEnv*, jclass, const JNINativeMethod*, jint);
typedef void (JNICALL *UnsafeParkFunc)(JNIEnv*, jobject, jboolean, jlong);
class LockTracer : public Engine {
private:
static double _ticks_to_nanos;
static jlong _threshold;
static jlong _start_time;
static jclass _UnsafeClass;
static jclass _LockSupport;
static jmethodID _getBlocker;
static bool _initialized;
static void initialize();
static RegisterNativesFunc _orig_RegisterNatives;
static jint JNICALL RegisterNativesHook(JNIEnv* env, jclass cls, const JNINativeMethod* methods, jint nMethods);
static UnsafeParkFunc _orig_Unsafe_park;
static void JNICALL UnsafeParkHook(JNIEnv* env, jobject instance, jboolean isAbsolute, jlong time);
static jobject getParkBlocker(jvmtiEnv* jvmti, JNIEnv* env);
static char* getLockName(jvmtiEnv* jvmti, JNIEnv* env, jobject lock);
@@ -51,19 +38,24 @@ class LockTracer : public Engine {
static void bindUnsafePark(UnsafeParkFunc entry);
public:
const char* title() {
return "Lock profile";
const char* name() {
return "lock";
}
const char* units() {
return "ns";
}
CStack cstack() {
return CSTACK_NO;
}
Error start(Arguments& args);
void stop();
static void JNICALL MonitorContendedEnter(jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jobject object);
static void JNICALL MonitorContendedEntered(jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jobject object);
static void JNICALL UnsafeParkTrap(JNIEnv* env, jobject instance, jboolean isAbsolute, jlong time);
};
#endif // _LOCKTRACER_H

View File

@@ -1,84 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <string.h>
#include "log.h"
#include "profiler.h"
const char* const Log::LEVEL_NAME[] = {NULL, "TRACE", "DEBUG", "INFO", "WARN", "ERROR"};
FILE* Log::_file = stdout;
void Log::open(const char* file_name) {
if (_file != stdout && _file != stderr) {
fclose(_file);
}
if (file_name == NULL || strcmp(file_name, "stdout") == 0) {
_file = stdout;
} else if (strcmp(file_name, "stderr") == 0) {
_file = stderr;
} else if ((_file = fopen(file_name, "w")) == NULL) {
_file = stdout;
warn("Could not open log file: %s", file_name);
}
}
void Log::close() {
if (_file != stdout && _file != stderr) {
fclose(_file);
_file = stdout;
}
}
void Log::log(LogLevel level, const char* msg, va_list args) {
char buf[1024];
size_t len = vsnprintf(buf, sizeof(buf), msg, args);
if (len >= sizeof(buf)) {
len = sizeof(buf) - 1;
buf[len] = 0;
}
if (level < LOG_ERROR) {
Profiler::instance()->writeLog(level, buf, len);
}
fprintf(_file, "[%s] %s\n", LEVEL_NAME[level], buf);
fflush(_file);
}
void Log::info(const char* msg, ...) {
va_list args;
va_start(args, msg);
log(LOG_INFO, msg, args);
va_end(args);
}
void Log::warn(const char* msg, ...) {
va_list args;
va_start(args, msg);
log(LOG_WARN, msg, args);
va_end(args);
}
void Log::error(const char* msg, ...) {
va_list args;
va_start(args, msg);
log(LOG_ERROR, msg, args);
va_end(args);
}

View File

@@ -1,57 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _LOG_H
#define _LOG_H
#include <stdarg.h>
#include <stdio.h>
#ifdef __GNUC__
#define ATTR_FORMAT __attribute__((format(printf, 1, 2)))
#else
#define ATTR_FORMAT
#endif
enum LogLevel {
LOG_NONE,
LOG_TRACE,
LOG_DEBUG,
LOG_INFO,
LOG_WARN,
LOG_ERROR
};
class Log {
private:
static FILE* _file;
public:
static const char* const LEVEL_NAME[];
static void open(const char* file_name);
static void close();
static void log(LogLevel level, const char* msg, va_list args);
static void ATTR_FORMAT info(const char* msg, ...);
static void ATTR_FORMAT warn(const char* msg, ...);
static void ATTR_FORMAT error(const char* msg, ...);
};
#endif // _LOG_H

View File

@@ -1,93 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <string.h>
#include "objectSampler.h"
#include "j9Ext.h"
#include "profiler.h"
u64 ObjectSampler::_interval;
volatile u64 ObjectSampler::_allocated_bytes;
void ObjectSampler::JavaObjectAlloc(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread,
jobject object, jclass object_klass, jlong size) {
if (_enabled) {
recordAllocation(jvmti, BCI_ALLOC, object_klass, size);
}
}
void ObjectSampler::VMObjectAlloc(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread,
jobject object, jclass object_klass, jlong size) {
if (_enabled) {
recordAllocation(jvmti, BCI_ALLOC_OUTSIDE_TLAB, object_klass, size);
}
}
void ObjectSampler::recordAllocation(jvmtiEnv* jvmti, int event_type, jclass object_klass, jlong size) {
if (!updateCounter(_allocated_bytes, size, _interval)) {
return;
}
AllocEvent event;
event._class_id = 0;
event._total_size = size;
event._instance_size = size;
char* class_name;
if (jvmti->GetClassSignature(object_klass, &class_name, NULL) == 0) {
if (class_name[0] == 'L') {
event._class_id = Profiler::instance()->classMap()->lookup(class_name + 1, strlen(class_name) - 2);
} else {
event._class_id = Profiler::instance()->classMap()->lookup(class_name);
}
jvmti->Deallocate((unsigned char*)class_name);
}
Profiler::instance()->recordSample(NULL, size, event_type, &event);
}
Error ObjectSampler::check(Arguments& args) {
if (J9Ext::InstrumentableObjectAlloc_id < 0) {
return Error("InstrumentableObjectAlloc is not supported on this JVM");
}
return Error::OK;
}
Error ObjectSampler::start(Arguments& args) {
Error error = check(args);
if (error) {
return error;
}
_interval = args._alloc > 1 ? args._alloc : 524287;
_allocated_bytes = 0;
jvmtiEnv* jvmti = VM::jvmti();
if (jvmti->SetExtensionEventCallback(J9Ext::InstrumentableObjectAlloc_id, (jvmtiExtensionEvent)JavaObjectAlloc) != 0) {
return Error("Could not enable InstrumentableObjectAlloc callback");
}
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, NULL);
return Error::OK;
}
void ObjectSampler::stop() {
jvmtiEnv* jvmti = VM::jvmti();
jvmti->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, NULL);
jvmti->SetExtensionEventCallback(J9Ext::InstrumentableObjectAlloc_id, NULL);
}

View File

@@ -1,52 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _OBJECTSAMPLER_H
#define _OBJECTSAMPLER_H
#include <jvmti.h>
#include "arch.h"
#include "engine.h"
class ObjectSampler : public Engine {
private:
static u64 _interval;
static volatile u64 _allocated_bytes;
static void recordAllocation(jvmtiEnv* jvmti, int event_type, jclass object_klass, jlong size);
public:
const char* title() {
return "Allocation profile";
}
const char* units() {
return "bytes";
}
Error check(Arguments& args);
Error start(Arguments& args);
void stop();
static void JNICALL JavaObjectAlloc(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread,
jobject object, jclass object_klass, jlong size);
static void JNICALL VMObjectAlloc(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread,
jobject object, jclass object_klass, jlong size);
};
#endif // _OBJECTSAMPLER_H

View File

@@ -19,17 +19,9 @@
#include <signal.h>
#include <stddef.h>
#include <sys/types.h>
#include "arch.h"
typedef void (*SigAction)(int, siginfo_t*, void*);
typedef void (*SigHandler)(int);
typedef void (*TimerCallback)(void*);
// Interrupt threads with this signal. The same signal is used inside JDK to interrupt I/O operations.
const int WAKEUP_SIGNAL = SIGIO;
enum ThreadState {
THREAD_INVALID,
THREAD_RUNNING,
@@ -37,6 +29,10 @@ enum ThreadState {
};
class Timer {
};
class ThreadList {
public:
virtual ~ThreadList() {}
@@ -46,27 +42,16 @@ class ThreadList {
};
// W^X memory support
class JitWriteProtection {
private:
u64 _prev;
bool _restore;
public:
JitWriteProtection(bool enable);
~JitWriteProtection();
};
class OS {
public:
static const size_t page_size;
static const size_t page_mask;
private:
typedef void (*SigAction)(int, siginfo_t*, void*);
typedef void (*SigHandler)(int);
typedef void (*TimerCallback)(void*);
public:
static u64 nanotime();
static u64 micros();
static u64 millis();
static u64 processStartTime();
static void sleep(u64 nanos);
static u64 hton64(u64 x);
static u64 ntoh64(u64 x);
@@ -74,26 +59,24 @@ class OS {
static int getMaxThreadId();
static int processId();
static int threadId();
static const char* schedPolicy(int thread_id);
static bool threadName(int thread_id, char* name_buf, size_t name_len);
static ThreadState threadState(int thread_id);
static ThreadList* listThreads();
static bool isJavaLibraryVisible();
static SigAction installSignalHandler(int signo, SigAction action, SigHandler handler = NULL);
static SigAction replaceCrashHandler(SigAction action);
static void installSignalHandler(int signo, SigAction action, SigHandler handler = NULL);
static bool sendSignalToThread(int thread_id, int signo);
static void* safeAlloc(size_t size);
static void safeFree(void* addr, size_t size);
static Timer* startTimer(u64 interval, TimerCallback callback, void* arg);
static void stopTimer(Timer* timer);
static bool getCpuDescription(char* buf, size_t size);
static u64 getProcessCpuTime(u64* utime, u64* stime);
static u64 getTotalCpuTime(u64* utime, u64* stime);
static void copyFile(int src_fd, int dst_fd, off_t offset, size_t size);
static void freePageCache(int fd, off_t start_offset);
};
#endif // _OS_H

View File

@@ -20,12 +20,10 @@
#include <byteswap.h>
#include <dirent.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
@@ -109,28 +107,16 @@ class LinuxThreadList : public ThreadList {
};
JitWriteProtection::JitWriteProtection(bool enable) {
// Not used on Linux
}
JitWriteProtection::~JitWriteProtection() {
// Not used on Linux
}
const size_t OS::page_size = sysconf(_SC_PAGESIZE);
const size_t OS::page_mask = OS::page_size - 1;
u64 OS::nanotime() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (u64)ts.tv_sec * 1000000000 + ts.tv_nsec;
struct timespec tp;
clock_gettime(CLOCK_MONOTONIC, &tp);
return (u64)tp.tv_sec * 1000000000 + tp.tv_nsec;
}
u64 OS::micros() {
u64 OS::millis() {
struct timeval tv;
gettimeofday(&tv, NULL);
return (u64)tv.tv_sec * 1000000 + tv.tv_usec;
return (u64)tv.tv_sec * 1000 + tv.tv_usec / 1000;
}
u64 OS::processStartTime() {
@@ -149,11 +135,6 @@ u64 OS::processStartTime() {
return start_time;
}
void OS::sleep(u64 nanos) {
struct timespec ts = {(time_t)(nanos / 1000000000), (long)(nanos % 1000000000)};
nanosleep(&ts, NULL);
}
u64 OS::hton64(u64 x) {
return htonl(1) == 1 ? x : bswap_64(x);
}
@@ -183,14 +164,6 @@ int OS::threadId() {
return syscall(__NR_gettid);
}
const char* OS::schedPolicy(int thread_id) {
int sched_policy = sched_getscheduler(thread_id);
if (sched_policy >= SCHED_BATCH) {
return sched_policy >= SCHED_IDLE ? "SCHED_IDLE" : "SCHED_BATCH";
}
return "SCHED_OTHER";
}
bool OS::threadName(int thread_id, char* name_buf, size_t name_len) {
char buf[64];
sprintf(buf, "/proc/self/task/%d/comm", thread_id);
@@ -235,9 +208,8 @@ bool OS::isJavaLibraryVisible() {
return false;
}
SigAction OS::installSignalHandler(int signo, SigAction action, SigHandler handler) {
void OS::installSignalHandler(int signo, SigAction action, SigHandler handler) {
struct sigaction sa;
struct sigaction oldsa;
sigemptyset(&sa.sa_mask);
if (handler != NULL) {
@@ -248,17 +220,7 @@ SigAction OS::installSignalHandler(int signo, SigAction action, SigHandler handl
sa.sa_flags = SA_SIGINFO | SA_RESTART;
}
sigaction(signo, &sa, &oldsa);
return oldsa.sa_sigaction;
}
SigAction OS::replaceCrashHandler(SigAction action) {
struct sigaction sa;
sigaction(SIGSEGV, NULL, &sa);
SigAction old_action = sa.sa_sigaction;
sa.sa_sigaction = action;
sigaction(SIGSEGV, &sa, NULL);
return old_action;
sigaction(signo, &sa, NULL);
}
bool OS::sendSignalToThread(int thread_id, int signo) {
@@ -279,6 +241,30 @@ void OS::safeFree(void* addr, size_t size) {
syscall(__NR_munmap, addr, size);
}
Timer* OS::startTimer(u64 interval, TimerCallback callback, void* arg) {
struct sigevent sev;
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_value.sival_ptr = arg;
sev.sigev_notify_function = (void (*)(union sigval)) callback;
sev.sigev_notify_attributes = NULL;
timer_t timer;
if (timer_create(CLOCK_MONOTONIC, &sev, &timer) != 0) {
return NULL;
}
struct itimerspec spec;
spec.it_interval.tv_sec = spec.it_value.tv_sec = interval / 1000000000;
spec.it_interval.tv_nsec = spec.it_value.tv_nsec = interval % 1000000000;
timer_settime(timer, 0, &spec, NULL);
return (Timer*)timer;
}
void OS::stopTimer(Timer* timer) {
timer_delete((timer_t)timer);
}
bool OS::getCpuDescription(char* buf, size_t size) {
int fd = open("/proc/cpuinfo", O_RDONLY);
if (fd == -1) {
@@ -330,19 +316,4 @@ u64 OS::getTotalCpuTime(u64* utime, u64* stime) {
return real;
}
void OS::copyFile(int src_fd, int dst_fd, off_t offset, size_t size) {
// copy_file_range() is probably better, but not supported on all kernels
while (size > 0) {
ssize_t bytes = sendfile(dst_fd, src_fd, &offset, size);
if (bytes <= 0) {
break;
}
size -= (size_t)bytes;
}
}
void OS::freePageCache(int fd, off_t start_offset) {
posix_fadvise(fd, start_offset & ~page_mask, 0, POSIX_FADV_DONTNEED);
}
#endif // __linux__

View File

@@ -16,6 +16,7 @@
#ifdef __APPLE__
#include <dispatch/dispatch.h>
#include <libkern/OSByteOrder.h>
#include <libproc.h>
#include <mach/mach.h>
@@ -27,8 +28,6 @@
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/times.h>
#include <time.h>
#include <unistd.h>
#include "os.h"
@@ -82,39 +81,6 @@ class MacThreadList : public ThreadList {
};
JitWriteProtection::JitWriteProtection(bool enable) {
#ifdef __aarch64__
// Mimic pthread_jit_write_protect_np(), but save the previous state
u64 val = enable ? *(volatile u64*)0xfffffc118 : *(volatile u64*)0xfffffc110;
u64 prev;
asm volatile("mrs %0, s3_6_c15_c1_5" : "=r" (prev) : : );
if (prev != val) {
_prev = prev;
_restore = true;
asm volatile("msr s3_6_c15_c1_5, %0\n"
"isb"
: "+r" (val) : : "memory");
} else {
_restore = false;
}
#endif
}
JitWriteProtection::~JitWriteProtection() {
#ifdef __aarch64__
if (_restore) {
u64 prev = _prev;
asm volatile("msr s3_6_c15_c1_5, %0\n"
"isb"
: "+r" (prev) : : "memory");
}
#endif
}
const size_t OS::page_size = sysconf(_SC_PAGESIZE);
const size_t OS::page_mask = OS::page_size - 1;
static mach_timebase_info_data_t timebase = {0, 0};
u64 OS::nanotime() {
@@ -124,15 +90,10 @@ u64 OS::nanotime() {
return (u64)mach_absolute_time() * timebase.numer / timebase.denom;
}
u64 OS::micros() {
u64 OS::millis() {
struct timeval tv;
gettimeofday(&tv, NULL);
return (u64)tv.tv_sec * 1000000 + tv.tv_usec;
}
void OS::sleep(u64 nanos) {
struct timespec ts = {(time_t)(nanos / 1000000000), (long)(nanos % 1000000000)};
nanosleep(&ts, NULL);
return (u64)tv.tv_sec * 1000 + tv.tv_usec / 1000;
}
u64 OS::processStartTime() {
@@ -174,11 +135,6 @@ int OS::threadId() {
return (int)port;
}
const char* OS::schedPolicy(int thread_id) {
// Not used on macOS
return "SCHED_OTHER";
}
bool OS::threadName(int thread_id, char* name_buf, size_t name_len) {
pthread_t thread = pthread_from_mach_thread_np(thread_id);
return thread && pthread_getname_np(thread, name_buf, name_len) == 0 && name_buf[0] != 0;
@@ -201,9 +157,8 @@ bool OS::isJavaLibraryVisible() {
return true;
}
SigAction OS::installSignalHandler(int signo, SigAction action, SigHandler handler) {
void OS::installSignalHandler(int signo, SigAction action, SigHandler handler) {
struct sigaction sa;
struct sigaction oldsa;
sigemptyset(&sa.sa_mask);
if (handler != NULL) {
@@ -214,37 +169,16 @@ SigAction OS::installSignalHandler(int signo, SigAction action, SigHandler handl
sa.sa_flags = SA_SIGINFO | SA_RESTART;
}
sigaction(signo, &sa, &oldsa);
return oldsa.sa_sigaction;
}
SigAction OS::replaceCrashHandler(SigAction action) {
struct sigaction sa;
sigaction(SIGBUS, NULL, &sa);
SigAction old_action = sa.sa_sigaction;
sa.sa_sigaction = action;
sigaction(SIGBUS, &sa, NULL);
return old_action;
sigaction(signo, &sa, NULL);
}
bool OS::sendSignalToThread(int thread_id, int signo) {
#ifdef __aarch64__
register long x0 asm("x0") = thread_id;
register long x1 asm("x1") = signo;
register long x16 asm("x16") = 328;
asm volatile("svc #0x80"
: "+r" (x0)
: "r" (x1), "r" (x16)
: "memory");
return x0 == 0;
#else
int result;
asm volatile("syscall"
: "=a" (result)
: "a" (0x2000148), "D" (thread_id), "S" (signo)
: "rcx", "r11", "memory");
return result == 0;
#endif
int result;
asm volatile("syscall"
: "=a" (result)
: "a" (0x2000148), "D" (thread_id), "S" (signo)
: "rcx", "r11", "memory");
return result == 0;
}
void* OS::safeAlloc(size_t size) {
@@ -261,6 +195,26 @@ void OS::safeFree(void* addr, size_t size) {
munmap(addr, size);
}
Timer* OS::startTimer(u64 interval, TimerCallback callback, void* arg) {
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (source != NULL) {
dispatch_source_set_timer(source, dispatch_time(DISPATCH_TIME_NOW, interval), interval, 0);
dispatch_source_set_event_handler_f(source, callback);
dispatch_set_context(source, arg);
dispatch_resume(source);
}
return (Timer*)source;
}
void OS::stopTimer(Timer* timer) {
dispatch_source_t source = (dispatch_source_t)timer;
if (source != NULL) {
dispatch_source_cancel(source);
dispatch_release(source);
}
}
bool OS::getCpuDescription(char* buf, size_t size) {
return sysctlbyname("machdep.cpu.brand_string", buf, &size, NULL, 0) == 0;
}
@@ -301,26 +255,4 @@ u64 OS::getTotalCpuTime(u64* utime, u64* stime) {
return user + system + idle;
}
void OS::copyFile(int src_fd, int dst_fd, off_t offset, size_t size) {
char* buf = (char*)mmap(NULL, size + offset, PROT_READ, MAP_PRIVATE, src_fd, 0);
if (buf == NULL) {
return;
}
while (size > 0) {
ssize_t bytes = write(dst_fd, buf + offset, size < 262144 ? size : 262144);
if (bytes <= 0) {
break;
}
offset += (size_t)bytes;
size -= (size_t)bytes;
}
munmap(buf, offset);
}
void OS::freePageCache(int fd, off_t start_offset) {
// Not supported on macOS
}
#endif // __APPLE__

View File

@@ -18,7 +18,6 @@
#define _PERFEVENTS_H
#include <signal.h>
#include "arch.h"
#include "engine.h"
@@ -33,26 +32,28 @@ class PerfEvents : public Engine {
static long _interval;
static Ring _ring;
static CStack _cstack;
static bool _print_extended_warning;
static u64 readCounter(siginfo_t* siginfo, void* ucontext);
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
static void signalHandlerJ9(int signo, siginfo_t* siginfo, void* ucontext);
public:
const char* name() {
return "perf";
}
const char* units();
Error check(Arguments& args);
Error start(Arguments& args);
void stop();
const char* title();
const char* units();
static int walk(int tid, const void** callchain, int max_depth);
static void resetBuffer(int tid);
int getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
CodeCache* java_methods, CodeCache* runtime_stubs);
static bool supported();
static const char* getEventName(int event_id);
static int createForThread(int tid);
static bool createForThread(int tid);
static void destroyForThread(int tid);
};

View File

@@ -25,24 +25,18 @@
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <linux/perf_event.h>
#include "arch.h"
#include "j9StackTraces.h"
#include "log.h"
#include "os.h"
#include "perfEvents.h"
#include "fdtransferClient.h"
#include "profiler.h"
#include "spinLock.h"
#include "stackFrame.h"
#include "stackWalker.h"
#include "symbols.h"
#include "vmStructs.h"
// Ancient fcntl.h does not define F_SETOWN_EX constants and structures
@@ -65,18 +59,7 @@ enum {
};
static int fetchInt(const char* file_name) {
int fd = open(file_name, O_RDONLY);
if (fd == -1) {
return 0;
}
char num[16] = "0";
ssize_t r = read(fd, num, sizeof(num) - 1);
(void) r;
close(fd);
return atoi(num);
}
static const unsigned long PERF_PAGE_SIZE = sysconf(_SC_PAGESIZE);
// Get perf_event_attr.config numeric value of the given tracepoint name
// by reading /sys/kernel/debug/tracing/events/<name>/id file
@@ -88,94 +71,16 @@ static int findTracepointId(const char* name) {
*strchr(buf, ':') = '/'; // make path from event name
return fetchInt(buf);
}
// Get perf_event_attr.type for the given event source
// by reading /sys/bus/event_source/devices/<name>/type
static int findDeviceType(const char* name) {
char buf[256];
if ((size_t)snprintf(buf, sizeof(buf), "/sys/bus/event_source/devices/%s/type", name) >= sizeof(buf)) {
return 0;
}
return fetchInt(buf);
}
// Convert pmu/event-name/ to pmu/param1=N,param2=M/
static void resolvePmuEventName(const char* device, char* event, size_t size) {
char buf[256];
if ((size_t)snprintf(buf, sizeof(buf), "/sys/bus/event_source/devices/%s/events/%s", device, event) >= sizeof(buf)) {
return;
}
int fd = open(buf, O_RDONLY);
if (fd == -1) {
return;
}
ssize_t r = read(fd, event, size);
if (r > 0 && (r == size || event[r - 1] == '\n')) {
event[r - 1] = 0;
}
close(fd);
}
// Set a PMU parameter (such as umask) to the corresponding config field
static bool setPmuConfig(const char* device, const char* param, __u64* config, __u64 val) {
char buf[256];
if ((size_t)snprintf(buf, sizeof(buf), "/sys/bus/event_source/devices/%s/format/%s", device, param) >= sizeof(buf)) {
return false;
}
int fd = open(buf, O_RDONLY);
if (fd == -1) {
return false;
}
ssize_t r = read(fd, buf, sizeof(buf));
close(fd);
if (r > 0 && r < sizeof(buf)) {
if (strncmp(buf, "config:", 7) == 0) {
config[0] |= val << atoi(buf + 7);
return true;
} else if (strncmp(buf, "config1:", 8) == 0) {
config[1] |= val << atoi(buf + 8);
return true;
} else if (strncmp(buf, "config2:", 8) == 0) {
config[2] |= val << atoi(buf + 8);
return true;
}
}
return false;
}
static const void** _pthread_entry = NULL;
// Intercept thread creation/termination by patching libjvm's GOT entry for pthread_setspecific().
// HotSpot puts VMThread into TLS on thread start, and resets on thread end.
static int pthread_setspecific_hook(pthread_key_t key, const void* value) {
if (key != VMThread::key()) {
return pthread_setspecific(key, value);
}
if (pthread_getspecific(key) == value) {
return 0;
}
if (value != NULL) {
int result = pthread_setspecific(key, value);
PerfEvents::createForThread(OS::threadId());
return result;
} else {
PerfEvents::destroyForThread(OS::threadId());
return pthread_setspecific(key, value);
}
}
static const void** lookupThreadEntry() {
CodeCache* lib = Profiler::instance()->findJvmLibrary("libj9thr");
return lib != NULL ? lib->findGlobalOffsetEntry((const void*)&pthread_setspecific) : NULL;
char id[16] = "0";
ssize_t r = read(fd, id, sizeof(id) - 1);
(void) r;
close(fd);
return atoi(id);
}
@@ -189,25 +94,13 @@ struct PerfEventType {
long default_interval;
__u32 type;
__u64 config;
__u64 config1;
__u64 config2;
__u32 bp_type;
__u32 bp_len;
int counter_arg;
enum {
IDX_PREDEFINED = 12,
IDX_RAW,
IDX_PMU,
IDX_BREAKPOINT,
IDX_TRACEPOINT,
IDX_KPROBE,
IDX_UPROBE,
};
static PerfEventType AVAILABLE_EVENTS[];
static FunctionWithCounter KNOWN_FUNCTIONS[];
static char probe_func[256];
// Find which argument of a known function serves as a profiling counter,
// e.g. the first argument of malloc() is allocation size
static int findCounterArg(const char* name) {
@@ -219,22 +112,22 @@ struct PerfEventType {
return 0;
}
// Breakpoint format: func[+offset][/len][:rwx][{arg}]
static PerfEventType* findByType(__u32 type) {
for (PerfEventType* event = AVAILABLE_EVENTS; ; event++) {
if (event->type == type) {
return event;
}
}
}
// Breakpoint format: func[+offset][/len][:rwx]
static PerfEventType* getBreakpoint(const char* name, __u32 bp_type, __u32 bp_len) {
char buf[256];
strncpy(buf, name, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = 0;
// Parse counter arg [{arg}]
int counter_arg = 0;
char* c = strrchr(buf, '{');
if (c != NULL && c[1] >= '1' && c[1] <= '9') {
*c++ = 0;
counter_arg = atoi(c);
}
// Parse access type [:rwx]
c = strrchr(buf, ':');
char* c = strrchr(buf, ':');
if (c != NULL && c != name && c[-1] != ':') {
*c++ = 0;
if (strcmp(c, "r") == 0) {
@@ -271,11 +164,7 @@ struct PerfEventType {
} else {
addr = (__u64)(uintptr_t)dlsym(RTLD_DEFAULT, buf);
if (addr == 0) {
addr = (__u64)(uintptr_t)Profiler::instance()->resolveSymbol(buf);
}
if (c == NULL) {
// If offset is not specified explicitly, add the default breakpoint offset
offset = BREAKPOINT_OFFSET;
addr = (__u64)(uintptr_t)Profiler::_instance.resolveSymbol(buf);
}
}
@@ -283,113 +172,21 @@ struct PerfEventType {
return NULL;
}
PerfEventType* breakpoint = &AVAILABLE_EVENTS[IDX_BREAKPOINT];
breakpoint->config = bp_type;
breakpoint->config1 = addr + offset;
breakpoint->config2 = bp_len;
breakpoint->counter_arg = bp_type == HW_BREAKPOINT_X && counter_arg == 0 ? findCounterArg(buf) : counter_arg;
PerfEventType* breakpoint = findByType(PERF_TYPE_BREAKPOINT);
breakpoint->config = addr + offset;
breakpoint->bp_type = bp_type;
breakpoint->bp_len = bp_len;
breakpoint->counter_arg = bp_type == HW_BREAKPOINT_X ? findCounterArg(buf) : 0;
return breakpoint;
}
static PerfEventType* getTracepoint(int tracepoint_id) {
PerfEventType* tracepoint = &AVAILABLE_EVENTS[IDX_TRACEPOINT];
PerfEventType* tracepoint = findByType(PERF_TYPE_TRACEPOINT);
tracepoint->config = tracepoint_id;
return tracepoint;
}
static PerfEventType* getProbe(PerfEventType* probe, const char* type, const char* name, __u64 ret) {
strncpy(probe_func, name, sizeof(probe_func) - 1);
probe_func[sizeof(probe_func) - 1] = 0;
if (probe->type == 0 && (probe->type = findDeviceType(type)) == 0) {
return NULL;
}
long long offset = 0;
char* c = strrchr(probe_func, '+');
if (c != NULL) {
*c++ = 0;
offset = strtoll(c, NULL, 0);
}
probe->config = ret;
probe->config1 = (__u64)(uintptr_t)probe_func;
probe->config2 = offset;
return probe;
}
static PerfEventType* getRawEvent(__u64 config) {
PerfEventType* raw = &AVAILABLE_EVENTS[IDX_RAW];
raw->config = config;
return raw;
}
static PerfEventType* getPmuEvent(const char* name) {
char buf[256];
strncpy(buf, name, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = 0;
char* descriptor = strchr(buf, '/');
*descriptor++ = 0;
descriptor[strlen(descriptor) - 1] = 0;
PerfEventType* raw = &AVAILABLE_EVENTS[IDX_PMU];
if ((raw->type = findDeviceType(buf)) == 0) {
return NULL;
}
// pmu/rNNN/
if (descriptor[0] == 'r' && descriptor[1] >= '0') {
char* end;
raw->config = strtoull(descriptor + 1, &end, 16);
if (*end == 0) {
return raw;
}
}
// Resolve event name to the list of parameters
resolvePmuEventName(buf, descriptor, sizeof(buf) - (descriptor - buf));
raw->config = 0;
raw->config1 = 0;
raw->config2 = 0;
// Parse parameters
while (descriptor != NULL && descriptor[0]) {
char* p = descriptor;
if ((descriptor = strchr(p, ',')) != NULL || (descriptor = strchr(p, ':')) != NULL) {
*descriptor++ = 0;
}
__u64 val = 1;
char* eq = strchr(p, '=');
if (eq != NULL) {
*eq++ = 0;
val = strtoull(eq, NULL, 0);
}
if (strcmp(p, "config") == 0) {
raw->config = val;
} else if (strcmp(p, "config1") == 0) {
raw->config1 = val;
} else if (strcmp(p, "config2") == 0) {
raw->config2 = val;
} else if (!setPmuConfig(buf, p, &raw->config, val)) {
return NULL;
}
}
return raw;
}
static PerfEventType* forName(const char* name) {
// Look through the table of predefined perf events
for (int i = 0; i < IDX_PREDEFINED; i++) {
if (strcmp(name, AVAILABLE_EVENTS[i].name) == 0) {
return &AVAILABLE_EVENTS[i];
}
}
// Hardware breakpoint
if (strncmp(name, "mem:", 4) == 0) {
return getBreakpoint(name + 4, HW_BREAKPOINT_RW, 1);
@@ -401,38 +198,16 @@ struct PerfEventType {
return tracepoint_id > 0 ? getTracepoint(tracepoint_id) : NULL;
}
// kprobe or uprobe
if (strncmp(name, "kprobe:", 7) == 0) {
return getProbe(&AVAILABLE_EVENTS[IDX_KPROBE], "kprobe", name + 7, 0);
}
if (strncmp(name, "uprobe:", 7) == 0) {
return getProbe(&AVAILABLE_EVENTS[IDX_UPROBE], "uprobe", name + 7, 0);
}
if (strncmp(name, "kretprobe:", 10) == 0) {
return getProbe(&AVAILABLE_EVENTS[IDX_KPROBE], "kprobe", name + 10, 1);
}
if (strncmp(name, "uretprobe:", 10) == 0) {
return getProbe(&AVAILABLE_EVENTS[IDX_UPROBE], "uprobe", name + 10, 1);
}
// Raw PMU register: rNNN
if (name[0] == 'r' && name[1] >= '0') {
char* end;
__u64 reg = strtoull(name + 1, &end, 16);
if (*end == 0) {
return getRawEvent(reg);
// Look through the table of predefined perf events
for (PerfEventType* event = AVAILABLE_EVENTS; event->name != NULL; event++) {
if (strcmp(name, event->name) == 0) {
return event;
}
}
// Raw perf event descriptor: pmu/event-descriptor/
const char* s = strchr(name, '/');
if (s > name && s[1] != 0 && s[strlen(s) - 1] == '/') {
return getPmuEvent(name);
}
// Kernel tracepoints defined in debugfs
s = strchr(name, ':');
if (s != NULL && s[1] != ':') {
const char* c = strchr(name, ':');
if (c != NULL && c[1] != ':') {
int tracepoint_id = findTracepointId(name);
if (tracepoint_id > 0) {
return getTracepoint(tracepoint_id);
@@ -457,7 +232,7 @@ PerfEventType PerfEventType::AVAILABLE_EVENTS[] = {
{"instructions", 1000000, PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS},
{"cache-references", 1000000, PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_REFERENCES},
{"cache-misses", 1000, PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_MISSES},
{"branch-instructions", 1000000, PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_INSTRUCTIONS},
{"branches", 1000000, PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_INSTRUCTIONS},
{"branch-misses", 1000, PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_MISSES},
{"bus-cycles", 1000000, PERF_TYPE_HARDWARE, PERF_COUNT_HW_BUS_CYCLES},
@@ -465,20 +240,15 @@ PerfEventType PerfEventType::AVAILABLE_EVENTS[] = {
{"LLC-load-misses", 1000, PERF_TYPE_HW_CACHE, LOAD_MISS(PERF_COUNT_HW_CACHE_LL)},
{"dTLB-load-misses", 1000, PERF_TYPE_HW_CACHE, LOAD_MISS(PERF_COUNT_HW_CACHE_DTLB)},
{"rNNN", 1000, PERF_TYPE_RAW, 0}, /* IDX_RAW */
{"pmu/event-descriptor/", 1000, PERF_TYPE_RAW, 0}, /* IDX_PMU */
{"mem:breakpoint", 1, PERF_TYPE_BREAKPOINT, 0},
{"trace:tracepoint", 1, PERF_TYPE_TRACEPOINT, 0},
{"mem:breakpoint", 1, PERF_TYPE_BREAKPOINT, 0}, /* IDX_BREAKPOINT */
{"trace:tracepoint", 1, PERF_TYPE_TRACEPOINT, 0}, /* IDX_TRACEPOINT */
{"kprobe:func", 1, 0, 0}, /* IDX_KPROBE */
{"uprobe:path", 1, 0, 0}, /* IDX_UPROBE */
{NULL}
};
FunctionWithCounter PerfEventType::KNOWN_FUNCTIONS[] = {
{"malloc", 1},
{"mmap", 2},
{"munmap", 2},
{"read", 3},
{"write", 3},
{"send", 3},
@@ -488,8 +258,6 @@ FunctionWithCounter PerfEventType::KNOWN_FUNCTIONS[] = {
{NULL}
};
char PerfEventType::probe_func[256];
class RingBuffer {
private:
@@ -498,21 +266,21 @@ class RingBuffer {
public:
RingBuffer(struct perf_event_mmap_page* page) {
_start = (const char*)page + OS::page_size;
_start = (const char*)page + PERF_PAGE_SIZE;
}
struct perf_event_header* seek(u64 offset) {
_offset = (unsigned long)offset & OS::page_mask;
_offset = (unsigned long)offset & (PERF_PAGE_SIZE - 1);
return (struct perf_event_header*)(_start + _offset);
}
u64 next() {
_offset = (_offset + sizeof(u64)) & OS::page_mask;
_offset = (_offset + sizeof(u64)) & (PERF_PAGE_SIZE - 1);
return *(u64*)(_start + _offset);
}
u64 peek(unsigned long words) {
unsigned long peek_offset = (_offset + words * sizeof(u64)) & OS::page_mask;
unsigned long peek_offset = (_offset + words * sizeof(u64)) & (PERF_PAGE_SIZE - 1);
return *(u64*)(_start + peek_offset);
}
};
@@ -533,22 +301,17 @@ PerfEventType* PerfEvents::_event_type = NULL;
long PerfEvents::_interval;
Ring PerfEvents::_ring;
CStack PerfEvents::_cstack;
bool PerfEvents::_print_extended_warning;
int PerfEvents::createForThread(int tid) {
bool PerfEvents::createForThread(int tid) {
if (tid >= _max_events) {
Log::warn("tid[%d] > pid_max[%d]. Restart profiler after changing pid_max", tid, _max_events);
return -1;
fprintf(stderr, "WARNING: tid[%d] > pid_max[%d]. Restart profiler after changing pid_max\n", tid, _max_events);
return false;
}
PerfEventType* event_type = _event_type;
if (event_type == NULL) {
return -1;
}
// Mark _events[tid] early to prevent duplicates. Real fd will be put later.
if (!__sync_bool_compare_and_swap(&_events[tid]._fd, 0, -1)) {
// Lost race. The event is created either from PerfEvents::start() or from pthread hook.
return -1;
return false;
}
struct perf_event_attr attr = {0};
@@ -556,12 +319,12 @@ int PerfEvents::createForThread(int tid) {
attr.type = event_type->type;
if (attr.type == PERF_TYPE_BREAKPOINT) {
attr.bp_type = event_type->config;
attr.bp_addr = event_type->config;
attr.bp_type = event_type->bp_type;
attr.bp_len = event_type->bp_len;
} else {
attr.config = event_type->config;
}
attr.config1 = event_type->config1;
attr.config2 = event_type->config2;
// Hardware events may not always support zero skid
if (attr.type == PERF_TYPE_SOFTWARE) {
@@ -579,10 +342,6 @@ int PerfEvents::createForThread(int tid) {
attr.exclude_user = 1;
}
if (_cstack == CSTACK_DWARF) {
attr.exclude_callchain_user = 1;
}
#ifdef PERF_ATTR_SIZE_VER5
if (_cstack == CSTACK_LBR) {
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK | PERF_SAMPLE_REGS_USER;
@@ -594,27 +353,31 @@ int PerfEvents::createForThread(int tid) {
#warning "Compiling without LBR support. Kernel headers 4.1+ required"
#endif
int fd;
if (FdTransferClient::hasPeer()) {
fd = FdTransferClient::requestPerfFd(&tid, &attr);
} else {
fd = syscall(__NR_perf_event_open, &attr, tid, -1, -1, 0);
}
int fd = syscall(__NR_perf_event_open, &attr, tid, -1, -1, 0);
if (fd == -1) {
int err = errno;
Log::warn("perf_event_open for TID %d failed: %s", tid, strerror(errno));
return err;
perror("perf_event_open failed");
if (err == EACCES && _print_extended_warning) {
fprintf(stderr, "Due to permission restrictions, you cannot collect kernel events.\n"
"Try with --all-user option, or 'echo 1 > /proc/sys/kernel/perf_event_paranoid'\n");
_print_extended_warning = false;
}
return false;
}
void* page = mmap(NULL, 2 * OS::page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (!__sync_bool_compare_and_swap(&_events[tid]._fd, 0, fd)) {
// Lost race. The event is created either from start() or from onThreadStart()
close(fd);
return false;
}
void* page = mmap(NULL, 2 * PERF_PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (page == MAP_FAILED) {
Log::warn("perf_event mmap failed: %s", strerror(errno));
perror("perf_event mmap failed");
page = NULL;
}
_events[tid].reset();
_events[tid]._fd = fd;
_events[tid]._page = (struct perf_event_mmap_page*)page;
struct f_owner_ex ex;
@@ -628,7 +391,7 @@ int PerfEvents::createForThread(int tid) {
ioctl(fd, PERF_EVENT_IOC_RESET, 0);
ioctl(fd, PERF_EVENT_IOC_REFRESH, 1);
return 0;
return true;
}
void PerfEvents::destroyForThread(int tid) {
@@ -638,31 +401,18 @@ void PerfEvents::destroyForThread(int tid) {
PerfEvent* event = &_events[tid];
int fd = event->_fd;
if (fd > 0 && __sync_bool_compare_and_swap(&event->_fd, fd, 0)) {
if (fd != 0 && __sync_bool_compare_and_swap(&event->_fd, fd, 0)) {
ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
close(fd);
}
if (event->_page != NULL) {
event->lock();
munmap(event->_page, 2 * OS::page_size);
munmap(event->_page, 2 * PERF_PAGE_SIZE);
event->_page = NULL;
event->unlock();
}
}
u64 PerfEvents::readCounter(siginfo_t* siginfo, void* ucontext) {
switch (_event_type->counter_arg) {
case 1: return StackFrame(ucontext).arg0();
case 2: return StackFrame(ucontext).arg1();
case 3: return StackFrame(ucontext).arg2();
case 4: return StackFrame(ucontext).arg3();
default: {
u64 counter;
return read(siginfo->si_fd, &counter, sizeof(counter)) == sizeof(counter) ? counter : 1;
}
}
}
void PerfEvents::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
if (siginfo->si_code <= 0) {
// Looks like an external signal; don't treat as a profiling event
@@ -670,63 +420,41 @@ void PerfEvents::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
}
if (_enabled) {
u64 counter = readCounter(siginfo, ucontext);
ExecutionEvent event;
Profiler::instance()->recordSample(ucontext, counter, 0, &event);
} else {
resetBuffer(OS::threadId());
}
ioctl(siginfo->si_fd, PERF_EVENT_IOC_RESET, 0);
ioctl(siginfo->si_fd, PERF_EVENT_IOC_REFRESH, 1);
}
void PerfEvents::signalHandlerJ9(int signo, siginfo_t* siginfo, void* ucontext) {
if (siginfo->si_code <= 0) {
// Looks like an external signal; don't treat as a profiling event
return;
}
if (_enabled) {
u64 counter = readCounter(siginfo, ucontext);
J9StackTraceNotification notif;
notif.num_frames = _cstack == CSTACK_NO ? 0 : walk(OS::threadId(), notif.addr, MAX_J9_NATIVE_FRAMES);
if (_cstack == CSTACK_DWARF) {
notif.num_frames += StackWalker::walkDwarf(ucontext, notif.addr + notif.num_frames, MAX_J9_NATIVE_FRAMES - notif.num_frames);
u64 counter;
switch (_event_type->counter_arg) {
case 1: counter = StackFrame(ucontext).arg0(); break;
case 2: counter = StackFrame(ucontext).arg1(); break;
case 3: counter = StackFrame(ucontext).arg2(); break;
case 4: counter = StackFrame(ucontext).arg3(); break;
default:
if (read(siginfo->si_fd, &counter, sizeof(counter)) != sizeof(counter)) {
counter = 1;
}
}
J9StackTraces::checkpoint(counter, &notif);
} else {
resetBuffer(OS::threadId());
ExecutionEvent event;
Profiler::_instance.recordSample(ucontext, counter, 0, &event);
}
ioctl(siginfo->si_fd, PERF_EVENT_IOC_RESET, 0);
ioctl(siginfo->si_fd, PERF_EVENT_IOC_REFRESH, 1);
}
const char* PerfEvents::title() {
if (_event_type == NULL || _event_type->name == EVENT_CPU) {
return "CPU profile";
} else if (_event_type->type == PERF_TYPE_SOFTWARE || _event_type->type == PERF_TYPE_HARDWARE || _event_type->type == PERF_TYPE_HW_CACHE) {
return _event_type->name;
} else {
return "Flame Graph";
}
}
const char* PerfEvents::units() {
return _event_type == NULL || _event_type->name == EVENT_CPU ? "ns" : "total";
if (_event_type == NULL || _event_type->name == EVENT_CPU) {
return "ns";
} else if (_event_type->type == PERF_TYPE_BREAKPOINT || _event_type->type == PERF_TYPE_TRACEPOINT) {
return "events";
}
const char* dash = strrchr(_event_type->name, '-');
return dash != NULL ? dash + 1 : _event_type->name;
}
Error PerfEvents::check(Arguments& args) {
PerfEventType* event_type = PerfEventType::forName(args._event);
PerfEventType* event_type = PerfEventType::forName(args._event_desc);
if (event_type == NULL) {
return Error("Unsupported event type");
} else if (event_type->counter_arg > 4) {
return Error("Only arguments 1-4 can be counted");
}
if (_pthread_entry == NULL && (_pthread_entry = lookupThreadEntry()) == NULL) {
return Error("Could not set pthread hook");
}
struct perf_event_attr attr = {0};
@@ -734,12 +462,12 @@ Error PerfEvents::check(Arguments& args) {
attr.type = event_type->type;
if (attr.type == PERF_TYPE_BREAKPOINT) {
attr.bp_type = event_type->config;
attr.bp_addr = event_type->config;
attr.bp_type = event_type->bp_type;
attr.bp_len = event_type->bp_len;
} else {
attr.config = event_type->config;
}
attr.config1 = event_type->config1;
attr.config2 = event_type->config2;
attr.sample_period = event_type->default_interval;
attr.sample_type = PERF_SAMPLE_CALLCHAIN;
@@ -750,14 +478,10 @@ Error PerfEvents::check(Arguments& args) {
} else if (args._ring == RING_KERNEL) {
attr.exclude_user = 1;
} else if (!Symbols::haveKernelSymbols()) {
Profiler::instance()->updateSymbols(true);
Profiler::_instance.updateSymbols(true);
attr.exclude_kernel = Symbols::haveKernelSymbols() ? 0 : 1;
}
if (_cstack == CSTACK_DWARF) {
attr.exclude_callchain_user = 1;
}
#ifdef PERF_ATTR_SIZE_VER5
if (args._cstack == CSTACK_LBR) {
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK | PERF_SAMPLE_REGS_USER;
@@ -777,15 +501,9 @@ Error PerfEvents::check(Arguments& args) {
}
Error PerfEvents::start(Arguments& args) {
_event_type = PerfEventType::forName(args._event);
_event_type = PerfEventType::forName(args._event_desc);
if (_event_type == NULL) {
return Error("Unsupported event type");
} else if (_event_type->counter_arg > 4) {
return Error("Only arguments 1-4 can be counted");
}
if (_pthread_entry == NULL && (_pthread_entry = lookupThreadEntry()) == NULL) {
return Error("Could not set pthread hook");
}
if (args._interval < 0) {
@@ -795,12 +513,13 @@ Error PerfEvents::start(Arguments& args) {
_ring = args._ring;
if (_ring != RING_USER && !Symbols::haveKernelSymbols()) {
Log::warn("Kernel symbols are unavailable due to restrictions. Try\n"
" sysctl kernel.kptr_restrict=0\n"
" sysctl kernel.perf_event_paranoid=1");
fprintf(stderr, "WARNING: Kernel symbols are unavailable due to restrictions. Try\n"
" echo 0 > /proc/sys/kernel/kptr_restrict\n"
" echo 1 > /proc/sys/kernel/perf_event_paranoid\n");
_ring = RING_USER;
}
_cstack = args._cstack;
_print_extended_warning = _ring != RING_USER;
int max_events = OS::getMaxThreadId();
if (max_events != _max_events) {
@@ -809,52 +528,34 @@ Error PerfEvents::start(Arguments& args) {
_max_events = max_events;
}
if (VM::isOpenJ9()) {
if (_cstack == CSTACK_DEFAULT) _cstack = CSTACK_DWARF;
OS::installSignalHandler(SIGPROF, signalHandlerJ9);
Error error = J9StackTraces::start(args);
if (error) {
return error;
}
} else {
OS::installSignalHandler(SIGPROF, signalHandler);
}
OS::installSignalHandler(SIGPROF, signalHandler);
// Enable pthread hook before traversing currently running threads
__atomic_store_n(_pthread_entry, (void*)pthread_setspecific_hook, __ATOMIC_RELEASE);
// Enable thread events before traversing currently running threads
Profiler::_instance.switchThreadEvents(JVMTI_ENABLE);
// Create perf_events for all existing threads
int err;
bool created = false;
ThreadList* thread_list = OS::listThreads();
for (int tid; (tid = thread_list->next()) != -1; ) {
if ((err = createForThread(tid)) == 0) {
created = true;
}
created |= createForThread(tid);
}
delete thread_list;
if (!created) {
__atomic_store_n(_pthread_entry, (void*)pthread_setspecific, __ATOMIC_RELEASE);
J9StackTraces::stop();
if (err == EACCES || err == EPERM) {
return Error("No access to perf events. Try --fdtransfer or --all-user option or 'sysctl kernel.perf_event_paranoid=1'");
} else {
return Error("Perf events unavailable");
}
Profiler::_instance.switchThreadEvents(JVMTI_DISABLE);
return Error("Perf events unavailable. See stderr of the target process.");
}
return Error::OK;
}
void PerfEvents::stop() {
__atomic_store_n(_pthread_entry, (void*)pthread_setspecific, __ATOMIC_RELEASE);
for (int i = 0; i < _max_events; i++) {
destroyForThread(i);
}
J9StackTraces::stop();
}
int PerfEvents::walk(int tid, const void** callchain, int max_depth) {
int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
CodeCache* java_methods, CodeCache* runtime_stubs) {
PerfEvent* event = &_events[tid];
if (!event->tryLock()) {
return 0; // the event is being destroyed
@@ -878,7 +579,7 @@ int PerfEvents::walk(int tid, const void** callchain, int max_depth) {
u64 ip = ring.next();
if (ip < PERF_CONTEXT_MAX) {
const void* iptr = (const void*)ip;
if (CodeHeap::contains(iptr) || depth >= max_depth) {
if (java_methods->contains(iptr) || runtime_stubs->contains(iptr) || depth >= max_depth) {
// Stop at the first Java frame
goto stack_complete;
}
@@ -891,7 +592,7 @@ int PerfEvents::walk(int tid, const void** callchain, int max_depth) {
// Last userspace PC is stored right after branch stack
const void* pc = (const void*)ring.peek(bnr * 3 + 2);
if (CodeHeap::contains(pc) || depth >= max_depth) {
if (java_methods->contains(pc) || runtime_stubs->contains(pc) || depth >= max_depth) {
goto stack_complete;
}
callchain[depth++] = pc;
@@ -901,12 +602,12 @@ int PerfEvents::walk(int tid, const void** callchain, int max_depth) {
const void* to = (const void*)ring.next();
ring.next();
if (CodeHeap::contains(to) || depth >= max_depth) {
if (java_methods->contains(to) || runtime_stubs->contains(to) || depth >= max_depth) {
goto stack_complete;
}
callchain[depth++] = to;
if (CodeHeap::contains(from) || depth >= max_depth) {
if (java_methods->contains(from) || runtime_stubs->contains(from) || depth >= max_depth) {
goto stack_complete;
}
callchain[depth++] = from;
@@ -926,22 +627,6 @@ stack_complete:
return depth;
}
void PerfEvents::resetBuffer(int tid) {
PerfEvent* event = &_events[tid];
if (!event->tryLock()) {
return; // the event is being destroyed
}
struct perf_event_mmap_page* page = event->_page;
if (page != NULL) {
u64 head = page->data_head;
rmb();
page->data_tail = head;
}
event->unlock();
}
bool PerfEvents::supported() {
// The official way of knowing if perf_event_open() support is enabled
// is checking for the existence of the file /proc/sys/kernel/perf_event_paranoid

View File

@@ -24,24 +24,14 @@ PerfEvent* PerfEvents::_events;
PerfEventType* PerfEvents::_event_type;
long PerfEvents::_interval;
Ring PerfEvents::_ring;
bool PerfEvents::_print_extended_warning;
u64 PerfEvents::readCounter(siginfo_t* siginfo, void* ucontext) {
return 0;
}
void PerfEvents::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
}
void PerfEvents::signalHandlerJ9(int signo, siginfo_t* siginfo, void* ucontext) {
}
const char* PerfEvents::title() {
return Engine::title();
}
const char* PerfEvents::units() {
return Engine::units();
return "ns";
}
Error PerfEvents::check(Arguments& args) {
@@ -55,13 +45,11 @@ Error PerfEvents::start(Arguments& args) {
void PerfEvents::stop() {
}
int PerfEvents::walk(int tid, const void** callchain, int max_depth) {
int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
CodeCache* java_methods, CodeCache* runtime_stubs) {
return 0;
}
void PerfEvents::resetBuffer(int tid) {
}
bool PerfEvents::supported() {
return false;
}
@@ -70,8 +58,8 @@ const char* PerfEvents::getEventName(int event_id) {
return NULL;
}
int PerfEvents::createForThread(int tid) {
return -1;
bool PerfEvents::createForThread(int tid) {
return false;
}
void PerfEvents::destroyForThread(int tid) {

File diff suppressed because it is too large Load Diff

View File

@@ -28,7 +28,6 @@
#include "engine.h"
#include "event.h"
#include "flightRecorder.h"
#include "log.h"
#include "mutex.h"
#include "spinLock.h"
#include "threadFilter.h"
@@ -46,6 +45,14 @@ const int MAX_NATIVE_LIBS = 2048;
const int CONCURRENCY_LEVEL = 16;
enum AddressType {
ADDR_UNKNOWN,
ADDR_JIT,
ADDR_STUB,
ADDR_NATIVE
};
union CallTraceBuffer {
ASGCT_CallFrame _asgct_frames[1];
jvmtiFrameInfo _jvmti_frames[1];
@@ -55,7 +62,6 @@ union CallTraceBuffer {
class FrameName;
enum State {
NEW,
IDLE,
RUNNING,
TERMINATED
@@ -77,11 +83,8 @@ class Profiler {
CallTraceStorage _call_trace_storage;
FlightRecorder _jfr;
Engine* _engine;
int _event_mask;
int _events;
time_t _start_time;
volatile bool _timer_is_running;
pthread_t _timer_thread;
u64 _total_samples;
u64 _failures[ASGCT_FAILURE_TYPES];
@@ -92,24 +95,38 @@ class Profiler {
int _safe_mode;
CStack _cstack;
bool _add_thread_frame;
bool _add_sched_frame;
bool _update_thread_names;
volatile bool _thread_events_state;
SpinLock _jit_lock;
SpinLock _stubs_lock;
CodeCache _runtime_stubs;
CodeCache* _native_libs[MAX_NATIVE_LIBS];
CodeCache _java_methods;
NativeCodeCache _runtime_stubs;
NativeCodeCache* _native_libs[MAX_NATIVE_LIBS];
volatile int _native_lib_count;
// dlopen() hook support
const void** _dlopen_entry;
static void* dlopen_hook(const char* filename, int flags);
void switchLibraryTrap(bool enable);
// Support for intercepting NativeLibrary.load() / NativeLibraries.load()
JNINativeMethod _load_method;
void* _original_NativeLibrary_load;
void* _trapped_NativeLibrary_load;
static jboolean JNICALL NativeLibraryLoadTrap(JNIEnv* env, jobject self, jstring name, jboolean builtin);
static jboolean JNICALL NativeLibrariesLoadTrap(JNIEnv* env, jobject self, jobject lib, jstring name, jboolean builtin, jboolean jni);
void bindNativeLibraryLoad(JNIEnv* env, bool enable);
// Support for intercepting Thread.setNativeName()
void* _original_Thread_setNativeName;
static void JNICALL ThreadSetNativeNameTrap(JNIEnv* env, jobject self, jstring name);
void bindThreadSetNativeName(JNIEnv* env, bool enable);
void switchNativeMethodTraps(bool enable);
Error installTraps(const char* begin, const char* end);
void uninstallTraps();
static void trapHandler(int signo, siginfo_t* siginfo, void* ucontext);
void trapHandlerImpl(void* ucontext);
void addJavaMethod(const void* address, int length, jmethodID method);
void removeJavaMethod(const void* address, jmethodID method);
void addRuntimeStub(const void* address, int length, const char* name);
void onThreadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread);
@@ -117,15 +134,12 @@ class Profiler {
const char* asgctError(int code);
u32 getLockIndex(int tid);
bool inJavaCode(void* ucontext);
bool isAddressInCode(const void* pc);
int getNativeTrace(void* ucontext, ASGCT_CallFrame* frames, int event_type, int tid);
int getNativeTrace(void* ucontext, ASGCT_CallFrame* frames, int tid);
int getJavaTraceAsync(void* ucontext, ASGCT_CallFrame* frames, int max_depth);
int getJavaTraceJvmti(jvmtiFrameInfo* jvmti_frames, ASGCT_CallFrame* frames, int start_depth, int max_depth);
int getJavaTraceInternal(jvmtiFrameInfo* jvmti_frames, ASGCT_CallFrame* frames, int max_depth);
int convertFrames(jvmtiFrameInfo* jvmti_frames, ASGCT_CallFrame* frames, int num_frames);
int getJavaTraceJvmti(jvmtiFrameInfo* jvmti_frames, ASGCT_CallFrame* frames, int max_depth);
int makeEventFrame(ASGCT_CallFrame* frames, jint event_type, uintptr_t id);
bool fillTopFrame(const void* pc, ASGCT_CallFrame* frame, bool* is_entry_frame);
bool fillTopFrame(const void* pc, ASGCT_CallFrame* frame);
AddressType getAddressType(instruction_t* pc);
void setThreadInfo(int tid, const char* name, jlong java_thread_id);
void updateThreadName(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread);
void updateJavaThreadNames();
@@ -133,104 +147,83 @@ class Profiler {
bool excludeTrace(FrameName* fn, CallTrace* trace);
void mangle(const char* name, char* buf, size_t size);
Engine* selectEngine(const char* event_name);
Engine* allocEngine();
Engine* activeEngine();
Error checkJvmCapabilities();
time_t addTimeout(time_t start, int timeout);
void startTimer(int timeout);
void stopTimer();
void timerLoop(int timeout);
static void* timerThreadEntry(void* arg);
void lockAll();
void unlockAll();
void dumpCollapsed(std::ostream& out, Arguments& args);
void dumpFlameGraph(std::ostream& out, Arguments& args, bool tree);
void dumpText(std::ostream& out, Arguments& args);
static Profiler* const _instance;
public:
static Profiler _instance;
Profiler() :
_state(NEW),
_begin_trap(2),
_end_trap(3),
_state(IDLE),
_begin_trap(),
_end_trap(),
_thread_filter(),
_call_trace_storage(),
_jfr(),
_start_time(0),
_timer_is_running(false),
_max_stack_depth(0),
_safe_mode(0),
_thread_events_state(JVMTI_DISABLE),
_jit_lock(),
_stubs_lock(),
_java_methods(),
_runtime_stubs("[stubs]"),
_native_lib_count(0),
_dlopen_entry(NULL) {
_original_NativeLibrary_load(NULL) {
for (int i = 0; i < CONCURRENCY_LEVEL; i++) {
_calltrace_buffer[i] = NULL;
}
}
static Profiler* instance() {
return _instance;
}
u64 total_samples() { return _total_samples; }
time_t uptime() { return time(NULL) - _start_time; }
Dictionary* classMap() { return &_class_map; }
ThreadFilter* threadFilter() { return &_thread_filter; }
Error run(Arguments& args);
Error runInternal(Arguments& args, std::ostream& out);
Error restart(Arguments& args);
void run(Arguments& args);
void runInternal(Arguments& args, std::ostream& out);
void shutdown(Arguments& args);
Error check(Arguments& args);
Error start(Arguments& args, bool reset);
Error stop();
Error flushJfr();
Error dump(std::ostream& out, Arguments& args);
void switchThreadEvents(jvmtiEventMode mode);
int convertNativeTrace(int native_frames, const void** callchain, ASGCT_CallFrame* frames);
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 recordExternalSample(u64 counter, int tid, int num_frames, ASGCT_CallFrame* frames);
void writeLog(LogLevel level, const char* message);
void writeLog(LogLevel level, const char* message, size_t len);
void updateSymbols(bool kernel_symbols);
const void* resolveSymbol(const char* name);
const char* getLibraryName(const char* native_symbol);
CodeCache* findJvmLibrary(const char* lib_name);
CodeCache* findNativeLibrary(const void* address);
NativeCodeCache* findNativeLibrary(const void* address);
const char* findNativeMethod(const void* address);
void trapHandler(int signo, siginfo_t* siginfo, void* ucontext);
static void segvHandler(int signo, siginfo_t* siginfo, void* ucontext);
static void setupSignalHandlers();
// CompiledMethodLoad is also needed to enable DebugNonSafepoints info by default
static void JNICALL CompiledMethodLoad(jvmtiEnv* jvmti, jmethodID method,
jint code_size, const void* code_addr,
jint map_length, const jvmtiAddrLocationMap* map,
const void* compile_info) {
instance()->addJavaMethod(code_addr, code_size, method);
_instance.addJavaMethod(code_addr, code_size, method);
}
static void JNICALL CompiledMethodUnload(jvmtiEnv* jvmti, jmethodID method,
const void* code_addr) {
_instance.removeJavaMethod(code_addr, method);
}
static void JNICALL DynamicCodeGenerated(jvmtiEnv* jvmti, const char* name,
const void* address, jint length) {
instance()->addRuntimeStub(address, length, name);
_instance.addRuntimeStub(address, length, name);
}
static void JNICALL ThreadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
instance()->onThreadStart(jvmti, jni, thread);
_instance.onThreadStart(jvmti, jni, thread);
}
static void JNICALL ThreadEnd(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
instance()->onThreadEnd(jvmti, jni, thread);
_instance.onThreadEnd(jvmti, jni, thread);
}
friend class Recording;

View File

@@ -1,56 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _SAFEACCESS_H
#define _SAFEACCESS_H
#include <stdint.h>
#include "arch.h"
#ifdef __clang__
# define NOINLINE __attribute__((noinline))
#else
# define NOINLINE __attribute__((noinline,noclone))
#endif
class SafeAccess {
public:
NOINLINE __attribute__((aligned(16)))
static void* load(void** ptr) {
return *ptr;
}
static uintptr_t skipFaultInstruction(uintptr_t pc) {
if (pc - (uintptr_t)load < 16) {
#if defined(__x86_64__)
return *(u16*)pc == 0x8b48 ? 3 : 0; // mov rax, [reg]
#elif defined(__i386__)
return *(u8*)pc == 0x8b ? 2 : 0; // mov eax, [reg]
#elif defined(__arm__) || defined(__thumb__)
return (*(instruction_t*)pc & 0x0e50f000) == 0x04100000 ? 4 : 0; // ldr r0, [reg]
#elif defined(__aarch64__)
return (*(instruction_t*)pc & 0xffc0001f) == 0xf9400000 ? 4 : 0; // ldr x0, [reg]
#else
return sizeof(instruction_t);
#endif
}
return 0;
}
};
#endif // _SAFEACCESS_H

View File

@@ -51,19 +51,9 @@ class SpinLock {
__sync_fetch_and_sub(&_lock, 1);
}
bool tryLockShared() {
int value;
while ((value = _lock) <= 0) {
if (__sync_bool_compare_and_swap(&_lock, value, value - 1)) {
return true;
}
}
return false;
}
void lockShared() {
int value;
while ((value = _lock) > 0 || !__sync_bool_compare_and_swap(&_lock, value, value - 1)) {
while ((value = _lock) == 1 || !__sync_bool_compare_and_swap(&_lock, value, value - 1)) {
spinPause();
}
}

View File

@@ -55,7 +55,7 @@ class StackFrame {
uintptr_t& sp();
uintptr_t& fp();
uintptr_t& retval();
uintptr_t retval();
uintptr_t arg0();
uintptr_t arg1();
uintptr_t arg2();
@@ -71,6 +71,10 @@ class StackFrame {
// 0 = do not use stack snooping heuristics.
static int callerLookupSlots();
// Check if PC looks like a valid return address (i.e. the previous instruction is a CALL).
// It's safe to return false to skip return address heuristics.
static bool isReturnAddress(instruction_t* pc);
// Check if PC points to a syscall instruction
static bool isSyscall(instruction_t* pc);
};

View File

@@ -1,5 +1,6 @@
/*
* Copyright 2021 Andrei Pangin
* Copyright 2017 Andrei Pangin
* Copyright 2017 BellSoft LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -12,107 +13,85 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Author: Dmitry Samersoff
*/
#ifdef __aarch64__
#if defined(__aarch64__)
#include <errno.h>
#include <sys/syscall.h>
#include "stackFrame.h"
#ifdef __APPLE__
# define REG(l, m) _ucontext->uc_mcontext->__ss.__##m
#else
# define REG(l, m) _ucontext->uc_mcontext.l
#endif
#define REG_FP 29
#define REG_LR 30
uintptr_t& StackFrame::pc() {
return (uintptr_t&)REG(pc, pc);
return (uintptr_t&)_ucontext->uc_mcontext.pc;
}
uintptr_t& StackFrame::sp() {
return (uintptr_t&)REG(sp, sp);
return (uintptr_t&)_ucontext->uc_mcontext.sp;
}
uintptr_t& StackFrame::fp() {
return (uintptr_t&)REG(regs[29], fp);
return (uintptr_t&)_ucontext->uc_mcontext.regs[REG_FP];
}
uintptr_t& StackFrame::retval() {
return (uintptr_t&)REG(regs[0], x[0]);
uintptr_t StackFrame::retval() {
return (uintptr_t)_ucontext->uc_mcontext.regs[0];
}
uintptr_t StackFrame::arg0() {
return (uintptr_t)REG(regs[0], x[0]);
return (uintptr_t)_ucontext->uc_mcontext.regs[0];
}
uintptr_t StackFrame::arg1() {
return (uintptr_t)REG(regs[1], x[1]);
return (uintptr_t)_ucontext->uc_mcontext.regs[1];
}
uintptr_t StackFrame::arg2() {
return (uintptr_t)REG(regs[2], x[2]);
return (uintptr_t)_ucontext->uc_mcontext.regs[2];
}
uintptr_t StackFrame::arg3() {
return (uintptr_t)REG(regs[3], x[3]);
return (uintptr_t)_ucontext->uc_mcontext.regs[3];
}
void StackFrame::ret() {
pc() = REG(regs[30], lr);
_ucontext->uc_mcontext.pc = _ucontext->uc_mcontext.regs[REG_LR];
}
bool StackFrame::pop(bool trust_frame_pointer) {
if (trust_frame_pointer && withinCurrentStack(fp())) {
sp() = fp() + 16;
fp() = stackAt(-2);
pc() = stackAt(-1);
return true;
} else if (fp() == sp()) {
if (fp() == sp()) {
// Expected frame layout:
// sp 000000nnnnnnnnnn [stack]
// sp+8 000000nnnnnnnnnn [link]
fp() = stackAt(0);
pc() = stackAt(1);
sp() += 16;
return true;
} else {
// Hope LR holds correct value
pc() = _ucontext->uc_mcontext.regs[REG_LR];
}
instruction_t insn = *(instruction_t*)pc();
if ((insn & 0xffe07fff) == 0xa9007bfd) {
// stp x29, x30, [sp, #offset]
// SP has been adjusted, but FP not yet stored in a new frame
unsigned int offset = (insn >> 12) & 0x1f8;
sp() += offset + 16;
}
ret();
return true;
}
bool StackFrame::checkInterruptedSyscall() {
#ifdef __APPLE__
// We are not interested in syscalls that do not check error code, e.g. semaphore_wait_trap
if (*(instruction_t*)pc() == 0xd65f03c0) {
return true;
}
// If carry flag is set, the error code is in low byte of x0
if (REG(pstate, cpsr) & (1 << 29)) {
return (retval() & 0xff) == EINTR || (retval() & 0xff) == ETIMEDOUT;
} else {
return retval() == (uintptr_t)-EINTR;
}
#else
return retval() == (uintptr_t)-EINTR;
#endif
}
int StackFrame::callerLookupSlots() {
return 0;
}
bool StackFrame::isSyscall(instruction_t* pc) {
// svc #0 or svc #80
return (*pc & 0xffffefff) == 0xd4000001;
bool StackFrame::isReturnAddress(instruction_t* pc) {
return false;
}
#endif // __aarch64__
bool StackFrame::isSyscall(instruction_t* pc) {
// svc #0
return *pc == 0xd4000001;
}
#endif // defined(__aarch64__)

View File

@@ -32,8 +32,8 @@ uintptr_t& StackFrame::fp() {
return (uintptr_t&)_ucontext->uc_mcontext.arm_fp;
}
uintptr_t& StackFrame::retval() {
return (uintptr_t&)_ucontext->uc_mcontext.arm_r0;
uintptr_t StackFrame::retval() {
return (uintptr_t)_ucontext->uc_mcontext.arm_r0;
}
uintptr_t StackFrame::arg0() {
@@ -68,6 +68,10 @@ int StackFrame::callerLookupSlots() {
return 0;
}
bool StackFrame::isReturnAddress(instruction_t* pc) {
return false;
}
bool StackFrame::isSyscall(instruction_t* pc) {
// swi #0
return *pc == 0xef000000;

View File

@@ -32,8 +32,8 @@ uintptr_t& StackFrame::fp() {
return (uintptr_t&)_ucontext->uc_mcontext.gregs[REG_EBP];
}
uintptr_t& StackFrame::retval() {
return (uintptr_t&)_ucontext->uc_mcontext.gregs[REG_EAX];
uintptr_t StackFrame::retval() {
return (uintptr_t)_ucontext->uc_mcontext.gregs[REG_EAX];
}
uintptr_t StackFrame::arg0() {
@@ -80,6 +80,17 @@ int StackFrame::callerLookupSlots() {
return 7;
}
bool StackFrame::isReturnAddress(instruction_t* pc) {
if (pc[-5] == 0xe8) {
// call rel32
return true;
} else if (pc[-2] == 0xff && ((pc[-1] & 0xf0) == 0xd0 || (pc[-1] & 0xf0) == 0x10)) {
// call reg or call [reg]
return true;
}
return false;
}
bool StackFrame::isSyscall(instruction_t* pc) {
// int 0x80
return pc[0] == 0xcd && pc[1] == 0x80;

View File

@@ -1,132 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Authors: Andrei Pangin and Gunter Haug
*/
#if defined(__PPC64__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
#include <errno.h>
#include <signal.h>
#include "stackFrame.h"
uintptr_t& StackFrame::pc() {
return (uintptr_t&)_ucontext->uc_mcontext.regs->nip;
}
uintptr_t& StackFrame::sp() {
return (uintptr_t&)_ucontext->uc_mcontext.regs->gpr[1];
}
uintptr_t& StackFrame::fp() {
return *((uintptr_t*)_ucontext->uc_mcontext.regs->gpr[1]);
}
uintptr_t& StackFrame::retval() {
return (uintptr_t&)_ucontext->uc_mcontext.regs->gpr[3];
}
uintptr_t StackFrame::arg0() {
return (uintptr_t)_ucontext->uc_mcontext.regs->gpr[3];
}
uintptr_t StackFrame::arg1() {
return (uintptr_t)_ucontext->uc_mcontext.regs->gpr[4];
}
uintptr_t StackFrame::arg2() {
return (uintptr_t)_ucontext->uc_mcontext.regs->gpr[5];
}
uintptr_t StackFrame::arg3() {
return (uintptr_t)_ucontext->uc_mcontext.regs->gpr[6];
}
void StackFrame::ret() {
_ucontext->uc_mcontext.regs->nip = _ucontext->uc_mcontext.regs->link;
}
static inline bool inC1EpilogueCrit(uintptr_t pc) {
if (!(pc & 0xfff)) {
// Make sure we are not at the page boundary, so that reading [pc - 1] is safe
return false;
}
// C1 epilogue and critical section (posX)
// 3821**** add r1,r1,xx
// pos3 xxxxxxxx
// pos2 1000e1eb ld r31,16(r1)
// pos1 a603e87f mtlr r31
// xxxxxxxx
// 2000804e blr
instruction_t* inst = (instruction_t*)pc;
if (inst[ 1] == 0xebe10010 && inst[2] == 0x7fe803a6 ||
inst[ 0] == 0xebe10010 && inst[1] == 0x7fe803a6 ||
inst[-1] == 0xebe10010 && inst[0] == 0x7fe803a6) {
return true;
}
return false; // not in critical section
}
static inline bool inC2PrologueCrit(uintptr_t pc) {
// C2 prologue and critical section
// f821**** stdu r1, (xx)r1
// pos1 fa950010 std r20,16(r21)
instruction_t* inst = (instruction_t*)pc;
if (inst[0] == 0xfa950010 && (inst[-1] & 0xffff0000) == 0xf8210000) {
return true;
}
return false; // not in critical section
}
bool StackFrame::pop(bool trust_frame_pointer) {
// On PPC there is a valid back link to the previous frame at all times. The callee stores
// the return address in the caller's frame before it constructs its own frame. After it
// has destroyed its frame it restores the link register and returns. A problematic sequence
// is the prologue/epilogue of a compiled method before/after frame construction/destruction.
// Therefore popping the frame would not help here, as it is not yet/anymore present, rather
// more adjusting the pc to the callers pc does the trick. There are two exceptions to this,
// One in the prologue of C2 compiled methods and one in the epilogue of C1 compiled methods.
if (inC1EpilogueCrit(pc())) {
// lr not yet set: use the value stored in the frame
pc() = stackAt(2);
} else if (inC2PrologueCrit(pc())) {
// frame constructed but lr not yet stored in it: just do it here
*(((unsigned long *) _ucontext->uc_mcontext.regs->gpr[21]) + 2) = (unsigned long) _ucontext->uc_mcontext.regs->gpr[20];
} else {
// most probably caller's framer is still on top but pc is already in callee: use caller's pc
pc() = _ucontext->uc_mcontext.regs->link;
}
return true;
}
bool StackFrame::checkInterruptedSyscall() {
return retval() == (uintptr_t)-EINTR;
}
int StackFrame::callerLookupSlots() {
return 0;
}
bool StackFrame::isSyscall(instruction_t* pc) {
// sc/svc
return (*pc & 0x1f) == 17;
}
#endif // defined(__PPC64__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)

View File

@@ -22,42 +22,42 @@
#ifdef __APPLE__
# define REG(l, m) _ucontext->uc_mcontext->__ss.__##m
# define REG(l, m) _ucontext->uc_mcontext->__ss.m
#else
# define REG(l, m) _ucontext->uc_mcontext.gregs[REG_##l]
# define REG(l, m) _ucontext->uc_mcontext.gregs[l]
#endif
uintptr_t& StackFrame::pc() {
return (uintptr_t&)REG(RIP, rip);
return (uintptr_t&)REG(REG_RIP, __rip);
}
uintptr_t& StackFrame::sp() {
return (uintptr_t&)REG(RSP, rsp);
return (uintptr_t&)REG(REG_RSP, __rsp);
}
uintptr_t& StackFrame::fp() {
return (uintptr_t&)REG(RBP, rbp);
return (uintptr_t&)REG(REG_RBP, __rbp);
}
uintptr_t& StackFrame::retval() {
return (uintptr_t&)REG(RAX, rax);
uintptr_t StackFrame::retval() {
return (uintptr_t)REG(REG_RAX, __rax);
}
uintptr_t StackFrame::arg0() {
return (uintptr_t)REG(RDI, rdi);
return (uintptr_t)REG(REG_RDI, __rdi);
}
uintptr_t StackFrame::arg1() {
return (uintptr_t)REG(RSI, rsi);
return (uintptr_t)REG(REG_RSI, __rsi);
}
uintptr_t StackFrame::arg2() {
return (uintptr_t)REG(RDX, rdx);
return (uintptr_t)REG(REG_RDX, __rdx);
}
uintptr_t StackFrame::arg3() {
return (uintptr_t)REG(RCX, rcx);
return (uintptr_t)REG(REG_RCX, __rcx);
}
void StackFrame::ret() {
@@ -113,7 +113,7 @@ bool StackFrame::checkInterruptedSyscall() {
}
// If CF is set, the error code is in low byte of eax,
// some other syscalls (ulock_wait) do not set CF when interrupted
if (REG(EFL, rflags) & 1) {
if (REG(REG_EFL, __rflags) & 1) {
return (retval() & 0xff) == EINTR || (retval() & 0xff) == ETIMEDOUT;
} else {
return retval() == (uintptr_t)-EINTR;
@@ -122,7 +122,7 @@ bool StackFrame::checkInterruptedSyscall() {
if (retval() == (uintptr_t)-EINTR) {
// Workaround for JDK-8237858: restart the interrupted poll() manually.
// Check if the previous instruction is mov eax, SYS_poll with infinite timeout
if ((int)arg2() == -1) {
if (arg2() == (uintptr_t)-1) {
uintptr_t pc = this->pc();
if ((pc & 0xfff) >= 7 && *(unsigned char*)(pc - 7) == 0xb8 && *(int*)(pc - 6) == SYS_poll) {
this->pc() = pc - 7;
@@ -138,6 +138,17 @@ int StackFrame::callerLookupSlots() {
return 7;
}
bool StackFrame::isReturnAddress(instruction_t* pc) {
if (pc[-5] == 0xe8) {
// call rel32
return true;
} else if (pc[-2] == 0xff && ((pc[-1] & 0xf0) == 0xd0 || (pc[-1] & 0xf0) == 0x10)) {
// call reg or call [reg]
return true;
}
return false;
}
bool StackFrame::isSyscall(instruction_t* pc) {
return pc[0] == 0x0f && pc[1] == 0x05;
}

View File

@@ -1,142 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "stackWalker.h"
#include "dwarf.h"
#include "profiler.h"
#include "safeAccess.h"
#include "stackFrame.h"
#include "vmStructs.h"
const intptr_t MIN_VALID_PC = 0x1000;
const intptr_t MAX_WALK_SIZE = 0x100000;
const intptr_t MAX_FRAME_SIZE = 0x40000;
int StackWalker::walkFP(void* ucontext, const void** callchain, int max_depth) {
const void* pc;
uintptr_t fp;
uintptr_t prev_fp = (uintptr_t)&fp;
uintptr_t bottom = prev_fp + MAX_WALK_SIZE;
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;
// Walk until the bottom of the stack or until the first Java frame
while (depth < max_depth && !CodeHeap::contains(pc)) {
callchain[depth++] = pc;
// Check if the next frame is below on the current stack
if (fp <= prev_fp || fp >= prev_fp + MAX_FRAME_SIZE || fp >= bottom) {
break;
}
// Frame pointer must be word aligned
if ((fp & (sizeof(uintptr_t) - 1)) != 0) {
break;
}
pc = stripPointer(SafeAccess::load((void**)fp + FRAME_PC_SLOT));
if (pc < (const void*)MIN_VALID_PC || pc > (const void*)-MIN_VALID_PC) {
break;
}
prev_fp = fp;
fp = *(uintptr_t*)fp;
}
return depth;
}
int StackWalker::walkDwarf(void* ucontext, const void** callchain, int max_depth) {
const void* pc;
uintptr_t fp;
uintptr_t sp;
uintptr_t prev_sp;
uintptr_t bottom = (uintptr_t)&sp + MAX_WALK_SIZE;
if (ucontext == NULL) {
pc = __builtin_return_address(0);
fp = (uintptr_t)__builtin_frame_address(1);
sp = (uintptr_t)__builtin_frame_address(0);
} else {
StackFrame frame(ucontext);
pc = (const void*)frame.pc();
fp = frame.fp();
sp = frame.sp();
}
int depth = 0;
Profiler* profiler = Profiler::instance();
// Walk until the bottom of the stack or until the first Java frame
while (depth < max_depth && !CodeHeap::contains(pc)) {
callchain[depth++] = pc;
prev_sp = sp;
FrameDesc* f;
CodeCache* cc = profiler->findNativeLibrary(pc);
if (cc == NULL || (f = cc->findFrameDesc(pc)) == NULL) {
f = &FrameDesc::default_frame;
}
u8 cfa_reg = (u8)f->cfa;
int cfa_off = f->cfa >> 8;
if (cfa_reg == DW_REG_SP) {
sp = sp + cfa_off;
} else if (cfa_reg == DW_REG_FP) {
sp = fp + cfa_off;
} else if (cfa_reg == DW_REG_PLT) {
sp += ((uintptr_t)pc & 15) >= 11 ? cfa_off * 2 : cfa_off;
} else {
break;
}
// Check if the next frame is below on the current stack
if (sp < prev_sp || sp >= prev_sp + MAX_FRAME_SIZE || sp >= bottom) {
break;
}
// Stack pointer must be word aligned
if ((sp & (sizeof(uintptr_t) - 1)) != 0) {
break;
}
if (f->fp_off & DW_PC_OFFSET) {
pc = (const char*)pc + (f->fp_off >> 1);
} else {
if (f->fp_off != DW_SAME_FP && f->fp_off < MAX_FRAME_SIZE && f->fp_off > -MAX_FRAME_SIZE) {
fp = (uintptr_t)SafeAccess::load((void**)(sp + f->fp_off));
}
pc = stripPointer(SafeAccess::load((void**)sp - 1));
}
if (pc < (const void*)MIN_VALID_PC || pc > (const void*)-MIN_VALID_PC) {
break;
}
}
return depth;
}

View File

@@ -1,27 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _STACKWALKER_H
#define _STACKWALKER_H
class StackWalker {
public:
static int walkFP(void* ucontext, const void** callchain, int max_depth);
static int walkDwarf(void* ucontext, const void** callchain, int max_depth);
};
#endif // _STACKWALKER_H

View File

@@ -29,10 +29,8 @@ class Symbols {
static bool _have_kernel_symbols;
public:
static void parseKernelSymbols(CodeCache* cc);
static void parseLibraries(CodeCache** array, volatile int& count, int size, bool kernel_symbols);
static void makePatchable(CodeCache* cc);
static void parseKernelSymbols(NativeCodeCache* cc);
static void parseLibraries(NativeCodeCache** array, volatile int& count, int size, bool kernel_symbols);
static bool haveKernelSymbols() {
return _have_kernel_symbols;

View File

@@ -27,12 +27,11 @@
#include <unistd.h>
#include <fcntl.h>
#include <linux/limits.h>
#include <fstream>
#include <iostream>
#include <string>
#include "symbols.h"
#include "arch.h"
#include "dwarf.h"
#include "fdtransferClient.h"
#include "log.h"
#include "os.h"
class SymbolDesc {
@@ -106,13 +105,13 @@ typedef Elf32_Rel ElfRelocation;
class ElfParser {
private:
CodeCache* _cc;
NativeCodeCache* _cc;
const char* _base;
const char* _file_name;
ElfHeader* _header;
const char* _sections;
ElfParser(CodeCache* cc, const char* base, const void* addr, const char* file_name = NULL) {
ElfParser(NativeCodeCache* cc, const char* base, const void* addr, const char* file_name = NULL) {
_cc = cc;
_base = base;
_file_name = file_name;
@@ -144,8 +143,8 @@ class ElfParser {
void addRelocationSymbols(ElfSection* reltab, const char* plt);
public:
static bool parseFile(CodeCache* cc, const char* base, const char* file_name, bool use_debug);
static void parseMem(CodeCache* cc, const char* base);
static bool parseFile(NativeCodeCache* cc, const char* base, const char* file_name, bool use_debug);
static void parseMem(NativeCodeCache* cc, const char* base);
};
@@ -154,7 +153,7 @@ ElfSection* ElfParser::findSection(uint32_t type, const char* name) {
for (int i = 0; i < _header->e_shnum; i++) {
ElfSection* section = this->section(i);
if ((type == 0 || section->sh_type == type) && section->sh_name != 0) {
if (section->sh_type == type && section->sh_name != 0) {
if (strcmp(strtab + section->sh_name, name) == 0) {
return section;
}
@@ -164,7 +163,7 @@ ElfSection* ElfParser::findSection(uint32_t type, const char* name) {
return NULL;
}
bool ElfParser::parseFile(CodeCache* cc, const char* base, const char* file_name, bool use_debug) {
bool ElfParser::parseFile(NativeCodeCache* cc, const char* base, const char* file_name, bool use_debug) {
int fd = open(file_name, O_RDONLY);
if (fd == -1) {
return false;
@@ -175,7 +174,12 @@ bool ElfParser::parseFile(CodeCache* cc, const char* base, const char* file_name
close(fd);
if (addr == MAP_FAILED) {
Log::warn("Could not parse symbols from %s: %s", file_name, strerror(errno));
if (strcmp(file_name, "/") == 0) {
// https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1843018
fprintf(stderr, "Could not parse symbols due to the OS bug\n");
} else {
fprintf(stderr, "Could not parse symbols from %s: %s\n", file_name, strerror(errno));
}
} else {
ElfParser elf(cc, base, addr, file_name);
elf.loadSymbols(use_debug);
@@ -184,7 +188,7 @@ bool ElfParser::parseFile(CodeCache* cc, const char* base, const char* file_name
return true;
}
void ElfParser::parseMem(CodeCache* cc, const char* base) {
void ElfParser::parseMem(NativeCodeCache* cc, const char* base) {
ElfParser elf(cc, base, base);
elf.loadSymbols(false);
}
@@ -215,10 +219,8 @@ void ElfParser::loadSymbols(bool use_debug) {
}
loaded:
// Synthesize names for PLT stubs
if (use_debug) {
_cc->setTextBase(_base);
// Synthesize names for PLT stubs
ElfSection* plt = findSection(SHT_PROGBITS, ".plt");
ElfSection* reltab = findSection(SHT_RELA, ".rela.plt");
if (reltab == NULL) {
@@ -227,21 +229,6 @@ loaded:
if (plt != NULL && reltab != NULL) {
addRelocationSymbols(reltab, _base + plt->sh_offset + PLT_HEADER_SIZE);
}
// Find the bounds of the Global Offset Table
ElfSection* got = findSection(SHT_PROGBITS, ".got.plt");
if (got != NULL
|| (got = findSection(SHT_NOBITS, ".plt")) != NULL /* ppc64le binaries */
|| (got = findSection(SHT_PROGBITS, ".got")) != NULL /* RELRO technique */) {
_cc->setGlobalOffsetTable(_base + got->sh_addr, got->sh_size);
}
// Read DWARF unwind info
ElfSection* eh_frame_hdr = findSection(0, ".eh_frame_hdr");
if (eh_frame_hdr != NULL && DWARF_SUPPORTED) {
DwarfParser dwarf(_cc->name(), (const char*)_header, at(eh_frame_hdr));
_cc->setDwarfTable(dwarf.table(), dwarf.count());
}
}
}
@@ -320,10 +307,7 @@ void ElfParser::loadSymbolTable(ElfSection* symtab) {
for (; symbols < symbols_end; symbols += symtab->sh_entsize) {
ElfSymbol* sym = (ElfSymbol*)symbols;
if (sym->st_name != 0 && sym->st_value != 0) {
// Skip special AArch64 mapping symbols: $x and $d
if (sym->st_size != 0 || sym->st_info != 0 || strings[sym->st_name] != '$') {
_cc->add(_base + sym->st_value, (int)sym->st_size, strings + sym->st_name);
}
_cc->add(_base + sym->st_value, (int)sym->st_size, strings + sym->st_name);
}
}
}
@@ -360,32 +344,13 @@ Mutex Symbols::_parse_lock;
std::set<const void*> Symbols::_parsed_libraries;
bool Symbols::_have_kernel_symbols = false;
void Symbols::parseKernelSymbols(CodeCache* cc) {
int fd;
if (FdTransferClient::hasPeer()) {
fd = FdTransferClient::requestKallsymsFd();
} else {
fd = open("/proc/kallsyms", O_RDONLY);
}
void Symbols::parseKernelSymbols(NativeCodeCache* cc) {
std::ifstream maps("/proc/kallsyms");
std::string str;
if (fd == -1) {
Log::warn("open(\"/proc/kallsyms\"): %s", strerror(errno));
return;
}
FILE* f = fdopen(fd, "r");
if (f == NULL) {
Log::warn("fdopen(): %s", strerror(errno));
close(fd);
return;
}
char str[256];
while (fgets(str, sizeof(str) - 8, f) != NULL) {
size_t len = strlen(str) - 1; // trim the '\n'
strcpy(str + len, "_[k]");
SymbolDesc symbol(str);
while (std::getline(maps, str)) {
str += "_[k]";
SymbolDesc symbol(str.c_str());
char type = symbol.type();
if (type == 'T' || type == 't' || type == 'W' || type == 'w') {
const char* addr = symbol.addr();
@@ -395,15 +360,13 @@ void Symbols::parseKernelSymbols(CodeCache* cc) {
}
}
}
fclose(f);
}
void Symbols::parseLibraries(CodeCache** array, volatile int& count, int size, bool kernel_symbols) {
void Symbols::parseLibraries(NativeCodeCache** array, volatile int& count, int size, bool kernel_symbols) {
MutexLocker ml(_parse_lock);
if (kernel_symbols && !haveKernelSymbols()) {
CodeCache* cc = new CodeCache("[kernel]");
NativeCodeCache* cc = new NativeCodeCache("[kernel]");
parseKernelSymbols(cc);
if (haveKernelSymbols()) {
@@ -415,26 +378,18 @@ void Symbols::parseLibraries(CodeCache** array, volatile int& count, int size, b
}
}
FILE* f = fopen("/proc/self/maps", "r");
if (f == NULL) {
return;
}
std::ifstream maps("/proc/self/maps");
std::string str;
char* str = NULL;
size_t str_size = 0;
ssize_t len;
while (count < size && (len = getline(&str, &str_size, f)) > 0) {
str[len - 1] = 0;
MemoryMapDesc map(str);
while (count < size && std::getline(maps, str)) {
MemoryMapDesc map(str.c_str());
if (map.isExecutable() && map.file() != NULL && map.file()[0] != 0) {
const char* image_base = map.addr();
if (!_parsed_libraries.insert(image_base).second) {
continue; // the library was already parsed
}
CodeCache* cc = new CodeCache(map.file(), count, image_base, map.end());
NativeCodeCache* cc = new NativeCodeCache(map.file(), image_base, map.end());
if (map.inode() != 0) {
ElfParser::parseFile(cc, image_base - map.offs(), map.file(), true);
@@ -447,15 +402,6 @@ void Symbols::parseLibraries(CodeCache** array, volatile int& count, int size, b
atomicInc(count);
}
}
free(str);
fclose(f);
}
void Symbols::makePatchable(CodeCache* cc) {
uintptr_t got_start = (uintptr_t)cc->gotStart() & ~OS::page_mask;
uintptr_t got_size = ((uintptr_t)cc->gotEnd() - got_start + OS::page_mask) & ~OS::page_mask;
mprotect((void*)got_start, got_size, PROT_READ | PROT_WRITE);
}
#endif // __linux__

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2021 Andrei Pangin
* Copyright 2017 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,96 +16,113 @@
#ifdef __APPLE__
#include <dlfcn.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <arpa/inet.h>
#include <mach-o/dyld.h>
#include <mach-o/loader.h>
#include <mach-o/fat.h>
#include <mach-o/nlist.h>
#include "symbols.h"
#include "arch.h"
#include "log.h"
class MachOParser {
private:
CodeCache* _cc;
NativeCodeCache* _cc;
const mach_header* _image_base;
static const char* add(const void* base, uint64_t offset) {
static const char* add(const void* base, uint32_t offset) {
return (const char*)base + offset;
}
void findGlobalOffsetTable(const segment_command_64* sc) {
const section_64* section = (const section_64*)add(sc, sizeof(segment_command_64));
for (uint32_t i = 0; i < sc->nsects; i++) {
if (strcmp(section->sectname, "__la_symbol_ptr") == 0) {
_cc->setGlobalOffsetTable(add(_image_base, section->addr), section->size);
break;
}
section++;
}
}
void loadSymbols(const symtab_command* symtab, const char* text_base, const char* link_base) {
const nlist_64* sym = (const nlist_64*)add(link_base, symtab->symoff);
const char* str_table = add(link_base, symtab->stroff);
void loadSymbols(mach_header_64* header, symtab_command* symtab) {
nlist_64* symbol_table = (nlist_64*)add(header, symtab->symoff);
const char* str_table = add(header, symtab->stroff);
for (uint32_t i = 0; i < symtab->nsyms; i++) {
if ((sym->n_type & 0xee) == 0x0e && sym->n_value != 0) {
const char* addr = text_base + sym->n_value;
const char* name = str_table + sym->n_un.n_strx;
nlist_64 sym = symbol_table[i];
if ((sym.n_type & 0xee) == 0x0e && sym.n_value != 0) {
const char* addr = add(_image_base, sym.n_value);
const char* name = str_table + sym.n_un.n_strx;
if (name[0] == '_') name++;
_cc->add(addr, 0, name);
}
sym++;
}
}
void parseMachO(mach_header_64* header) {
load_command* lc = (load_command*)(header + 1);
for (uint32_t i = 0; i < header->ncmds; i++) {
if (lc->cmd == LC_SYMTAB) {
loadSymbols(header, (symtab_command*)lc);
break;
}
lc = (load_command*)add(lc, lc->cmdsize);
}
}
void parseFatObject(fat_header* header) {
int narch = header->nfat_arch;
fat_arch* arch = (fat_arch*)(header + 1);
for (uint32_t i = 0; i < narch; i++) {
if (arch[i].cputype == _image_base->cputype &&
arch[i].cpusubtype == _image_base->cpusubtype) {
parseMachO((mach_header_64*)add(header, arch[i].offset));
}
}
}
// The same as parseFatObject, but fields are big-endian
void parseFatObjectBE(fat_header* header) {
int narch = htonl(header->nfat_arch);
fat_arch* arch = (fat_arch*)(header + 1);
for (uint32_t i = 0; i < narch; i++) {
if (htonl(arch[i].cputype) == _image_base->cputype &&
htonl(arch[i].cpusubtype) == _image_base->cpusubtype) {
parseMachO((mach_header_64*)add(header, htonl(arch[i].offset)));
}
}
}
void parse(mach_header* header) {
uint32_t magic = header->magic;
if (magic == MH_MAGIC_64) {
parseMachO((mach_header_64*)header);
} else if (magic == FAT_MAGIC) {
parseFatObject((fat_header*)header);
} else if (magic == FAT_CIGAM) {
parseFatObjectBE((fat_header*)header);
}
}
public:
MachOParser(CodeCache* cc, const mach_header* image_base) : _cc(cc), _image_base(image_base) {
MachOParser(NativeCodeCache* cc, const mach_header* image_base) : _cc(cc), _image_base(image_base) {
}
bool parse() {
if (_image_base->magic != MH_MAGIC_64) {
return false;
static void parseFile(NativeCodeCache* cc, const mach_header* image_base, const char* file_name) {
int fd = open(file_name, O_RDONLY);
if (fd == -1) {
return;
}
const mach_header_64* header = (const mach_header_64*)_image_base;
const load_command* lc = (const load_command*)(header + 1);
size_t length = (size_t)lseek(fd, 0, SEEK_END);
void* addr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
const char* UNDEFINED = (const char*)-1;
const char* text_base = UNDEFINED;
const char* link_base = UNDEFINED;
for (uint32_t i = 0; i < header->ncmds; i++) {
if (lc->cmd == LC_SEGMENT_64) {
const segment_command_64* sc = (const segment_command_64*)lc;
if ((sc->initprot & 4) != 0) {
if (text_base == UNDEFINED || strcmp(sc->segname, "__TEXT") == 0) {
text_base = (const char*)_image_base - sc->vmaddr;
_cc->setTextBase(text_base);
_cc->updateBounds(_image_base, add(_image_base, sc->vmsize));
}
} else if ((sc->initprot & 7) == 1) {
if (link_base == UNDEFINED || strcmp(sc->segname, "__LINKEDIT") == 0) {
link_base = text_base + sc->vmaddr - sc->fileoff;
}
} else if ((sc->initprot & 2) != 0) {
if (strcmp(sc->segname, "__DATA") == 0) {
findGlobalOffsetTable(sc);
}
}
} else if (lc->cmd == LC_SYMTAB) {
if (text_base == UNDEFINED || link_base == UNDEFINED) {
return false;
}
loadSymbols((const symtab_command*)lc, text_base, link_base);
break;
}
lc = (const load_command*)add(lc, lc->cmdsize);
if (addr == MAP_FAILED) {
fprintf(stderr, "Could not parse symbols from %s: %s\n", file_name, strerror(errno));
} else {
MachOParser parser(cc, image_base);
parser.parse((mach_header*)addr);
munmap(addr, length);
}
return true;
}
};
@@ -114,33 +131,23 @@ Mutex Symbols::_parse_lock;
std::set<const void*> Symbols::_parsed_libraries;
bool Symbols::_have_kernel_symbols = false;
void Symbols::parseKernelSymbols(CodeCache* cc) {
void Symbols::parseKernelSymbols(NativeCodeCache* cc) {
}
void Symbols::parseLibraries(CodeCache** array, volatile int& count, int size, bool kernel_symbols) {
void Symbols::parseLibraries(NativeCodeCache** array, volatile int& count, int size, bool kernel_symbols) {
MutexLocker ml(_parse_lock);
uint32_t images = _dyld_image_count();
for (uint32_t i = 0; i < images && count < size; i++) {
const mach_header* image_base = _dyld_get_image_header(i);
if (image_base == NULL || !_parsed_libraries.insert(image_base).second) {
if (!_parsed_libraries.insert(image_base).second) {
continue; // the library was already parsed
}
const char* path = _dyld_get_image_name(i);
// Protect the library from unloading while parsing symbols
void* handle = dlopen(path, RTLD_LAZY | RTLD_NOLOAD);
if (handle == NULL) {
continue;
}
CodeCache* cc = new CodeCache(path, count);
MachOParser parser(cc, image_base);
if (!parser.parse()) {
Log::warn("Could not parse symbols from %s", path);
}
dlclose(handle);
NativeCodeCache* cc = new NativeCodeCache(path);
MachOParser::parseFile(cc, image_base, path);
cc->sort();
array[count] = cc;
@@ -148,8 +155,4 @@ void Symbols::parseLibraries(CodeCache** array, volatile int& count, int size, b
}
}
void Symbols::makePatchable(CodeCache* cc) {
// Global Offset Table is always writable
}
#endif // __APPLE__

View File

@@ -14,62 +14,50 @@
* limitations under the License.
*/
#include <unistd.h>
#include <sys/mman.h>
#include "trap.h"
#include "os.h"
uintptr_t Trap::_page_start[TRAP_COUNT] = {0};
bool Trap::isFaultInstruction(uintptr_t pc) {
for (int i = 0; i < TRAP_COUNT; i++) {
if (pc - _page_start[i] < OS::page_size) {
return true;
}
bool Trap::assign(const void* address) {
uintptr_t entry = (uintptr_t)address;
if (entry == 0) {
_entry = 0;
return true;
}
return false;
}
void Trap::assign(const void* address, uintptr_t offset) {
_entry = (uintptr_t)address;
if (_entry == 0) {
return;
}
_entry += offset;
#if defined(__arm__) || defined(__thumb__)
_breakpoint_insn = (_entry & 1) ? BREAKPOINT_THUMB : BREAKPOINT;
_entry &= ~(uintptr_t)1;
if (entry & 1) {
entry ^= 1;
_breakpoint_insn = BREAKPOINT_THUMB;
}
#endif
_saved_insn = *(instruction_t*)_entry;
_page_start[_id] = _entry & -OS::page_size;
}
// Two allocation traps are always enabled/disabled together.
// If both traps belong to the same page, protect/unprotect it just once.
void Trap::pair(Trap& second) {
if (_page_start[_id] == _page_start[second._id]) {
_protect = false;
second._unprotect = false;
}
}
// Patch instruction at the entry point
bool Trap::patch(instruction_t insn) {
if (_unprotect) {
int prot = WX_MEMORY ? (PROT_READ | PROT_WRITE) : (PROT_READ | PROT_WRITE | PROT_EXEC);
if (mprotect((void*)(_entry & -OS::page_size), OS::page_size, prot) != 0) {
if (entry != _entry) {
// Make the entry point writable, so we can rewrite instructions
long page_size = sysconf(_SC_PAGESIZE);
if (mprotect((void*)(entry & -page_size), page_size, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) {
return false;
}
_entry = entry;
_saved_insn = *(instruction_t*)entry;
}
*(instruction_t*)_entry = insn;
flushCache(_entry);
if (_protect) {
mprotect((void*)(_entry & -OS::page_size), OS::page_size, PROT_READ | PROT_EXEC);
}
return true;
}
// Insert breakpoint at the very first instruction
void Trap::install() {
if (_entry) {
*(instruction_t*)_entry = _breakpoint_insn;
flushCache(_entry);
}
}
// Clear breakpoint - restore the original instruction
void Trap::uninstall() {
if (_entry) {
*(instruction_t*)_entry = _saved_insn;
flushCache(_entry);
}
}

View File

@@ -21,24 +21,14 @@
#include "arch.h"
const int TRAP_COUNT = 4;
class Trap {
private:
int _id;
bool _unprotect;
bool _protect;
uintptr_t _entry;
instruction_t _breakpoint_insn;
instruction_t _saved_insn;
bool patch(instruction_t insn);
static uintptr_t _page_start[TRAP_COUNT];
public:
Trap(int id) : _id(id), _unprotect(true), _protect(WX_MEMORY), _entry(0), _breakpoint_insn(BREAKPOINT) {
Trap() : _entry(0), _breakpoint_insn(BREAKPOINT) {
}
uintptr_t entry() {
@@ -50,18 +40,9 @@ class Trap {
return pc - _entry <= sizeof(instruction_t);
}
void assign(const void* address, uintptr_t offset = BREAKPOINT_OFFSET);
void pair(Trap& second);
bool install() {
return _entry == 0 || patch(_breakpoint_insn);
}
bool uninstall() {
return _entry == 0 || patch(_saved_insn);
}
static bool isFaultInstruction(uintptr_t pc);
bool assign(const void* address);
void install();
void uninstall();
};
#endif // _TRAP_H

View File

@@ -1,51 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <jvmti.h>
#include "tsc.h"
#include "vmEntry.h"
bool TSC::_initialized = false;
bool TSC::_enabled = false;
u64 TSC::_offset = 0;
u64 TSC::_frequency = 1000000000;
void TSC::initialize() {
JNIEnv* env = VM::jni();
jfieldID jvm;
jmethodID getTicksFrequency, counterTime;
jclass cls = env->FindClass("jdk/jfr/internal/JVM");
if (cls != NULL
&& ((jvm = env->GetStaticFieldID(cls, "jvm", "Ljdk/jfr/internal/JVM;")) != NULL)
&& ((getTicksFrequency = env->GetMethodID(cls, "getTicksFrequency", "()J")) != NULL)
&& ((counterTime = env->GetStaticMethodID(cls, "counterTime", "()J")) != NULL)) {
u64 frequency = env->CallLongMethod(env->GetStaticObjectField(cls, jvm), getTicksFrequency);
if (frequency > 1000000000) {
// Default 1GHz frequency might mean that rdtsc is not available
u64 jvm_ticks = env->CallStaticLongMethod(cls, counterTime);
_offset = rdtsc() - jvm_ticks;
_frequency = frequency;
_enabled = true;
}
}
env->ExceptionClear();
_initialized = true;
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2021 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _TSC_H
#define _TSC_H
#include "os.h"
#if defined(__x86_64__)
#define TSC_SUPPORTED true
static inline u64 rdtsc() {
u32 lo, hi;
asm volatile("rdtsc" : "=a" (lo), "=d" (hi));
return ((u64)hi << 32) | lo;
}
#elif defined(__i386__)
#define TSC_SUPPORTED true
static inline u64 rdtsc() {
u64 result;
asm volatile("rdtsc" : "=A" (result));
return result;
}
#else
#define TSC_SUPPORTED false
#define rdtsc() 0
#endif
class TSC {
private:
static bool _initialized;
static bool _enabled;
static u64 _offset;
static u64 _frequency;
public:
static void initialize();
static bool initialized() {
return TSC_SUPPORTED ? _initialized : true;
}
static bool enabled() {
return TSC_SUPPORTED && _enabled;
}
static u64 ticks() {
return enabled() ? rdtsc() - _offset : OS::nanotime();
}
static u64 frequency() {
return _frequency;
}
};
#endif // _TSC_H

View File

@@ -14,139 +14,73 @@
* limitations under the License.
*/
#include <fstream>
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#include "vmEntry.h"
#include "arguments.h"
#include "j9Ext.h"
#include "javaApi.h"
#include "os.h"
#include "profiler.h"
#include "instrument.h"
#include "lockTracer.h"
#include "log.h"
#include "objectSampler.h"
#include "vmStructs.h"
// JVM TI agent return codes
const int ARGUMENTS_ERROR = 100;
const int COMMAND_ERROR = 200;
static Arguments _agent_args(true);
static Arguments _agent_args;
JavaVM* VM::_vm;
jvmtiEnv* VM::_jvmti = NULL;
int VM::_hotspot_version = 0;
bool VM::_openj9 = false;
jvmtiError (JNICALL *VM::_orig_RedefineClasses)(jvmtiEnv*, jint, const jvmtiClassDefinition*);
jvmtiError (JNICALL *VM::_orig_RetransformClasses)(jvmtiEnv*, jint, const jclass* classes);
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;
static void wakeupHandler(int signo) {
// Dummy handler for interrupting syscalls
}
static bool isZeroInterpreterMethod(const char* blob_name) {
return strncmp(blob_name, "_ZN15ZeroInterpreter", 20) == 0
|| strncmp(blob_name, "_ZN19BytecodeInterpreter3run", 28) == 0;
}
static bool isOpenJ9InterpreterMethod(const char* blob_name) {
return strncmp(blob_name, "_ZN32VM_BytecodeInterpreter", 27) == 0
|| strncmp(blob_name, "_ZN26VM_BytecodeInterpreter", 27) == 0
|| strncmp(blob_name, "bytecodeLoop", 12) == 0
|| strcmp(blob_name, "cInterpreter") == 0;
}
static bool isOpenJ9JitStub(const char* blob_name) {
if (strncmp(blob_name, "jit", 3) == 0) {
blob_name += 3;
return strcmp(blob_name, "NewObject") == 0
|| strcmp(blob_name, "NewArray") == 0
|| strcmp(blob_name, "ANewArray") == 0;
}
return false;
}
bool VM::init(JavaVM* vm, bool attach) {
if (_jvmti != NULL) return true;
void VM::init(JavaVM* vm, bool attach) {
if (_jvmti != NULL) return;
_vm = vm;
if (_vm->GetEnv((void**)&_jvmti, JVMTI_VERSION_1_0) != 0) {
return false;
}
_vm->GetEnv((void**)&_jvmti, JVMTI_VERSION_1_0);
#ifdef __APPLE__
Dl_info dl_info;
if (dladdr((const void*)wakeupHandler, &dl_info) && dl_info.dli_fname != NULL) {
// Make sure async-profiler DSO cannot be unloaded, since it contains JVM callbacks.
// On Linux, we use 'nodelete' linker option.
dlopen(dl_info.dli_fname, RTLD_LAZY | RTLD_NODELETE);
}
#endif
bool is_hotspot = false;
bool is_zero_vm = false;
char* prop;
if (_jvmti->GetSystemProperty("java.vm.name", &prop) == 0) {
is_hotspot = strstr(prop, "OpenJDK") != NULL ||
strstr(prop, "HotSpot") != NULL ||
strstr(prop, "GraalVM") != NULL ||
strstr(prop, "Dynamic Code Evolution") != NULL;
is_zero_vm = strstr(prop, "Zero") != NULL;
bool is_hotspot = strstr(prop, "OpenJDK") != NULL ||
strstr(prop, "HotSpot") != NULL ||
strstr(prop, "GraalVM") != NULL;
_jvmti->Deallocate((unsigned char*)prop);
}
if (is_hotspot && _jvmti->GetSystemProperty("java.vm.version", &prop) == 0) {
if (strncmp(prop, "25.", 3) == 0) {
_hotspot_version = 8;
} else if (strncmp(prop, "24.", 3) == 0) {
_hotspot_version = 7;
} else if (strncmp(prop, "20.", 3) == 0) {
_hotspot_version = 6;
} else if ((_hotspot_version = atoi(prop)) < 9) {
_hotspot_version = 9;
if (is_hotspot && _jvmti->GetSystemProperty("java.vm.version", &prop) == 0) {
if (strncmp(prop, "25.", 3) == 0) {
_hotspot_version = 8;
} else if (strncmp(prop, "24.", 3) == 0) {
_hotspot_version = 7;
} else if (strncmp(prop, "20.", 3) == 0) {
_hotspot_version = 6;
} else if ((_hotspot_version = atoi(prop)) < 9) {
_hotspot_version = 9;
}
_jvmti->Deallocate((unsigned char*)prop);
}
if (is_hotspot) {
JVMTIFunctions* functions = *(JVMTIFunctions**)_jvmti;
_orig_RedefineClasses = functions->RedefineClasses;
_orig_RetransformClasses = functions->RetransformClasses;
functions->RedefineClasses = RedefineClassesHook;
functions->RetransformClasses = RetransformClassesHook;
}
_jvmti->Deallocate((unsigned char*)prop);
}
_libjvm = getLibraryHandle("libjvm.so");
_asyncGetCallTrace = (AsyncGetCallTrace)dlsym(_libjvm, "AsyncGetCallTrace");
_getManagement = (JVM_GetManagement)dlsym(_libjvm, "JVM_GetManagement");
Profiler* profiler = Profiler::instance();
profiler->updateSymbols(false);
_openj9 = !is_hotspot && J9Ext::initialize(_jvmti, profiler->resolveSymbol("j9thread_self"));
CodeCache* lib = isOpenJ9()
? profiler->findJvmLibrary("libj9vm")
: profiler->findNativeLibrary((const void*)_asyncGetCallTrace);
if (lib == NULL) {
return false; // TODO: verify
}
VMStructs::init(lib);
if (is_zero_vm) {
lib->mark(isZeroInterpreterMethod);
} else if (isOpenJ9()) {
lib->mark(isOpenJ9InterpreterMethod);
CodeCache* libjit = profiler->findJvmLibrary("libj9jit");
if (libjit != NULL) {
libjit->mark(isOpenJ9JitStub);
}
}
if (attach) {
ready();
}
@@ -154,8 +88,7 @@ bool VM::init(JavaVM* vm, bool attach) {
jvmtiCapabilities capabilities = {0};
capabilities.can_generate_all_class_hook_events = 1;
capabilities.can_retransform_classes = 1;
capabilities.can_retransform_any_class = isOpenJ9() ? 0 : 1;
capabilities.can_generate_vm_object_alloc_events = isOpenJ9() ? 1 : 0;
capabilities.can_retransform_any_class = 1;
capabilities.can_get_bytecodes = 1;
capabilities.can_get_constant_pool = 1;
capabilities.can_get_source_file_name = 1;
@@ -172,61 +105,38 @@ bool VM::init(JavaVM* vm, bool attach) {
callbacks.ClassPrepare = ClassPrepare;
callbacks.ClassFileLoadHook = Instrument::ClassFileLoadHook;
callbacks.CompiledMethodLoad = Profiler::CompiledMethodLoad;
callbacks.CompiledMethodUnload = Profiler::CompiledMethodUnload;
callbacks.DynamicCodeGenerated = Profiler::DynamicCodeGenerated;
callbacks.ThreadStart = Profiler::ThreadStart;
callbacks.ThreadEnd = Profiler::ThreadEnd;
callbacks.MonitorContendedEnter = LockTracer::MonitorContendedEnter;
callbacks.MonitorContendedEntered = LockTracer::MonitorContendedEntered;
callbacks.VMObjectAlloc = ObjectSampler::VMObjectAlloc;
_jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));
_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL);
_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, NULL);
_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_LOAD, NULL);
_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_PREPARE, NULL);
_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_COMPILED_METHOD_LOAD, NULL);
_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_COMPILED_METHOD_UNLOAD, NULL);
_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_DYNAMIC_CODE_GENERATED, NULL);
if (hotspot_version() == 0 || !CodeHeap::available()) {
// Workaround for JDK-8173361: avoid CompiledMethodLoad events when possible
_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_COMPILED_METHOD_LOAD, NULL);
} else {
// DebugNonSafepoints is automatically enabled with CompiledMethodLoad,
// otherwise we set the flag manually
char* flag_addr = (char*)JVMFlag::find("DebugNonSafepoints");
if (flag_addr != NULL) {
*flag_addr = 1;
}
}
if (attach) {
loadAllMethodIDs(jvmti(), jni());
_jvmti->GenerateEvents(JVMTI_EVENT_DYNAMIC_CODE_GENERATED);
_jvmti->GenerateEvents(JVMTI_EVENT_COMPILED_METHOD_LOAD);
} else {
_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL);
}
OS::installSignalHandler(WAKEUP_SIGNAL, NULL, wakeupHandler);
return true;
}
// Run late initialization when JVM is ready
void VM::ready() {
{
JitWriteProtection jit(true);
VMStructs::ready();
Profiler::_instance.updateSymbols(false);
NativeCodeCache* libjvm = Profiler::_instance.findNativeLibrary((const void*)_asyncGetCallTrace);
if (libjvm != NULL) {
VMStructs::init(libjvm);
}
Profiler::setupSignalHandlers();
_libjava = getLibraryHandle("libjava.so");
// Make sure we reload method IDs upon class retransformation
JVMTIFunctions* functions = *(JVMTIFunctions**)_jvmti;
_orig_RedefineClasses = functions->RedefineClasses;
_orig_RetransformClasses = functions->RetransformClasses;
functions->RedefineClasses = RedefineClassesHook;
functions->RetransformClasses = RetransformClassesHook;
}
void* VM::getLibraryHandle(const char* name) {
@@ -235,7 +145,7 @@ void* VM::getLibraryHandle(const char* name) {
if (handle != NULL) {
return handle;
}
Log::warn("Failed to load %s: %s", name, dlerror());
std::cerr << "Failed to load " << name << ": " << dlerror() << std::endl;
}
return RTLD_DEFAULT;
}
@@ -274,118 +184,87 @@ void VM::loadAllMethodIDs(jvmtiEnv* jvmti, JNIEnv* jni) {
}
}
void VM::restartProfiler() {
Profiler::instance()->restart(_agent_args);
}
void JNICALL VM::VMInit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
ready();
loadAllMethodIDs(jvmti, jni);
// Delayed start of profiler if agent has been loaded at VM bootstrap
Error error = Profiler::instance()->run(_agent_args);
if (error) {
Log::error("%s", error.message());
}
Profiler::_instance.run(_agent_args);
}
void JNICALL VM::VMDeath(jvmtiEnv* jvmti, JNIEnv* jni) {
Profiler::instance()->shutdown(_agent_args);
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);
if (result == 0) {
// 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);
}
// 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);
if (result == 0) {
// 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]);
}
// 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" DLLEXPORT jint JNICALL
extern "C" JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM* vm, char* options, void* reserved) {
Error error = _agent_args.parse(options);
Log::open(_agent_args._log);
if (error) {
Log::error("%s", error.message());
return ARGUMENTS_ERROR;
}
VM::init(vm, false);
if (!VM::init(vm, false)) {
Log::error("JVM does not support Tool Interface");
return COMMAND_ERROR;
Error error = _agent_args.parse(options);
if (error) {
std::cerr << error.message() << std::endl;
return -1;
}
return 0;
}
extern "C" DLLEXPORT jint JNICALL
extern "C" JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
Arguments args(true);
Error error = args.parse(options);
Log::open(args._log);
if (error) {
Log::error("%s", error.message());
return ARGUMENTS_ERROR;
}
VM::init(vm, true);
if (!VM::init(vm, true)) {
Log::error("JVM does not support Tool Interface");
return COMMAND_ERROR;
Arguments args;
Error error = args.parse(options);
if (error) {
std::cerr << error.message() << std::endl;
return -1;
}
// Save the arguments in case of shutdown
if (args._action == ACTION_START || args._action == ACTION_RESUME) {
_agent_args.save(args);
}
error = Profiler::instance()->run(args);
if (error) {
Log::error("%s", error.message());
return COMMAND_ERROR;
}
Profiler::_instance.run(args);
return 0;
}
extern "C" DLLEXPORT jint JNICALL
extern "C" JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM* vm, void* reserved) {
if (!VM::init(vm, true)) {
return 0;
}
VM::init(vm, true);
JavaAPI::registerNatives(VM::jvmti(), VM::jni());
return JNI_VERSION_1_6;
}
extern "C" DLLEXPORT void JNICALL
JNI_OnUnload(JavaVM* vm, void* reserved) {
Profiler* profiler = Profiler::instance();
if (profiler != NULL) {
profiler->stop();
}
}

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