mirror of
https://github.com/async-profiler/async-profiler.git
synced 2026-04-28 19:03:33 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a20d771635 | ||
|
|
3a44bb6ba6 | ||
|
|
c94b1685cf | ||
|
|
39f84be219 | ||
|
|
4af327e2c1 | ||
|
|
61919df2ff | ||
|
|
af02f6b0fb | ||
|
|
c11d4ca487 | ||
|
|
b2dfe9b5b0 | ||
|
|
5585a77355 | ||
|
|
04dac10d41 | ||
|
|
5290b81190 | ||
|
|
a18af69f8b | ||
|
|
60cac04c24 | ||
|
|
3d7e8efd3b | ||
|
|
d26d69e550 | ||
|
|
8160e49c14 | ||
|
|
731ac31064 | ||
|
|
013ceee55d | ||
|
|
f7ef0e97b2 | ||
|
|
c01fe588ce | ||
|
|
edb31a0f79 | ||
|
|
450f251732 | ||
|
|
53ca190457 | ||
|
|
683144a907 | ||
|
|
02b65627cd | ||
|
|
642a1ac7fb | ||
|
|
114e711fd6 | ||
|
|
f833f41b46 | ||
|
|
a82163b703 | ||
|
|
6b49cfa9be | ||
|
|
d7d56c762b | ||
|
|
61d5cdcd68 | ||
|
|
d6d4a3c2a3 | ||
|
|
49f9050bf5 | ||
|
|
67b77b9645 | ||
|
|
971fc85d1c | ||
|
|
50b9fe4d85 | ||
|
|
f006e00443 |
42
CHANGELOG.md
42
CHANGELOG.md
@@ -1,40 +1,20 @@
|
||||
# Changelog
|
||||
|
||||
## [1.8.8] - 2022-09-10
|
||||
## [2.0-b1] - Early access
|
||||
|
||||
### Bug fixes
|
||||
- Could not find NativeLibrary_load on JDK 11.0.15
|
||||
|
||||
## [1.8.7] - 2021-10-01
|
||||
|
||||
### Bug fixes
|
||||
- Workaround for JDK-8173361
|
||||
- Backported fix for "Accept timed out" exception
|
||||
|
||||
## [1.8.6] - 2021-07-08
|
||||
### Features
|
||||
- Profile multiple events together (cpu + alloc + lock)
|
||||
- HTML 5 Flame Graphs: faster rendering, smaller size
|
||||
- JFR v2 output format, compatible with FlightRecorder API
|
||||
- Automatically turn profiling on/off at `--begin`/`--end` functions
|
||||
- Time-to-safepoint profiling
|
||||
|
||||
### Improvements
|
||||
- `log=none` option to suppress warnings about missing JVM symbols
|
||||
- Sign macOS binaries
|
||||
- Unlimited frame buffer. Removed `-b` option and 64K stack traces limit
|
||||
- Record CPU load in JFR format
|
||||
|
||||
### Bug fixes
|
||||
- Workaround for JDK-8212160
|
||||
|
||||
## [1.8.5] - 2021-03-22
|
||||
|
||||
### Improvements
|
||||
- Backported JFR to FlameGraph converter
|
||||
|
||||
### Bug fixes
|
||||
- Stricter `safemode` to avoid stack walking in suspicious cases
|
||||
|
||||
## [1.8.4] - 2021-02-24
|
||||
|
||||
### Improvements
|
||||
- Smaller and faster agent library
|
||||
|
||||
### Bug fixes
|
||||
- Fixed JDK 7 crash during wall-clock profiling
|
||||
### Changes
|
||||
- Removed non-ASL code. No more CDDL license
|
||||
|
||||
## [1.8.3] - 2021-01-06
|
||||
|
||||
|
||||
12
Makefile
12
Makefile
@@ -1,4 +1,4 @@
|
||||
PROFILER_VERSION=1.8.8
|
||||
PROFILER_VERSION=2.0-b1
|
||||
JATTACH_VERSION=1.5
|
||||
JAVAC_RELEASE_VERSION=6
|
||||
|
||||
@@ -33,13 +33,11 @@ ifeq ($(OS), Darwin)
|
||||
CXXFLAGS += -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE
|
||||
INCLUDES += -I$(JAVA_HOME)/include/darwin
|
||||
SOEXT=dylib
|
||||
CODESIGN=codesign -s "Developer ID" -o runtime --timestamp -v $(1)
|
||||
OS_TAG=macos
|
||||
else
|
||||
LIBS += -lrt
|
||||
INCLUDES += -I$(JAVA_HOME)/include/linux
|
||||
SOEXT=so
|
||||
CODESIGN=
|
||||
ifeq ($(findstring musl,$(shell ldd /bin/ls)),musl)
|
||||
OS_TAG=linux-musl
|
||||
else
|
||||
@@ -71,13 +69,11 @@ release: build $(PACKAGE_NAME).tar.gz
|
||||
|
||||
$(PACKAGE_NAME).tar.gz: build/$(LIB_PROFILER) build/$(JATTACH) \
|
||||
build/$(API_JAR) build/$(CONVERTER_JAR) \
|
||||
profiler.sh LICENSE NOTICE *.md
|
||||
$(call CODESIGN,build/$(LIB_PROFILER))
|
||||
$(call CODESIGN,build/$(JATTACH))
|
||||
profiler.sh LICENSE *.md
|
||||
mkdir -p $(PACKAGE_DIR)
|
||||
cp -RP build profiler.sh LICENSE NOTICE *.md $(PACKAGE_DIR)
|
||||
cp -RP build profiler.sh LICENSE *.md $(PACKAGE_DIR)
|
||||
chmod -R 755 $(PACKAGE_DIR)
|
||||
chmod 644 $(PACKAGE_DIR)/LICENSE $(PACKAGE_DIR)/NOTICE $(PACKAGE_DIR)/*.md $(PACKAGE_DIR)/build/*.jar
|
||||
chmod 644 $(PACKAGE_DIR)/LICENSE $(PACKAGE_DIR)/*.md $(PACKAGE_DIR)/build/*.jar
|
||||
tar cvzf $@ -C $(PACKAGE_DIR)/.. $(PACKAGE_NAME)
|
||||
rm -r $(PACKAGE_DIR)
|
||||
|
||||
|
||||
31
NOTICE
31
NOTICE
@@ -1,31 +0,0 @@
|
||||
async-profiler
|
||||
Copyright 2018 - 2020 Andrei Pangin
|
||||
|
||||
This product includes software licensed under CDDL 1.0 from the
|
||||
following sources:
|
||||
|
||||
This product includes a specialized C++ port of the FlameGraph script, licensed under CDDL, available at
|
||||
https://github.com/brendangregg/FlameGraph/blob/master/flamegraph.pl
|
||||
|
||||
Copyright 2016 Netflix, Inc.
|
||||
Copyright 2011 Joyent, Inc. All rights reserved.
|
||||
Copyright 2011 Brendan Gregg. All rights reserved.
|
||||
|
||||
CDDL HEADER START
|
||||
|
||||
The contents of this file are subject to the terms of the
|
||||
Common Development and Distribution License (the "License").
|
||||
You may not use this file except in compliance with the License.
|
||||
|
||||
You can obtain a copy of the license at docs/cddl1.txt or
|
||||
http://opensource.org/licenses/CDDL-1.0.
|
||||
See the License for the specific language governing permissions
|
||||
and limitations under the License.
|
||||
|
||||
When distributing Covered Code, include this CDDL HEADER in each
|
||||
file and include the License file at docs/cddl1.txt.
|
||||
If applicable, add the following below this CDDL HEADER, with the
|
||||
fields enclosed by brackets "[]" replaced with your own identifying
|
||||
information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
CDDL HEADER END
|
||||
528
README.md
528
README.md
@@ -1,5 +1,7 @@
|
||||
# async-profiler
|
||||
|
||||
[](https://travis-ci.org/jvm-profiling-tools/async-profiler)
|
||||
|
||||
This project is a low overhead sampling profiler for Java
|
||||
that does not suffer from [Safepoint bias problem](http://psy-lob-saw.blogspot.ru/2016/02/why-most-sampling-java-profilers-are.html).
|
||||
It features HotSpot-specific APIs to collect stack traces
|
||||
@@ -11,22 +13,28 @@ async-profiler can trace the following kinds of events:
|
||||
- Hardware and Software performance counters like cache misses, branch misses, page faults, context switches etc.
|
||||
- Allocations in Java Heap
|
||||
- Contented lock attempts, including both Java object monitors and ReentrantLocks
|
||||
|
||||
## Usage
|
||||
|
||||
See our [Wiki](https://github.com/jvm-profiling-tools/async-profiler/wiki) or [3 hours playlist](https://www.youtube.com/playlist?list=PLNCLTEx3B8h4Yo_WvKWdLvI9mj1XpTKBr) to learn about all set of features.
|
||||
|
||||
## Download
|
||||
|
||||
Latest v1 release (1.8.8):
|
||||
Latest release (1.8.3):
|
||||
|
||||
- Linux x64 (glibc): [async-profiler-1.8.8-linux-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.8/async-profiler-1.8.8-linux-x64.tar.gz)
|
||||
- Linux x86 (glibc): [async-profiler-1.8.8-linux-x86.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.8/async-profiler-1.8.8-linux-x86.tar.gz)
|
||||
- Linux x64 (musl): [async-profiler-1.8.8-linux-musl-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.8/async-profiler-1.8.8-linux-musl-x64.tar.gz)
|
||||
- Linux ARM: [async-profiler-1.8.8-linux-arm.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.8/async-profiler-1.8.8-linux-arm.tar.gz)
|
||||
- Linux AArch64: [async-profiler-1.8.8-linux-aarch64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.8/async-profiler-1.8.8-linux-aarch64.tar.gz)
|
||||
- macOS x64: [async-profiler-1.8.8-macos-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.8/async-profiler-1.8.8-macos-x64.tar.gz)
|
||||
- Linux x64 (glibc): [async-profiler-1.8.3-linux-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.3/async-profiler-1.8.3-linux-x64.tar.gz)
|
||||
- Linux x86 (glibc): [async-profiler-1.8.3-linux-x86.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.3/async-profiler-1.8.3-linux-x86.tar.gz)
|
||||
- Linux x64 (musl): [async-profiler-1.8.3-linux-musl-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.3/async-profiler-1.8.3-linux-musl-x64.tar.gz)
|
||||
- Linux ARM: [async-profiler-1.8.3-linux-arm.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.3/async-profiler-1.8.3-linux-arm.tar.gz)
|
||||
- Linux AArch64: [async-profiler-1.8.3-linux-aarch64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.3/async-profiler-1.8.3-linux-aarch64.tar.gz)
|
||||
- macOS x64: [async-profiler-1.8.3-macos-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.3/async-profiler-1.8.3-macos-x64.tar.gz)
|
||||
|
||||
[Other releases](https://github.com/jvm-profiling-tools/async-profiler/releases)
|
||||
[Early access](https://github.com/jvm-profiling-tools/async-profiler/releases/tag/v2.0-b1) (2.0-b1):
|
||||
|
||||
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).
|
||||
- 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)
|
||||
|
||||
## Supported platforms
|
||||
|
||||
@@ -35,511 +43,11 @@ For more information refer to [IntelliJ IDEA documentation](https://www.jetbrain
|
||||
|
||||
Note: macOS profiling is limited to user space code only.
|
||||
|
||||
## CPU profiling
|
||||
|
||||
In this mode profiler collects stack trace samples that include **Java** methods,
|
||||
**native** calls, **JVM** code and **kernel** functions.
|
||||
|
||||
The general approach is receiving call stacks generated by `perf_events`
|
||||
and matching them up with call stacks generated by `AsyncGetCallTrace`,
|
||||
in order to produce an accurate profile of both Java and native code.
|
||||
Additionally, async-profiler provides a workaround to recover stack traces
|
||||
in some [corner cases](https://bugs.openjdk.java.net/browse/JDK-8178287)
|
||||
where `AsyncGetCallTrace` fails.
|
||||
|
||||
This approach has the following advantages compared to using `perf_events`
|
||||
directly with a Java agent that translates addresses to Java method names:
|
||||
|
||||
* Works on older Java versions because it doesn't require
|
||||
`-XX:+PreserveFramePointer`, which is only available in JDK 8u60 and later.
|
||||
|
||||
* Does not introduce the performance overhead from `-XX:+PreserveFramePointer`,
|
||||
which can in rare cases be as high as 10%.
|
||||
|
||||
* Does not require generating a map file to map Java code addresses to method
|
||||
names.
|
||||
|
||||
* Works with interpreter frames.
|
||||
|
||||
* Does not require writing out a perf.data file for further processing in
|
||||
user space scripts.
|
||||
|
||||
If you wish to resolve frames within `libjvm`, the [debug symbols](#installing-debug-symbols) are required.
|
||||
|
||||
## ALLOCATION profiling
|
||||
|
||||
Instead of detecting CPU-consuming code, the profiler can be configured
|
||||
to collect call sites where the largest amount of heap memory is allocated.
|
||||
|
||||
async-profiler does not use intrusive techniques like bytecode instrumentation
|
||||
or expensive DTrace probes which have significant performance impact.
|
||||
It also does not affect Escape Analysis or prevent from JIT optimizations
|
||||
like allocation elimination. Only actual heap allocations are measured.
|
||||
|
||||
The profiler features TLAB-driven sampling. It relies on HotSpot-specific
|
||||
callbacks to receive two kinds of notifications:
|
||||
- when an object is allocated in a newly created TLAB (aqua frames in a Flame Graph);
|
||||
- when an object is allocated on a slow path outside TLAB (brown frames).
|
||||
|
||||
This means not each allocation is counted, but only allocations every _N_ kB,
|
||||
where _N_ is the average size of TLAB. This makes heap sampling very cheap
|
||||
and suitable for production. On the other hand, the collected data
|
||||
may be incomplete, though in practice it will often reflect the top allocation
|
||||
sources.
|
||||
|
||||
Sampling interval can be adjusted with `-i` option.
|
||||
For example, `-i 500k` will take one sample after 500 KB of allocated
|
||||
space on average. However, intervals less than TLAB size will not take effect.
|
||||
|
||||
Unlike Java Mission Control which uses similar approach, async-profiler
|
||||
does not require Java Flight Recorder or any other JDK commercial feature.
|
||||
It is completely based on open source technologies and it works with OpenJDK.
|
||||
|
||||
The minimum supported JDK version is 7u40 where the TLAB callbacks appeared.
|
||||
|
||||
### Installing Debug Symbols
|
||||
|
||||
The allocation profiler requires HotSpot debug symbols. Oracle JDK already has them
|
||||
embedded in `libjvm.so`, but in OpenJDK builds they are typically shipped
|
||||
in a separate package. For example, to install OpenJDK debug symbols on
|
||||
Debian / Ubuntu, run:
|
||||
```
|
||||
# apt install openjdk-8-dbg
|
||||
```
|
||||
or for OpenJDK 11:
|
||||
```
|
||||
# apt install openjdk-11-dbg
|
||||
```
|
||||
|
||||
On CentOS, RHEL and some other RPM-based distributions, this could be done with
|
||||
[debuginfo-install](http://man7.org/linux/man-pages/man1/debuginfo-install.1.html) utility:
|
||||
```
|
||||
# debuginfo-install java-1.8.0-openjdk
|
||||
```
|
||||
|
||||
On Gentoo the `icedtea` OpenJDK package can be built with the per-package setting
|
||||
`FEATURES="nostrip"` to retain symbols.
|
||||
|
||||
The `gdb` tool can be used to verify if the debug symbols are properly installed for the `libjvm` library.
|
||||
For example on Linux:
|
||||
```
|
||||
$ gdb $JAVA_HOME/lib/server/libjvm.so -ex 'info address UseG1GC'
|
||||
```
|
||||
This command's output will either contain `Symbol "UseG1GC" is at 0xxxxx` or `No symbol "UseG1GC" in current context`.
|
||||
|
||||
## Wall-clock profiling
|
||||
|
||||
`-e wall` option tells async-profiler to sample all threads equally every given
|
||||
period of time regardless of thread status: Running, Sleeping or Blocked.
|
||||
For instance, this can be helpful when profiling application start-up time.
|
||||
|
||||
Wall-clock profiler is most useful in per-thread mode: `-t`.
|
||||
|
||||
Example: `./profiler.sh -e wall -t -i 5ms -f result.svg 8983`
|
||||
|
||||
## Java method profiling
|
||||
|
||||
`-e ClassName.methodName` option instruments the given Java method
|
||||
in order to record all invocations of this method with the stack traces.
|
||||
|
||||
Example: `-e java.util.Properties.getProperty` will profile all places
|
||||
where `getProperty` method is called from.
|
||||
|
||||
Only non-native Java methods are supported. To profile a native method,
|
||||
use hardware breakpoint event instead, e.g. `-e Java_java_lang_Throwable_fillInStackTrace`
|
||||
|
||||
## Building
|
||||
|
||||
Build status: [](https://travis-ci.org/jvm-profiling-tools/async-profiler)
|
||||
|
||||
Make sure the `JAVA_HOME` environment variable points to your JDK installation,
|
||||
and then run `make`. GCC is required. After building, the profiler agent binary
|
||||
will be in the `build` subdirectory. Additionally, a small application `jattach`
|
||||
that can load the agent into the target process will also be compiled to the
|
||||
`build` subdirectory.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
As of Linux 4.6, capturing kernel call stacks using `perf_events` from a non-
|
||||
root process requires setting two runtime variables. You can set them using
|
||||
sysctl or as follows:
|
||||
|
||||
```
|
||||
# echo 1 > /proc/sys/kernel/perf_event_paranoid
|
||||
# echo 0 > /proc/sys/kernel/kptr_restrict
|
||||
```
|
||||
|
||||
To run the agent and pass commands to it, the helper script `profiler.sh`
|
||||
is provided. A typical workflow would be to launch your Java application,
|
||||
attach the agent and start profiling, exercise your performance scenario, and
|
||||
then stop profiling. The agent's output, including the profiling results, will
|
||||
be displayed in the Java application's standard output.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
$ jps
|
||||
9234 Jps
|
||||
8983 Computey
|
||||
$ ./profiler.sh start 8983
|
||||
$ ./profiler.sh stop 8983
|
||||
```
|
||||
|
||||
The following may be used in lieu of the `pid` (8983):
|
||||
|
||||
- The keyword `jps`, which will use the most recently launched Java process.
|
||||
- The application name as it appears in the `jps` output: e.g. `Computey`
|
||||
|
||||
Alternatively, you may specify `-d` (duration) argument to profile
|
||||
the application for a fixed period of time with a single command.
|
||||
|
||||
```
|
||||
$ ./profiler.sh -d 30 8983
|
||||
```
|
||||
|
||||
By default, the profiling frequency is 100Hz (every 10ms of CPU time).
|
||||
Here is a sample of the output printed to the Java application's terminal:
|
||||
|
||||
```
|
||||
--- Execution profile ---
|
||||
Total samples: 687
|
||||
Unknown (native): 1 (0.15%)
|
||||
|
||||
--- 6790000000 (98.84%) ns, 679 samples
|
||||
[ 0] Primes.isPrime
|
||||
[ 1] Primes.primesThread
|
||||
[ 2] Primes.access$000
|
||||
[ 3] Primes$1.run
|
||||
[ 4] java.lang.Thread.run
|
||||
|
||||
... a lot of output omitted for brevity ...
|
||||
|
||||
ns percent samples top
|
||||
---------- ------- ------- ---
|
||||
6790000000 98.84% 679 Primes.isPrime
|
||||
40000000 0.58% 4 __do_softirq
|
||||
|
||||
... more output omitted ...
|
||||
```
|
||||
|
||||
This indicates that the hottest method was `Primes.isPrime`, and the hottest
|
||||
call stack leading to it comes from `Primes.primesThread`.
|
||||
|
||||
## Launching as an Agent
|
||||
|
||||
If you need to profile some code as soon as the JVM starts up, instead of using the `profiler.sh` script,
|
||||
it is possible to attach async-profiler as an agent on the command line. For example:
|
||||
|
||||
```
|
||||
$ java -agentpath:/path/to/libasyncProfiler.so=start,file=profile.svg ...
|
||||
```
|
||||
|
||||
Agent library is configured through the JVMTI argument interface.
|
||||
The format of the arguments string is described
|
||||
[in the source code](https://github.com/jvm-profiling-tools/async-profiler/blob/v1.8.8/src/arguments.cpp#L49).
|
||||
The `profiler.sh` script actually converts command line arguments to that format.
|
||||
|
||||
For instance, `-e alloc` is converted to `event=alloc`, `-f profile.svg`
|
||||
is converted to `file=profile.svg` and so on. But some arguments are processed
|
||||
directly by `profiler.sh` script. E.g. `-d 5` results in 3 actions:
|
||||
attaching profiler agent with start command, sleeping for 5 seconds,
|
||||
and then attaching the agent again with stop command.
|
||||
|
||||
## Flame Graph visualization
|
||||
|
||||
async-profiler provides out-of-the-box [Flame Graph](https://github.com/BrendanGregg/FlameGraph) support.
|
||||
Specify `-o svg` argument to dump profiling results as an interactive SVG
|
||||
immediately viewable in all mainstream browsers.
|
||||
Also, SVG output format will be chosen automatically if the target
|
||||
filename ends with `.svg`.
|
||||
|
||||
```
|
||||
$ jps
|
||||
9234 Jps
|
||||
8983 Computey
|
||||
$ ./profiler.sh -d 30 -f /tmp/flamegraph.svg 8983
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Profiler Options
|
||||
|
||||
The following is a complete list of the command-line options accepted by
|
||||
`profiler.sh` script.
|
||||
|
||||
* `start` - starts profiling in semi-automatic mode, i.e. profiler will run
|
||||
until `stop` command is explicitly called.
|
||||
|
||||
* `resume` - starts or resumes earlier profiling session that has been stopped.
|
||||
All the collected data remains valid. The profiling options are not preserved
|
||||
between sessions, and should be specified again.
|
||||
|
||||
* `stop` - stops profiling and prints the report.
|
||||
|
||||
* `check` - check if the specified profiling event is available.
|
||||
|
||||
* `status` - prints profiling status: whether profiler is active and
|
||||
for how long.
|
||||
|
||||
* `list` - show the list of available profiling events. This option still
|
||||
requires PID, since supported events may differ depending on JVM version.
|
||||
|
||||
* `-d N` - the profiling duration, in seconds. If no `start`, `resume`, `stop`
|
||||
or `status` option is given, the profiler will run for the specified period
|
||||
of time and then automatically stop.
|
||||
Example: `./profiler.sh -d 30 8983`
|
||||
|
||||
* `-e event` - the profiling event: `cpu`, `alloc`, `lock`, `cache-misses` etc.
|
||||
Use `list` to see the complete list of available events.
|
||||
|
||||
In allocation profiling mode the top frame of every call trace is the class
|
||||
of the allocated object, and the counter is the heap pressure (the total size
|
||||
of allocated TLABs or objects outside TLAB).
|
||||
|
||||
In lock profiling mode the top frame is the class of lock/monitor, and
|
||||
the counter is number of nanoseconds it took to enter this lock/monitor.
|
||||
|
||||
Two special event types are supported on Linux: hardware breakpoints
|
||||
and kernel tracepoints:
|
||||
- `-e mem:<func>[:rwx]` sets read/write/exec breakpoint at function
|
||||
`<func>`. The format of `mem` event is the same as in `perf-record`.
|
||||
Execution breakpoints can be also specified by the function name,
|
||||
e.g. `-e malloc` will trace all calls of native `malloc` function.
|
||||
- `-e trace:<id>` sets a kernel tracepoint. It is possible to specify
|
||||
tracepoint symbolic name, e.g. `-e syscalls:sys_enter_open` will trace
|
||||
all `open` syscalls.
|
||||
|
||||
* `-i N` - sets the profiling interval in nanoseconds or in other units,
|
||||
if N is followed by `ms` (for milliseconds), `us` (for microseconds)
|
||||
or `s` (for seconds). Only CPU active time is counted. No samples
|
||||
are collected while CPU is idle. The default is 10000000 (10ms).
|
||||
Example: `./profiler.sh -i 500us 8983`
|
||||
|
||||
* `-j N` - sets the Java stack profiling depth. This option will be ignored if N is greater
|
||||
than default 2048.
|
||||
Example: `./profiler.sh -j 30 8983`
|
||||
|
||||
* `-b N` - sets the frame buffer size, in the number of Java
|
||||
method ids that should fit in the buffer. If you receive messages about an
|
||||
insufficient frame buffer size, increase this value from the default.
|
||||
Example: `./profiler.sh -b 5000000 8983`
|
||||
|
||||
* `-t` - profile threads separately. Each stack trace will end with a frame
|
||||
that denotes a single thread.
|
||||
Example: `./profiler.sh -t 8983`
|
||||
|
||||
* `-s` - print simple class names instead of FQN.
|
||||
|
||||
* `-g` - print method signatures.
|
||||
|
||||
* `-a` - annotate Java method names by adding `_[j]` suffix.
|
||||
|
||||
* `-o fmt` - specifies what information to dump when profiling ends.
|
||||
`fmt` can be one of the following options:
|
||||
- `summary` - dump basic profiling statistics;
|
||||
- `traces[=N]` - dump call traces (at most N samples);
|
||||
- `flat[=N]` - dump flat profile (top N hot methods);
|
||||
- `jfr` - dump events in Java Flight Recorder format readable by Java Mission Control.
|
||||
This *does not* require JDK commercial features to be enabled.
|
||||
- `collapsed[=C]` - dump collapsed call traces in the format used by
|
||||
[FlameGraph](https://github.com/brendangregg/FlameGraph) script. This is
|
||||
a collection of call stacks, where each line is a semicolon separated list
|
||||
of frames followed by a counter.
|
||||
- `svg[=C]` - produce Flame Graph in SVG format.
|
||||
- `tree[=C]` - produce call tree in HTML format.
|
||||
--reverse option will generate backtrace view.
|
||||
|
||||
`C` is a counter type:
|
||||
- `samples` - the counter is a number of samples for the given trace;
|
||||
- `total` - the counter is a total value of collected metric, e.g. total allocation size.
|
||||
|
||||
`summary`, `traces` and `flat` can be combined together.
|
||||
The default format is `summary,traces=200,flat=200`.
|
||||
|
||||
* `-I include`, `-X exclude` - filter stack traces by the given pattern(s).
|
||||
`-I` defines the name pattern that *must* be present in the stack traces,
|
||||
while `-X` is the pattern that *must not* occur in any of stack traces in the output.
|
||||
`-I` and `-X` options can be specified multiple times. A pattern may begin or end with
|
||||
a star `*` that denotes any (possibly empty) sequence of characters.
|
||||
Example: `./profiler.sh -I 'Primes.*' -I 'java/*' -X '*Unsafe.park*' 8983`
|
||||
|
||||
* `--title TITLE`, `--width PX`, `--height PX`, `--minwidth PX`, `--reverse` - FlameGraph parameters.
|
||||
Example: `./profiler.sh -f profile.svg --title "Sample CPU profile" --minwidth 0.5 8983`
|
||||
|
||||
* `-f FILENAME` - the file name to dump the profile information to.
|
||||
`%p` in the file name is expanded to the PID of the target JVM;
|
||||
`%t` - to the timestamp at the time of command invocation.
|
||||
Example: `./profiler.sh -o collapsed -f /tmp/traces-%t.txt 8983`
|
||||
|
||||
* `--all-user` - include only user-mode events. This option is helpful when kernel profiling
|
||||
is restricted by `perf_event_paranoid` settings.
|
||||
`--all-kernel` is its counterpart option for including only kernel-mode events.
|
||||
|
||||
* `--cstack MODE` - how to traverse native frames (C stack). Possible modes are
|
||||
`fp` (Frame Pointer), `lbr` (Last Branch Record, available on Haswell since Linux 4.1),
|
||||
and `no` (do not collect C stack).
|
||||
|
||||
By default, C stack is shown in cpu, itimer, wall-clock and perf-events profiles.
|
||||
Java-level events like `alloc` and `lock` collect only Java stack.
|
||||
|
||||
* `-v`, `--version` - prints the version of profiler library. If PID is specified,
|
||||
gets the version of the library loaded into the given process.
|
||||
|
||||
## Profiling Java in a container
|
||||
|
||||
It is possible to profile Java processes running in a Docker or LXC container
|
||||
both from within a container and from the host system.
|
||||
|
||||
When profiling from the host, `pid` should be the Java process ID in the host
|
||||
namespace. Use `ps aux | grep java` or `docker top <container>` to find
|
||||
the process ID.
|
||||
|
||||
async-profiler should be run from the host by a privileged user - it will
|
||||
automatically switch to the proper pid/mount namespace and change
|
||||
user credentials to match the target process. Also make sure that
|
||||
the target container can access `libasyncProfiler.so` by the same
|
||||
absolute path as on the host.
|
||||
|
||||
By default, Docker container restricts the access to `perf_event_open`
|
||||
syscall. So, in order to allow profiling inside a container, you'll need
|
||||
to modify [seccomp profile](https://docs.docker.com/engine/security/seccomp/)
|
||||
or disable it altogether with `--security-opt seccomp=unconfined` option. In
|
||||
addition, `--cap-add SYS_ADMIN` may be required.
|
||||
|
||||
Alternatively, if changing Docker configuration is not possible,
|
||||
you may fall back to `-e itimer` profiling mode, see [Troubleshooting](#troubleshooting).
|
||||
|
||||
## Restrictions/Limitations
|
||||
|
||||
* On most Linux systems, `perf_events` captures call stacks with a maximum depth
|
||||
of 127 frames. On recent Linux kernels, this can be configured using
|
||||
`sysctl kernel.perf_event_max_stack` or by writing to the
|
||||
`/proc/sys/kernel/perf_event_max_stack` file.
|
||||
|
||||
* Profiler allocates 8kB perf_event buffer for each thread of the target process.
|
||||
Make sure `/proc/sys/kernel/perf_event_mlock_kb` value is large enough
|
||||
(more than `8 * threads`) when running under unprivileged user.
|
||||
Otherwise the message _"perf_event mmap failed: Operation not permitted"_
|
||||
will be printed, and no native stack traces will be collected.
|
||||
|
||||
* There is no bullet-proof guarantee that the `perf_events` overflow signal
|
||||
is delivered to the Java thread in a way that guarantees no other code has run,
|
||||
which means that in some rare cases, the captured Java stack might not match
|
||||
the captured native (user+kernel) stack.
|
||||
|
||||
* You will not see the non-Java frames _preceding_ the Java frames on the
|
||||
stack. For example, if `start_thread` called `JavaMain` and then your Java
|
||||
code started running, you will not see the first two frames in the resulting
|
||||
stack. On the other hand, you _will_ see non-Java frames (user and kernel)
|
||||
invoked by your Java code.
|
||||
|
||||
* No Java stacks will be collected if `-XX:MaxJavaStackTraceDepth` is zero
|
||||
or negative.
|
||||
|
||||
* Too short profiling interval may cause continuous interruption of heavy
|
||||
system calls like `clone()`, so that it will never complete;
|
||||
see [#97](https://github.com/jvm-profiling-tools/async-profiler/issues/97).
|
||||
The workaround is simply to increase the interval.
|
||||
|
||||
* When agent is not loaded at JVM startup (by using -agentpath option) it is
|
||||
highly recommended to use `-XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints` JVM flags.
|
||||
Without those flags the profiler will still work correctly but results might be
|
||||
less accurate e.g. without `-XX:+DebugNonSafepoints` there is a high chance that simple inlined methods will not appear in the profile. When agent is attached at runtime `CompiledMethodLoad` JVMTI event
|
||||
enables debug info, but only for methods compiled after the event is turned on.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
```
|
||||
Failed to change credentials to match the target process: Operation not permitted
|
||||
```
|
||||
Due to limitation of HotSpot Dynamic Attach mechanism, the profiler must be run
|
||||
by exactly the same user (and group) as the owner of target JVM process.
|
||||
If profiler is run by a different user, it will try to automatically change
|
||||
current user and group. This will likely succeed for `root`, but not for
|
||||
other users, resulting in the above error.
|
||||
|
||||
```
|
||||
Could not start attach mechanism: No such file or directory
|
||||
```
|
||||
The profiler cannot establish communication with the target JVM through UNIX domain socket.
|
||||
|
||||
Usually this happens in one of the following cases:
|
||||
1. Attach socket `/tmp/.java_pidNNN` has been deleted. It is a common
|
||||
practice to clean `/tmp` automatically with some scheduled script.
|
||||
Configure the cleanup software to exclude `.java_pid*` files from deletion.
|
||||
How to check: run `lsof -p PID | grep java_pid`
|
||||
If it lists a socket file, but the file does not exist, then this is exactly
|
||||
the described problem.
|
||||
2. JVM is started with `-XX:+DisableAttachMechanism` option.
|
||||
3. `/tmp` directory of Java process is not physically the same directory
|
||||
as `/tmp` of your shell, because Java is running in a container or in
|
||||
`chroot` environment. `jattach` attempts to solve this automatically,
|
||||
but it might lack the required permissions to do so.
|
||||
Check `strace build/jattach PID properties`
|
||||
4. JVM is busy and cannot reach a safepoint. For instance,
|
||||
JVM is in the middle of long-running garbage collection.
|
||||
How to check: run `kill -3 PID`. Healthy JVM process should print
|
||||
a thread dump and heap info in its console.
|
||||
|
||||
```
|
||||
Failed to inject profiler into <pid>
|
||||
```
|
||||
The connection with the target JVM has been established, but JVM is unable to load profiler shared library.
|
||||
Make sure the user of JVM process has permissions to access `libasyncProfiler.so` by exactly the same absolute path.
|
||||
For more information see [#78](https://github.com/jvm-profiling-tools/async-profiler/issues/78).
|
||||
|
||||
```
|
||||
Perf events unavailble. See stderr of the target process.
|
||||
```
|
||||
`perf_event_open()` syscall has failed. The error message is printed to the error stream
|
||||
of the target JVM.
|
||||
|
||||
Typical reasons include:
|
||||
1. `/proc/sys/kernel/perf_event_paranoid` is set to restricted mode (>=2).
|
||||
2. seccomp disables perf_event_open API in a container.
|
||||
3. OS runs under a hypervisor that does not virtualize performance counters.
|
||||
4. perf_event_open API is not supported on this system, e.g. WSL.
|
||||
|
||||
If changing the configuration is not possible, you may fall back to
|
||||
`-e itimer` profiling mode. It is similar to `cpu` mode, but does not
|
||||
require perf_events support. As a drawback, there will be no kernel
|
||||
stack traces.
|
||||
|
||||
```
|
||||
No AllocTracer symbols found. Are JDK debug symbols installed?
|
||||
```
|
||||
The OpenJDK debug symbols are required for allocation profiling.
|
||||
See [Installing Debug Symbols](#installing-debug-symbols) for more details.
|
||||
If the error message persists after a successful installation of the debug symbols, it is possible that the JDK was upgraded when installing the debug symbols.
|
||||
In this case, profiling any Java process which had started prior to the installation will continue to display this message, since the process had loaded the older version of the JDK which lacked debug symbols.
|
||||
Restarting the affected Java processes should resolve the issue.
|
||||
|
||||
```
|
||||
VMStructs unavailable. Unsupported JVM?
|
||||
```
|
||||
JVM shared library does not export `gHotSpotVMStructs*` symbols -
|
||||
apparently this is not a HotSpot JVM. Sometimes the same message
|
||||
can be also caused by an incorrectly built JDK
|
||||
(see [#218](https://github.com/jvm-profiling-tools/async-profiler/issues/218)).
|
||||
In these cases installing JDK debug symbols may solve the problem.
|
||||
|
||||
```
|
||||
Could not parse symbols due to the OS bug
|
||||
```
|
||||
Async-profiler was unable to parse non-Java function names because of
|
||||
the corrupted contents in `/proc/[pid]/maps`. The problem is known to
|
||||
occur in a container when running Ubuntu with Linux kernel 5.x.
|
||||
This is the OS bug, see https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1843018.
|
||||
|
||||
```
|
||||
[frame_buffer_overflow]
|
||||
```
|
||||
This message in the output means there was not enough space to store all call traces.
|
||||
Consider increasing frame buffer size with `-b` option.
|
||||
|
||||
```
|
||||
Output file is not created
|
||||
```
|
||||
Output file is written by the target JVM process, not by the profiler script.
|
||||
If the file cannot be opened (e.g. due to lack of permissions), the error message
|
||||
is printed to `stderr` of the target process (JVM console).
|
||||
|
||||
358
docs/cddl1.txt
358
docs/cddl1.txt
@@ -1,358 +0,0 @@
|
||||
COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
|
||||
|
||||
1. Definitions.
|
||||
|
||||
1.1. "Contributor" means each individual or entity that
|
||||
creates or contributes to the creation of Modifications.
|
||||
|
||||
1.2. "Contributor Version" means the combination of the
|
||||
Original Software, prior Modifications used by a
|
||||
Contributor (if any), and the Modifications made by that
|
||||
particular Contributor.
|
||||
|
||||
1.3. "Covered Software" means (a) the Original Software, or
|
||||
(b) Modifications, or (c) the combination of files
|
||||
containing Original Software with files containing
|
||||
Modifications, in each case including portions thereof.
|
||||
|
||||
1.4. "Executable" means the Covered Software in any form
|
||||
other than Source Code.
|
||||
|
||||
1.5. "Initial Developer" means the individual or entity
|
||||
that first makes Original Software available under this
|
||||
License.
|
||||
|
||||
1.6. "Larger Work" means a work which combines Covered
|
||||
Software or portions thereof with code not governed by the
|
||||
terms of this License.
|
||||
|
||||
1.7. "License" means this document.
|
||||
|
||||
1.8. "Licensable" means having the right to grant, to the
|
||||
maximum extent possible, whether at the time of the initial
|
||||
grant or subsequently acquired, any and all of the rights
|
||||
conveyed herein.
|
||||
|
||||
1.9. "Modifications" means the Source Code and Executable
|
||||
form of any of the following:
|
||||
A. Any file that results from an addition to,
|
||||
deletion from or modification of the contents of a
|
||||
file containing Original Software or previous
|
||||
Modifications;
|
||||
B. Any new file that contains any part of the
|
||||
Original Software or previous Modification; or
|
||||
C. Any new file that is contributed or otherwise made
|
||||
available under the terms of this License.
|
||||
|
||||
1.10. "Original Software" means the Source Code and
|
||||
Executable form of computer software code that is
|
||||
originally released under this License.
|
||||
|
||||
1.11. "Patent Claims" means any patent claim(s), now owned
|
||||
or hereafter acquired, including without limitation,
|
||||
method, process, and apparatus claims, in any patent
|
||||
Licensable by grantor.
|
||||
|
||||
1.12. "Source Code" means (a) the common form of computer
|
||||
software code in which modifications are made and (b)
|
||||
associated documentation included in or with such code.
|
||||
|
||||
1.13. "You" (or "Your") means an individual or a legal
|
||||
entity exercising rights under, and complying with all of
|
||||
the terms of, this License. For legal entities, "You"
|
||||
includes any entity which controls, is controlled by, or is
|
||||
under common control with You. For purposes of this
|
||||
definition, "control" means (a) the power, direct or
|
||||
indirect, to cause the direction or management of such
|
||||
entity, whether by contract or otherwise, or (b) ownership
|
||||
of more than fifty percent (50%) of the outstanding shares
|
||||
or beneficial ownership of such entity.
|
||||
|
||||
2. License Grants.
|
||||
|
||||
2.1. The Initial Developer Grant.
|
||||
Conditioned upon Your compliance with Section 3.1 below and
|
||||
subject to third party intellectual property claims, the
|
||||
Initial Developer hereby grants You a world-wide,
|
||||
royalty-free, non-exclusive license:
|
||||
(a) under intellectual property rights (other than
|
||||
patent or trademark) Licensable by Initial Developer,
|
||||
to use, reproduce, modify, display, perform,
|
||||
sublicense and distribute the Original Software (or
|
||||
portions thereof), with or without Modifications,
|
||||
and/or as part of a Larger Work; and
|
||||
(b) under Patent Claims infringed by the making,
|
||||
using or selling of Original Software, to make, have
|
||||
made, use, practice, sell, and offer for sale, and/or
|
||||
otherwise dispose of the Original Software (or
|
||||
portions thereof).
|
||||
(c) The licenses granted in Sections 2.1(a) and (b)
|
||||
are effective on the date Initial Developer first
|
||||
distributes or otherwise makes the Original Software
|
||||
available to a third party under the terms of this
|
||||
License.
|
||||
(d) Notwithstanding Section 2.1(b) above, no patent
|
||||
license is granted: (1) for code that You delete from
|
||||
the Original Software, or (2) for infringements
|
||||
caused by: (i) the modification of the Original
|
||||
Software, or (ii) the combination of the Original
|
||||
Software with other software or devices.
|
||||
|
||||
2.2. Contributor Grant.
|
||||
Conditioned upon Your compliance with Section 3.1 below and
|
||||
subject to third party intellectual property claims, each
|
||||
Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
(a) under intellectual property rights (other than
|
||||
patent or trademark) Licensable by Contributor to
|
||||
use, reproduce, modify, display, perform, sublicense
|
||||
and distribute the Modifications created by such
|
||||
Contributor (or portions thereof), either on an
|
||||
unmodified basis, with other Modifications, as
|
||||
Covered Software and/or as part of a Larger Work; and
|
||||
(b) under Patent Claims infringed by the making,
|
||||
using, or selling of Modifications made by that
|
||||
Contributor either alone and/or in combination with
|
||||
its Contributor Version (or portions of such
|
||||
combination), to make, use, sell, offer for sale,
|
||||
have made, and/or otherwise dispose of: (1)
|
||||
Modifications made by that Contributor (or portions
|
||||
thereof); and (2) the combination of Modifications
|
||||
made by that Contributor with its Contributor Version
|
||||
(or portions of such combination).
|
||||
(c) The licenses granted in Sections 2.2(a) and
|
||||
2.2(b) are effective on the date Contributor first
|
||||
distributes or otherwise makes the Modifications
|
||||
available to a third party.
|
||||
(d) Notwithstanding Section 2.2(b) above, no patent
|
||||
license is granted: (1) for any code that Contributor
|
||||
has deleted from the Contributor Version; (2) for
|
||||
infringements caused by: (i) third party
|
||||
modifications of Contributor Version, or (ii) the
|
||||
combination of Modifications made by that Contributor
|
||||
with other software (except as part of the
|
||||
Contributor Version) or other devices; or (3) under
|
||||
Patent Claims infringed by Covered Software in the
|
||||
absence of Modifications made by that Contributor.
|
||||
|
||||
3. Distribution Obligations.
|
||||
|
||||
3.1. Availability of Source Code.
|
||||
Any Covered Software that You distribute or otherwise make
|
||||
available in Executable form must also be made available in
|
||||
Source Code form and that Source Code form must be
|
||||
distributed only under the terms of this License. You must
|
||||
include a copy of this License with every copy of the
|
||||
Source Code form of the Covered Software You distribute or
|
||||
otherwise make available. You must inform recipients of any
|
||||
such Covered Software in Executable form as to how they can
|
||||
obtain such Covered Software in Source Code form in a
|
||||
reasonable manner on or through a medium customarily used
|
||||
for software exchange.
|
||||
|
||||
3.2. Modifications.
|
||||
The Modifications that You create or to which You
|
||||
contribute are governed by the terms of this License. You
|
||||
represent that You believe Your Modifications are Your
|
||||
original creation(s) and/or You have sufficient rights to
|
||||
grant the rights conveyed by this License.
|
||||
|
||||
3.3. Required Notices.
|
||||
You must include a notice in each of Your Modifications
|
||||
that identifies You as the Contributor of the Modification.
|
||||
You may not remove or alter any copyright, patent or
|
||||
trademark notices contained within the Covered Software, or
|
||||
any notices of licensing or any descriptive text giving
|
||||
attribution to any Contributor or the Initial Developer.
|
||||
|
||||
3.4. Application of Additional Terms.
|
||||
You may not offer or impose any terms on any Covered
|
||||
Software in Source Code form that alters or restricts the
|
||||
applicable version of this License or the recipients'
|
||||
rights hereunder. You may choose to offer, and to charge a
|
||||
fee for, warranty, support, indemnity or liability
|
||||
obligations to one or more recipients of Covered Software.
|
||||
However, you may do so only on Your own behalf, and not on
|
||||
behalf of the Initial Developer or any Contributor. You
|
||||
must make it absolutely clear that any such warranty,
|
||||
support, indemnity or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify the Initial
|
||||
Developer and every Contributor for any liability incurred
|
||||
by the Initial Developer or such Contributor as a result of
|
||||
warranty, support, indemnity or liability terms You offer.
|
||||
|
||||
3.5. Distribution of Executable Versions.
|
||||
You may distribute the Executable form of the Covered
|
||||
Software under the terms of this License or under the terms
|
||||
of a license of Your choice, which may contain terms
|
||||
different from this License, provided that You are in
|
||||
compliance with the terms of this License and that the
|
||||
license for the Executable form does not attempt to limit
|
||||
or alter the recipient's rights in the Source Code form
|
||||
from the rights set forth in this License. If You
|
||||
distribute the Covered Software in Executable form under a
|
||||
different license, You must make it absolutely clear that
|
||||
any terms which differ from this License are offered by You
|
||||
alone, not by the Initial Developer or Contributor. You
|
||||
hereby agree to indemnify the Initial Developer and every
|
||||
Contributor for any liability incurred by the Initial
|
||||
Developer or such Contributor as a result of any such terms
|
||||
You offer.
|
||||
|
||||
3.6. Larger Works.
|
||||
You may create a Larger Work by combining Covered Software
|
||||
with other code not governed by the terms of this License
|
||||
and distribute the Larger Work as a single product. In such
|
||||
a case, You must make sure the requirements of this License
|
||||
are fulfilled for the Covered Software.
|
||||
|
||||
4. Versions of the License.
|
||||
|
||||
4.1. New Versions.
|
||||
Sun Microsystems, Inc. is the initial license steward and
|
||||
may publish revised and/or new versions of this License
|
||||
from time to time. Each version will be given a
|
||||
distinguishing version number. Except as provided in
|
||||
Section 4.3, no one other than the license steward has the
|
||||
right to modify this License.
|
||||
|
||||
4.2. Effect of New Versions.
|
||||
You may always continue to use, distribute or otherwise
|
||||
make the Covered Software available under the terms of the
|
||||
version of the License under which You originally received
|
||||
the Covered Software. If the Initial Developer includes a
|
||||
notice in the Original Software prohibiting it from being
|
||||
distributed or otherwise made available under any
|
||||
subsequent version of the License, You must distribute and
|
||||
make the Covered Software available under the terms of the
|
||||
version of the License under which You originally received
|
||||
the Covered Software. Otherwise, You may also choose to
|
||||
use, distribute or otherwise make the Covered Software
|
||||
available under the terms of any subsequent version of the
|
||||
License published by the license steward.
|
||||
|
||||
4.3. Modified Versions.
|
||||
When You are an Initial Developer and You want to create a
|
||||
new license for Your Original Software, You may create and
|
||||
use a modified version of this License if You: (a) rename
|
||||
the license and remove any references to the name of the
|
||||
license steward (except to note that the license differs
|
||||
from this License); and (b) otherwise make it clear that
|
||||
the license contains terms which differ from this License.
|
||||
|
||||
5. DISCLAIMER OF WARRANTY.
|
||||
|
||||
COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS"
|
||||
BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
|
||||
INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED
|
||||
SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR
|
||||
PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND
|
||||
PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY
|
||||
COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE
|
||||
INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF
|
||||
ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF
|
||||
WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
|
||||
ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS
|
||||
DISCLAIMER.
|
||||
|
||||
6. TERMINATION.
|
||||
|
||||
6.1. This License and the rights granted hereunder will
|
||||
terminate automatically if You fail to comply with terms
|
||||
herein and fail to cure such breach within 30 days of
|
||||
becoming aware of the breach. Provisions which, by their
|
||||
nature, must remain in effect beyond the termination of
|
||||
this License shall survive.
|
||||
|
||||
6.2. If You assert a patent infringement claim (excluding
|
||||
declaratory judgment actions) against Initial Developer or
|
||||
a Contributor (the Initial Developer or Contributor against
|
||||
whom You assert such claim is referred to as "Participant")
|
||||
alleging that the Participant Software (meaning the
|
||||
Contributor Version where the Participant is a Contributor
|
||||
or the Original Software where the Participant is the
|
||||
Initial Developer) directly or indirectly infringes any
|
||||
patent, then any and all rights granted directly or
|
||||
indirectly to You by such Participant, the Initial
|
||||
Developer (if the Initial Developer is not the Participant)
|
||||
and all Contributors under Sections 2.1 and/or 2.2 of this
|
||||
License shall, upon 60 days notice from Participant
|
||||
terminate prospectively and automatically at the expiration
|
||||
of such 60 day notice period, unless if within such 60 day
|
||||
period You withdraw Your claim with respect to the
|
||||
Participant Software against such Participant either
|
||||
unilaterally or pursuant to a written agreement with
|
||||
Participant.
|
||||
|
||||
6.3. In the event of termination under Sections 6.1 or 6.2
|
||||
above, all end user licenses that have been validly granted
|
||||
by You or any distributor hereunder prior to termination
|
||||
(excluding licenses granted to You by any distributor)
|
||||
shall survive termination.
|
||||
|
||||
7. LIMITATION OF LIABILITY.
|
||||
|
||||
UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
|
||||
(INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE
|
||||
INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF
|
||||
COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE
|
||||
LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
|
||||
CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT
|
||||
LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK
|
||||
STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
|
||||
COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
|
||||
INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
|
||||
LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL
|
||||
INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT
|
||||
APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO
|
||||
NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT
|
||||
APPLY TO YOU.
|
||||
|
||||
8. U.S. GOVERNMENT END USERS.
|
||||
|
||||
The Covered Software is a "commercial item," as that term is
|
||||
defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial
|
||||
computer software" (as that term is defined at 48 C.F.R. ¤
|
||||
252.227-7014(a)(1)) and "commercial computer software
|
||||
documentation" as such terms are used in 48 C.F.R. 12.212 (Sept.
|
||||
1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1
|
||||
through 227.7202-4 (June 1995), all U.S. Government End Users
|
||||
acquire Covered Software with only those rights set forth herein.
|
||||
This U.S. Government Rights clause is in lieu of, and supersedes,
|
||||
any other FAR, DFAR, or other clause or provision that addresses
|
||||
Government rights in computer software under this License.
|
||||
|
||||
9. MISCELLANEOUS.
|
||||
|
||||
This License represents the complete agreement concerning subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the
|
||||
extent necessary to make it enforceable. This License shall be
|
||||
governed by the law of the jurisdiction specified in a notice
|
||||
contained within the Original Software (except to the extent
|
||||
applicable law, if any, provides otherwise), excluding such
|
||||
jurisdiction's conflict-of-law provisions. Any litigation
|
||||
relating to this License shall be subject to the jurisdiction of
|
||||
the courts located in the jurisdiction and venue specified in a
|
||||
notice contained within the Original Software, with the losing
|
||||
party responsible for costs, including, without limitation, court
|
||||
costs and reasonable attorneys' fees and expenses. The
|
||||
application of the United Nations Convention on Contracts for the
|
||||
International Sale of Goods is expressly excluded. Any law or
|
||||
regulation which provides that the language of a contract shall
|
||||
be construed against the drafter shall not apply to this License.
|
||||
You agree that You alone are responsible for compliance with the
|
||||
United States export administration regulations (and the export
|
||||
control laws and regulation of any other countries) when You use,
|
||||
distribute or otherwise make available any Covered Software.
|
||||
|
||||
10. RESPONSIBILITY FOR CLAIMS.
|
||||
|
||||
As between Initial Developer and the Contributors, each party is
|
||||
responsible for claims and damages arising, directly or
|
||||
indirectly, out of its utilization of rights under this License
|
||||
and You agree to work with Initial Developer and Contributors to
|
||||
distribute such responsibility on an equitable basis. Nothing
|
||||
herein is intended or shall be deemed to constitute any admission
|
||||
of liability.
|
||||
2
pom.xml
2
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>tools.profiler</groupId>
|
||||
<artifactId>async-profiler</artifactId>
|
||||
<version>1.8.8</version>
|
||||
<version>1.8.3</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>async-profiler</name>
|
||||
|
||||
29
profiler.sh
29
profiler.sh
@@ -7,6 +7,7 @@ usage() {
|
||||
echo " start start profiling and return immediately"
|
||||
echo " resume resume profiling without resetting collected data"
|
||||
echo " stop stop profiling"
|
||||
echo " 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"
|
||||
@@ -18,25 +19,24 @@ usage() {
|
||||
echo " -f filename dump output to <filename>"
|
||||
echo " -i interval sampling interval in nanoseconds"
|
||||
echo " -j jstackdepth maximum Java stack depth"
|
||||
echo " -b bufsize frame buffer size"
|
||||
echo " -t profile different threads separately"
|
||||
echo " -s simple class names instead of FQN"
|
||||
echo " -g print method signatures"
|
||||
echo " -a annotate Java method names"
|
||||
echo " -o fmt output format: summary|traces|flat|collapsed|svg|tree|jfr"
|
||||
echo " -o fmt output format: flat|collapsed|html|tree|jfr"
|
||||
echo " -I include output only stack traces containing the specified pattern"
|
||||
echo " -X exclude exclude stack traces with the specified pattern"
|
||||
echo " -v, --version display version string"
|
||||
echo ""
|
||||
echo " --title string SVG title"
|
||||
echo " --width px SVG width"
|
||||
echo " --height px SVG frame height"
|
||||
echo " --minwidth px skip frames smaller than px"
|
||||
echo " --title string FlameGraph title"
|
||||
echo " --minwidth pct skip frames smaller than pct%"
|
||||
echo " --reverse generate stack-reversed FlameGraph / Call tree"
|
||||
echo ""
|
||||
echo " --all-kernel only include kernel-mode events"
|
||||
echo " --all-user only include user-mode events"
|
||||
echo " --cstack mode how to traverse C stack: fp|lbr|no"
|
||||
echo " --begin function begin profiling when function is executed"
|
||||
echo " --end function end profiling when function is executed"
|
||||
echo ""
|
||||
echo "<pid> is a numeric process ID of the target JVM"
|
||||
echo " or 'jps' keyword to find running JVM automatically"
|
||||
@@ -44,7 +44,7 @@ usage() {
|
||||
echo ""
|
||||
echo "Example: $0 -d 30 -f profile.svg 3456"
|
||||
echo " $0 start -i 999000 jps"
|
||||
echo " $0 stop -o summary,flat jps"
|
||||
echo " $0 stop -o flat jps"
|
||||
echo " $0 -d 5 -e alloc MyAppName"
|
||||
exit 1
|
||||
}
|
||||
@@ -110,11 +110,16 @@ while [ $# -gt 0 ]; do
|
||||
start|resume|stop|check|status|list|collect)
|
||||
ACTION="$1"
|
||||
;;
|
||||
jstack)
|
||||
ACTION="start"
|
||||
EVENT="jstack"
|
||||
PARAMS="$PARAMS,threads"
|
||||
;;
|
||||
-v|--version)
|
||||
ACTION="version"
|
||||
;;
|
||||
-e)
|
||||
EVENT="$2"
|
||||
EVENT="$(echo "$2" | sed 's/,/,event=/g')"
|
||||
shift
|
||||
;;
|
||||
-d)
|
||||
@@ -134,10 +139,6 @@ while [ $# -gt 0 ]; do
|
||||
PARAMS="$PARAMS,jstackdepth=$2"
|
||||
shift
|
||||
;;
|
||||
-b)
|
||||
PARAMS="$PARAMS,framebuf=$2"
|
||||
shift
|
||||
;;
|
||||
-t)
|
||||
PARAMS="$PARAMS,threads"
|
||||
;;
|
||||
@@ -190,6 +191,10 @@ while [ $# -gt 0 ]; do
|
||||
PARAMS="$PARAMS,cstack=$2"
|
||||
shift
|
||||
;;
|
||||
--begin|--end)
|
||||
PARAMS="$PARAMS,${1#--}=$2"
|
||||
shift
|
||||
;;
|
||||
--safe-mode)
|
||||
PARAMS="$PARAMS,safemode=$2"
|
||||
shift
|
||||
|
||||
@@ -14,100 +14,63 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include "allocTracer.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "stackFrame.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
int AllocTracer::_trap_kind;
|
||||
Trap AllocTracer::_in_new_tlab;
|
||||
Trap AllocTracer::_outside_tlab;
|
||||
|
||||
Trap AllocTracer::_in_new_tlab2;
|
||||
Trap AllocTracer::_outside_tlab2;
|
||||
|
||||
u64 AllocTracer::_interval;
|
||||
volatile u64 AllocTracer::_allocated_bytes;
|
||||
|
||||
|
||||
// Resolve the address of the intercepted function
|
||||
bool Trap::resolve(NativeCodeCache* libjvm, const char* func_name) {
|
||||
if (_entry != NULL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uintptr_t addr = (uintptr_t)libjvm->findSymbolByPrefix(func_name);
|
||||
if (addr == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(__arm__) || defined(__thumb__)
|
||||
if (addr & 1) {
|
||||
addr ^= 1;
|
||||
_breakpoint_insn = BREAKPOINT_THUMB;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Make the entry point writable, so we can rewrite instructions
|
||||
long page_size = sysconf(_SC_PAGESIZE);
|
||||
uintptr_t page_start = addr & -page_size;
|
||||
if (mprotect((void*)page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_entry = (instruction_t*)addr;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Insert breakpoint at the very first instruction
|
||||
void Trap::install() {
|
||||
if (_entry != NULL) {
|
||||
_saved_insn = *_entry;
|
||||
*_entry = _breakpoint_insn;
|
||||
flushCache(_entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear breakpoint - restore the original instruction
|
||||
void Trap::uninstall() {
|
||||
if (_entry != NULL) {
|
||||
*_entry = _saved_insn;
|
||||
flushCache(_entry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Called whenever our breakpoint trap is hit
|
||||
void AllocTracer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
StackFrame frame(ucontext);
|
||||
int event_type;
|
||||
uintptr_t total_size;
|
||||
uintptr_t instance_size;
|
||||
|
||||
// PC points either to BREAKPOINT instruction or to the next one
|
||||
if (frame.pc() - (uintptr_t)_in_new_tlab._entry <= sizeof(instruction_t)) {
|
||||
// send_allocation_in_new_tlab_event(KlassHandle klass, size_t tlab_size, size_t alloc_size)
|
||||
recordAllocation(ucontext, frame, frame.arg0(), frame.arg1(), false);
|
||||
} else if (frame.pc() - (uintptr_t)_outside_tlab._entry <= sizeof(instruction_t)) {
|
||||
// send_allocation_outside_tlab_event(KlassHandle klass, size_t alloc_size);
|
||||
recordAllocation(ucontext, frame, frame.arg0(), frame.arg1(), true);
|
||||
} else if (frame.pc() - (uintptr_t)_in_new_tlab2._entry <= sizeof(instruction_t)) {
|
||||
if (_in_new_tlab.covers(frame.pc())) {
|
||||
// send_allocation_in_new_tlab(Klass* klass, HeapWord* obj, size_t tlab_size, size_t alloc_size, Thread* thread)
|
||||
recordAllocation(ucontext, frame, frame.arg0(), frame.arg2(), false);
|
||||
} else if (frame.pc() - (uintptr_t)_outside_tlab2._entry <= sizeof(instruction_t)) {
|
||||
// send_allocation_in_new_tlab_event(KlassHandle klass, size_t tlab_size, size_t alloc_size)
|
||||
event_type = BCI_ALLOC;
|
||||
total_size = _trap_kind == 1 ? frame.arg2() : frame.arg1();
|
||||
instance_size = _trap_kind == 1 ? frame.arg3() : frame.arg2();
|
||||
} else if (_outside_tlab.covers(frame.pc())) {
|
||||
// send_allocation_outside_tlab(Klass* klass, HeapWord* obj, size_t alloc_size, Thread* thread)
|
||||
recordAllocation(ucontext, frame, frame.arg0(), frame.arg2(), true);
|
||||
// send_allocation_outside_tlab_event(KlassHandle klass, size_t alloc_size);
|
||||
event_type = BCI_ALLOC_OUTSIDE_TLAB;
|
||||
total_size = _trap_kind == 1 ? frame.arg2() : frame.arg1();
|
||||
instance_size = 0;
|
||||
} else {
|
||||
// Not our trap
|
||||
return;
|
||||
}
|
||||
|
||||
// Leave the trapped function by simulating "ret" instruction
|
||||
uintptr_t klass = frame.arg0();
|
||||
frame.ret();
|
||||
|
||||
if (_enabled) {
|
||||
// TODO: _enabled also uses traps
|
||||
recordAllocation(ucontext, event_type, klass, total_size, instance_size);
|
||||
}
|
||||
}
|
||||
|
||||
void AllocTracer::recordAllocation(void* ucontext, StackFrame& frame, uintptr_t rklass, uintptr_t rsize, bool outside_tlab) {
|
||||
// Leave the trapped function by simulating "ret" instruction
|
||||
frame.ret();
|
||||
|
||||
void AllocTracer::recordAllocation(void* ucontext, int event_type, uintptr_t rklass,
|
||||
uintptr_t total_size, uintptr_t instance_size) {
|
||||
if (_interval) {
|
||||
// Do not record allocation unless allocated at least _interval bytes
|
||||
while (true) {
|
||||
u64 prev = _allocated_bytes;
|
||||
u64 next = prev + rsize;
|
||||
u64 next = prev + total_size;
|
||||
if (next < _interval) {
|
||||
if (__sync_bool_compare_and_swap(&_allocated_bytes, prev, next)) {
|
||||
return;
|
||||
@@ -120,34 +83,46 @@ void AllocTracer::recordAllocation(void* ucontext, StackFrame& frame, uintptr_t
|
||||
}
|
||||
}
|
||||
|
||||
AllocEvent event;
|
||||
event._class_id = 0;
|
||||
event._total_size = total_size;
|
||||
event._instance_size = instance_size;
|
||||
|
||||
if (VMStructs::hasClassNames()) {
|
||||
VMSymbol* symbol = VMKlass::fromHandle(rklass)->name();
|
||||
if (outside_tlab) {
|
||||
// Invert the last bit to distinguish jmethodID from the allocation in new TLAB
|
||||
Profiler::_instance.recordSample(ucontext, rsize, BCI_SYMBOL_OUTSIDE_TLAB, (jmethodID)((uintptr_t)symbol ^ 1));
|
||||
} else {
|
||||
Profiler::_instance.recordSample(ucontext, rsize, BCI_SYMBOL, (jmethodID)symbol);
|
||||
}
|
||||
} else {
|
||||
Profiler::_instance.recordSample(ucontext, rsize, BCI_SYMBOL, NULL);
|
||||
event._class_id = Profiler::_instance.classMap()->lookup(symbol->body(), symbol->length());
|
||||
}
|
||||
|
||||
Profiler::_instance.recordSample(ucontext, total_size, event_type, &event);
|
||||
}
|
||||
|
||||
Error AllocTracer::check(Arguments& args) {
|
||||
NativeCodeCache* libjvm = VMStructs::libjvm();
|
||||
|
||||
if (_in_new_tlab2.resolve(libjvm, "_ZN11AllocTracer27send_allocation_in_new_tlab") &&
|
||||
_outside_tlab2.resolve(libjvm, "_ZN11AllocTracer28send_allocation_outside_tlab")) {
|
||||
return Error::OK; // JDK 10+
|
||||
} else if (_in_new_tlab2.resolve(libjvm, "_ZN11AllocTracer33send_allocation_in_new_tlab_eventE11KlassHandleP8HeapWord") &&
|
||||
_outside_tlab2.resolve(libjvm, "_ZN11AllocTracer34send_allocation_outside_tlab_eventE11KlassHandleP8HeapWord")) {
|
||||
return Error::OK; // JDK 8u262+
|
||||
} else if (_in_new_tlab.resolve(libjvm, "_ZN11AllocTracer33send_allocation_in_new_tlab_event") &&
|
||||
_outside_tlab.resolve(libjvm, "_ZN11AllocTracer34send_allocation_outside_tlab_event")) {
|
||||
return Error::OK; // JDK 7-9
|
||||
if (_in_new_tlab.entry() != 0 && _outside_tlab.entry() != 0) {
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
return Error("No AllocTracer symbols found. Are JDK debug symbols installed?");
|
||||
NativeCodeCache* libjvm = VMStructs::libjvm();
|
||||
const void* ne;
|
||||
const void* oe;
|
||||
|
||||
if ((ne = libjvm->findSymbolByPrefix("_ZN11AllocTracer27send_allocation_in_new_tlab")) != NULL &&
|
||||
(oe = libjvm->findSymbolByPrefix("_ZN11AllocTracer28send_allocation_outside_tlab")) != NULL) {
|
||||
_trap_kind = 1; // JDK 10+
|
||||
} else if ((ne = libjvm->findSymbolByPrefix("_ZN11AllocTracer33send_allocation_in_new_tlab_eventE11KlassHandleP8HeapWord")) != NULL &&
|
||||
(oe = libjvm->findSymbolByPrefix("_ZN11AllocTracer34send_allocation_outside_tlab_eventE11KlassHandleP8HeapWord")) != NULL) {
|
||||
_trap_kind = 1; // JDK 8u262+
|
||||
} else if ((ne = libjvm->findSymbolByPrefix("_ZN11AllocTracer33send_allocation_in_new_tlab_event")) != NULL &&
|
||||
(oe = libjvm->findSymbolByPrefix("_ZN11AllocTracer34send_allocation_outside_tlab_event")) != NULL) {
|
||||
_trap_kind = 2; // JDK 7-9
|
||||
} else {
|
||||
return Error("No AllocTracer symbols found. Are JDK debug symbols installed?");
|
||||
}
|
||||
|
||||
if (!_in_new_tlab.assign(ne) || !_outside_tlab.assign(oe)) {
|
||||
return Error("Unable to install allocation trap");
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error AllocTracer::start(Arguments& args) {
|
||||
@@ -163,8 +138,6 @@ Error AllocTracer::start(Arguments& args) {
|
||||
|
||||
_in_new_tlab.install();
|
||||
_outside_tlab.install();
|
||||
_in_new_tlab2.install();
|
||||
_outside_tlab2.install();
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
@@ -172,6 +145,4 @@ Error AllocTracer::start(Arguments& args) {
|
||||
void AllocTracer::stop() {
|
||||
_in_new_tlab.uninstall();
|
||||
_outside_tlab.uninstall();
|
||||
_in_new_tlab2.uninstall();
|
||||
_outside_tlab2.uninstall();
|
||||
}
|
||||
|
||||
@@ -19,45 +19,23 @@
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include "arch.h"
|
||||
#include "codeCache.h"
|
||||
#include "engine.h"
|
||||
#include "stackFrame.h"
|
||||
|
||||
|
||||
// Describes OpenJDK function being intercepted
|
||||
class Trap {
|
||||
private:
|
||||
instruction_t* _entry;
|
||||
instruction_t _breakpoint_insn;
|
||||
instruction_t _saved_insn;
|
||||
|
||||
public:
|
||||
Trap() : _entry(NULL), _breakpoint_insn(BREAKPOINT) {
|
||||
}
|
||||
|
||||
bool resolve(NativeCodeCache* libjvm, const char* func_name);
|
||||
void install();
|
||||
void uninstall();
|
||||
|
||||
friend class AllocTracer;
|
||||
};
|
||||
#include "trap.h"
|
||||
|
||||
|
||||
class AllocTracer : public Engine {
|
||||
private:
|
||||
// JDK 7-9
|
||||
static int _trap_kind;
|
||||
static Trap _in_new_tlab;
|
||||
static Trap _outside_tlab;
|
||||
// JDK 10+, 8u262+
|
||||
static Trap _in_new_tlab2;
|
||||
static Trap _outside_tlab2;
|
||||
|
||||
static u64 _interval;
|
||||
static volatile u64 _allocated_bytes;
|
||||
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
static void recordAllocation(void* ucontext, StackFrame& frame, uintptr_t rklass, uintptr_t rsize, bool outside_tlab);
|
||||
|
||||
static void recordAllocation(void* ucontext, int event_type, uintptr_t rklass,
|
||||
uintptr_t total_size, uintptr_t instance_size);
|
||||
|
||||
public:
|
||||
const char* name() {
|
||||
|
||||
@@ -135,21 +135,6 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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("summary,traces=" + maxTraces);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump flat profile, i.e. the histogram of the hottest methods
|
||||
*
|
||||
@@ -159,7 +144,7 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
@Override
|
||||
public String dumpFlat(int maxMethods) {
|
||||
try {
|
||||
return execute0("summary,flat=" + maxMethods);
|
||||
return execute0("flat=" + maxMethods);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,5 @@ public interface AsyncProfilerMXBean {
|
||||
String execute(String command) throws IllegalArgumentException, java.io.IOException;
|
||||
|
||||
String dumpCollapsed(Counter counter);
|
||||
String dumpTraces(int maxTraces);
|
||||
String dumpFlat(int maxMethods);
|
||||
}
|
||||
|
||||
@@ -58,16 +58,13 @@ const size_t EXTRA_BUF_SIZE = 512;
|
||||
// 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)
|
||||
// svg[=C] - produce Flame Graph in SVG format
|
||||
// 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
|
||||
// summary - dump profiling summary (number of collected samples of each type)
|
||||
// traces[=N] - dump top N call traces
|
||||
// flat[=N] - dump top N methods (aka flat profile)
|
||||
// interval=N - sampling interval in ns (default: 10'000'000, i.e. 10 ms)
|
||||
// jstackdepth=N - maximum Java stack depth (default: 2048)
|
||||
// framebuf=N - size of the buffer for stack frames (default: 1'000'000)
|
||||
// safemode=BITS - disable stack recovery techniques (default: 0, i.e. everything enabled)
|
||||
// file=FILENAME - output file name for dumping
|
||||
// filter=FILTER - thread filter
|
||||
@@ -82,10 +79,10 @@ const size_t EXTRA_BUF_SIZE = 512;
|
||||
// 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
|
||||
// width=PX - FlameGraph image width
|
||||
// height=PX - FlameGraph frame height
|
||||
// minwidth=PX - FlameGraph minimum frame width
|
||||
// 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
|
||||
@@ -135,7 +132,7 @@ Error Arguments::parse(const char* args) {
|
||||
_output = OUTPUT_COLLAPSED;
|
||||
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
|
||||
CASE2("flamegraph", "svg")
|
||||
CASE2("flamegraph", "html")
|
||||
_output = OUTPUT_FLAMEGRAPH;
|
||||
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
|
||||
@@ -146,15 +143,8 @@ Error Arguments::parse(const char* args) {
|
||||
CASE("jfr")
|
||||
_output = OUTPUT_JFR;
|
||||
|
||||
CASE("summary")
|
||||
_output = OUTPUT_TEXT;
|
||||
|
||||
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);
|
||||
|
||||
// Basic options
|
||||
@@ -162,7 +152,10 @@ Error Arguments::parse(const char* args) {
|
||||
if (value == NULL || value[0] == 0) {
|
||||
return Error("event must not be empty");
|
||||
}
|
||||
_event = value;
|
||||
|
||||
if (!addEvent(value)) {
|
||||
return Error("multiple incompatible events");
|
||||
}
|
||||
|
||||
CASE("interval")
|
||||
if (value == NULL || (_interval = parseUnits(value)) <= 0) {
|
||||
@@ -174,11 +167,6 @@ Error Arguments::parse(const char* args) {
|
||||
return Error("jstackdepth must be > 0");
|
||||
}
|
||||
|
||||
CASE("framebuf")
|
||||
if (value == NULL || (_framebuf = atoi(value)) <= 0) {
|
||||
return Error("framebuf must be > 0");
|
||||
}
|
||||
|
||||
CASE("safemode")
|
||||
_safe_mode = value == NULL ? INT_MAX : atoi(value);
|
||||
|
||||
@@ -201,9 +189,6 @@ Error Arguments::parse(const char* args) {
|
||||
CASE("threads")
|
||||
_threads = true;
|
||||
|
||||
CASE("log")
|
||||
_log = value == NULL || (strcmp(value, "none") != 0 && strcmp(value, "/dev/null") != 0);
|
||||
|
||||
CASE("allkernel")
|
||||
_ring = RING_KERNEL;
|
||||
|
||||
@@ -234,16 +219,16 @@ Error Arguments::parse(const char* args) {
|
||||
CASE("ann")
|
||||
_style |= STYLE_ANNOTATE;
|
||||
|
||||
CASE("begin")
|
||||
_begin = value;
|
||||
|
||||
CASE("end")
|
||||
_end = value;
|
||||
|
||||
// FlameGraph options
|
||||
CASE("title")
|
||||
if (value != NULL) _title = value;
|
||||
|
||||
CASE("width")
|
||||
if (value != NULL) _width = atoi(value);
|
||||
|
||||
CASE("height")
|
||||
if (value != NULL) _height = atoi(value);
|
||||
|
||||
CASE("minwidth")
|
||||
if (value != NULL) _minwidth = atof(value);
|
||||
|
||||
@@ -258,7 +243,6 @@ Error Arguments::parse(const char* args) {
|
||||
|
||||
if (_file != NULL && _output == OUTPUT_NONE) {
|
||||
_output = detectOutputFormat(_file);
|
||||
_dump_traces = 200;
|
||||
_dump_flat = 200;
|
||||
}
|
||||
|
||||
@@ -269,6 +253,21 @@ Error Arguments::parse(const char* args) {
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
bool Arguments::addEvent(const char* event) {
|
||||
if (strcmp(event, EVENT_ALLOC) == 0) {
|
||||
_events |= EK_ALLOC;
|
||||
} else if (strcmp(event, EVENT_LOCK) == 0) {
|
||||
_events |= EK_LOCK;
|
||||
} else {
|
||||
if (_events & EK_CPU) {
|
||||
return false;
|
||||
}
|
||||
_events |= EK_CPU;
|
||||
_event_desc = event;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// The linked list of string offsets is embedded right into _buf array
|
||||
void Arguments::appendToEmbeddedList(int& list, char* value) {
|
||||
((int*)value)[-1] = list;
|
||||
@@ -319,17 +318,15 @@ const char* Arguments::expandFilePattern(char* dest, size_t max_size, const char
|
||||
Output Arguments::detectOutputFormat(const char* file) {
|
||||
const char* ext = strrchr(file, '.');
|
||||
if (ext != NULL) {
|
||||
if (strcmp(ext, ".svg") == 0) {
|
||||
if (strcmp(ext, ".html") == 0) {
|
||||
return OUTPUT_FLAMEGRAPH;
|
||||
} else if (strcmp(ext, ".html") == 0) {
|
||||
return OUTPUT_TREE;
|
||||
} else if (strcmp(ext, ".jfr") == 0) {
|
||||
return OUTPUT_JFR;
|
||||
} else if (strcmp(ext, ".collapsed") == 0 || strcmp(ext, ".folded") == 0) {
|
||||
return OUTPUT_COLLAPSED;
|
||||
}
|
||||
}
|
||||
return OUTPUT_TEXT;
|
||||
return OUTPUT_FLAT;
|
||||
}
|
||||
|
||||
long Arguments::parseUnits(const char* str) {
|
||||
@@ -353,11 +350,11 @@ long Arguments::parseUnits(const char* str) {
|
||||
}
|
||||
|
||||
Arguments::~Arguments() {
|
||||
free(_buf);
|
||||
if (!_shared) free(_buf);
|
||||
}
|
||||
|
||||
void Arguments::save(Arguments& other) {
|
||||
free(_buf);
|
||||
if (!_shared) free(_buf);
|
||||
*this = other;
|
||||
other._buf = NULL;
|
||||
other._shared = true;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
|
||||
const long DEFAULT_INTERVAL = 10000000; // 10 ms
|
||||
const int DEFAULT_FRAMEBUF = 1000000;
|
||||
const int DEFAULT_JSTACKDEPTH = 2048;
|
||||
|
||||
const char* const EVENT_CPU = "cpu";
|
||||
@@ -29,6 +28,7 @@ const char* const EVENT_ALLOC = "alloc";
|
||||
const char* const EVENT_LOCK = "lock";
|
||||
const char* const EVENT_WALL = "wall";
|
||||
const char* const EVENT_ITIMER = "itimer";
|
||||
const char* const EVENT_JSTACK = "jstack";
|
||||
|
||||
enum Action {
|
||||
ACTION_NONE,
|
||||
@@ -54,6 +54,12 @@ enum Ring {
|
||||
RING_USER
|
||||
};
|
||||
|
||||
enum EventKind {
|
||||
EK_CPU = 1,
|
||||
EK_ALLOC = 2,
|
||||
EK_LOCK = 4
|
||||
};
|
||||
|
||||
enum Style {
|
||||
STYLE_SIMPLE = 1,
|
||||
STYLE_DOTTED = 2,
|
||||
@@ -70,7 +76,7 @@ enum CStack {
|
||||
|
||||
enum Output {
|
||||
OUTPUT_NONE,
|
||||
OUTPUT_TEXT,
|
||||
OUTPUT_FLAT,
|
||||
OUTPUT_COLLAPSED,
|
||||
OUTPUT_FLAMEGRAPH,
|
||||
OUTPUT_TREE,
|
||||
@@ -101,6 +107,7 @@ class Error {
|
||||
class Arguments {
|
||||
private:
|
||||
char* _buf;
|
||||
bool _shared;
|
||||
|
||||
void appendToEmbeddedList(int& list, char* value);
|
||||
|
||||
@@ -113,54 +120,50 @@ class Arguments {
|
||||
Action _action;
|
||||
Counter _counter;
|
||||
Ring _ring;
|
||||
const char* _event;
|
||||
int _events;
|
||||
const char* _event_desc;
|
||||
long _interval;
|
||||
int _jstackdepth;
|
||||
int _framebuf;
|
||||
int _safe_mode;
|
||||
const char* _file;
|
||||
const char* _filter;
|
||||
int _include;
|
||||
int _exclude;
|
||||
bool _threads;
|
||||
bool _log;
|
||||
int _style;
|
||||
CStack _cstack;
|
||||
Output _output;
|
||||
int _dump_traces;
|
||||
int _dump_flat;
|
||||
const char* _begin;
|
||||
const char* _end;
|
||||
// FlameGraph parameters
|
||||
const char* _title;
|
||||
int _width;
|
||||
int _height;
|
||||
double _minwidth;
|
||||
bool _reverse;
|
||||
|
||||
Arguments() :
|
||||
_buf(NULL),
|
||||
_shared(false),
|
||||
_action(ACTION_NONE),
|
||||
_counter(COUNTER_SAMPLES),
|
||||
_ring(RING_ANY),
|
||||
_event(EVENT_CPU),
|
||||
_events(0),
|
||||
_event_desc(NULL),
|
||||
_interval(0),
|
||||
_jstackdepth(DEFAULT_JSTACKDEPTH),
|
||||
_framebuf(DEFAULT_FRAMEBUF),
|
||||
_safe_mode(0),
|
||||
_file(NULL),
|
||||
_filter(NULL),
|
||||
_include(0),
|
||||
_exclude(0),
|
||||
_threads(false),
|
||||
_log(true),
|
||||
_style(0),
|
||||
_cstack(CSTACK_DEFAULT),
|
||||
_output(OUTPUT_NONE),
|
||||
_dump_traces(0),
|
||||
_dump_flat(0),
|
||||
_begin(NULL),
|
||||
_end(NULL),
|
||||
_title("Flame Graph"),
|
||||
_width(1200),
|
||||
_height(16),
|
||||
_minwidth(0.25),
|
||||
_minwidth(0),
|
||||
_reverse(false) {
|
||||
}
|
||||
|
||||
@@ -170,7 +173,10 @@ class Arguments {
|
||||
|
||||
Error parse(const char* args);
|
||||
|
||||
bool addEvent(const char* event);
|
||||
|
||||
friend class FrameName;
|
||||
friend class Recording;
|
||||
};
|
||||
|
||||
#endif // _ARGUMENTS_H
|
||||
|
||||
246
src/callTraceStorage.cpp
Normal file
246
src/callTraceStorage.cpp
Normal file
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "callTraceStorage.h"
|
||||
#include "os.h"
|
||||
|
||||
|
||||
static const u32 INITIAL_CAPACITY = 65536;
|
||||
static const u32 CALL_TRACE_CHUNK = 8 * 1024 * 1024;
|
||||
static const size_t PAGE_ALIGNMENT = sysconf(_SC_PAGESIZE) - 1;
|
||||
|
||||
|
||||
class LongHashTable {
|
||||
private:
|
||||
LongHashTable* _prev;
|
||||
void* _padding0;
|
||||
u32 _capacity;
|
||||
u32 _padding1[15];
|
||||
volatile u32 _size;
|
||||
u32 _padding2[15];
|
||||
|
||||
static size_t getSize(u32 capacity) {
|
||||
size_t size = sizeof(LongHashTable) + (sizeof(u64) + sizeof(CallTraceSample)) * capacity;
|
||||
return (size + PAGE_ALIGNMENT) & ~PAGE_ALIGNMENT;
|
||||
}
|
||||
|
||||
public:
|
||||
static LongHashTable* allocate(LongHashTable* prev, u32 capacity) {
|
||||
LongHashTable* table = (LongHashTable*)OS::safeAlloc(getSize(capacity));
|
||||
if (table != NULL) {
|
||||
table->_prev = prev;
|
||||
table->_capacity = capacity;
|
||||
table->_size = 0;
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
LongHashTable* destroy() {
|
||||
LongHashTable* prev = _prev;
|
||||
OS::safeFree(this, getSize(_capacity));
|
||||
return prev;
|
||||
}
|
||||
|
||||
LongHashTable* prev() {
|
||||
return _prev;
|
||||
}
|
||||
|
||||
u32 capacity() {
|
||||
return _capacity;
|
||||
}
|
||||
|
||||
u32 size() {
|
||||
return _size;
|
||||
}
|
||||
|
||||
u32 incSize() {
|
||||
return __sync_add_and_fetch(&_size, 1);
|
||||
}
|
||||
|
||||
u64* keys() {
|
||||
return (u64*)(this + 1);
|
||||
}
|
||||
|
||||
CallTraceSample* values() {
|
||||
return (CallTraceSample*)(keys() + _capacity);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
memset(keys(), 0, (sizeof(u64) + sizeof(CallTraceSample)) * _capacity);
|
||||
_size = 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
CallTraceStorage::CallTraceStorage() : _allocator(CALL_TRACE_CHUNK) {
|
||||
_current_table = LongHashTable::allocate(NULL, INITIAL_CAPACITY);
|
||||
}
|
||||
|
||||
CallTraceStorage::~CallTraceStorage() {
|
||||
while (_current_table != NULL) {
|
||||
_current_table = _current_table->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void CallTraceStorage::clear() {
|
||||
while (_current_table->prev() != NULL) {
|
||||
_current_table = _current_table->destroy();
|
||||
}
|
||||
_current_table->clear();
|
||||
_allocator.clear();
|
||||
}
|
||||
|
||||
void CallTraceStorage::collectTraces(std::map<u32, CallTrace*>& map) {
|
||||
for (LongHashTable* table = _current_table; table != NULL; table = table->prev()) {
|
||||
u64* keys = table->keys();
|
||||
CallTraceSample* values = table->values();
|
||||
u32 capacity = table->capacity();
|
||||
|
||||
for (u32 slot = 0; slot < capacity; slot++) {
|
||||
if (keys[slot] != 0) {
|
||||
map[capacity - (INITIAL_CAPACITY - 1) + slot] = values[slot].trace;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CallTraceStorage::collectSamples(std::vector<CallTraceSample*>& samples) {
|
||||
for (LongHashTable* table = _current_table; table != NULL; table = table->prev()) {
|
||||
u64* keys = table->keys();
|
||||
CallTraceSample* values = table->values();
|
||||
u32 capacity = table->capacity();
|
||||
|
||||
for (u32 slot = 0; slot < capacity; slot++) {
|
||||
if (keys[slot] != 0) {
|
||||
samples.push_back(&values[slot]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adaptation of MurmurHash64A by Austin Appleby
|
||||
u64 CallTraceStorage::calcHash(int num_frames, ASGCT_CallFrame* frames) {
|
||||
const u64 M = 0xc6a4a7935bd1e995ULL;
|
||||
const int R = 47;
|
||||
|
||||
int len = num_frames * sizeof(ASGCT_CallFrame);
|
||||
u64 h = len * M;
|
||||
|
||||
const u64* data = (const u64*)frames;
|
||||
const u64* end = data + len / 8;
|
||||
|
||||
while (data != end) {
|
||||
u64 k = *data++;
|
||||
k *= M;
|
||||
k ^= k >> R;
|
||||
k *= M;
|
||||
h ^= k;
|
||||
h *= M;
|
||||
}
|
||||
|
||||
if (len & 4) {
|
||||
h ^= *(u32*)data;
|
||||
h *= M;
|
||||
}
|
||||
|
||||
h ^= h >> R;
|
||||
h *= M;
|
||||
h ^= h >> R;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
CallTrace* CallTraceStorage::storeCallTrace(int num_frames, ASGCT_CallFrame* frames) {
|
||||
const size_t header_size = sizeof(CallTrace) - sizeof(ASGCT_CallFrame);
|
||||
CallTrace* buf = (CallTrace*)_allocator.alloc(header_size + num_frames * sizeof(ASGCT_CallFrame));
|
||||
if (buf != NULL) {
|
||||
buf->num_frames = num_frames;
|
||||
// Do not use memcpy inside signal handler
|
||||
for (int i = 0; i < num_frames; i++) {
|
||||
buf->frames[i] = frames[i];
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
CallTrace* CallTraceStorage::findCallTrace(LongHashTable* table, u64 hash) {
|
||||
u64* keys = table->keys();
|
||||
u32 capacity = table->capacity();
|
||||
u32 slot = hash & (capacity - 1);
|
||||
u32 step = 0;
|
||||
|
||||
while (keys[slot] != hash) {
|
||||
if (keys[slot] == 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (++step >= capacity) {
|
||||
return NULL;
|
||||
}
|
||||
slot = (slot + step) & (capacity - 1);
|
||||
}
|
||||
|
||||
return table->values()[slot].trace;
|
||||
}
|
||||
|
||||
u32 CallTraceStorage::put(int num_frames, ASGCT_CallFrame* frames, u64 counter) {
|
||||
u64 hash = calcHash(num_frames, frames);
|
||||
|
||||
LongHashTable* table = _current_table;
|
||||
u64* keys = table->keys();
|
||||
u32 capacity = table->capacity();
|
||||
u32 slot = hash & (capacity - 1);
|
||||
u32 step = 0;
|
||||
|
||||
while (keys[slot] != hash) {
|
||||
if (keys[slot] == 0) {
|
||||
if (!__sync_bool_compare_and_swap(&keys[slot], 0, hash)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Increment the table size, and if the load factor exceeds 0.75, reserve a new table
|
||||
if (table->incSize() == capacity * 3 / 4) {
|
||||
LongHashTable* new_table = LongHashTable::allocate(table, capacity * 2);
|
||||
if (new_table != NULL) {
|
||||
__sync_bool_compare_and_swap(&_current_table, table, new_table);
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate from a previous table to save space
|
||||
CallTrace* trace = table->prev() == NULL ? NULL : findCallTrace(table->prev(), hash);
|
||||
if (trace == NULL) {
|
||||
trace = storeCallTrace(num_frames, frames);
|
||||
}
|
||||
table->values()[slot].trace = trace;
|
||||
break;
|
||||
}
|
||||
|
||||
if (++step >= capacity) {
|
||||
// Very unlikely case of a table overflow
|
||||
return 0;
|
||||
}
|
||||
// Improved version of linear probing
|
||||
slot = (slot + step) & (capacity - 1);
|
||||
}
|
||||
|
||||
// TODO: check overhead
|
||||
CallTraceSample& s = table->values()[slot];
|
||||
atomicInc(s.samples);
|
||||
atomicInc(s.counter, counter);
|
||||
|
||||
return capacity - (INITIAL_CAPACITY - 1) + slot;
|
||||
}
|
||||
60
src/callTraceStorage.h
Normal file
60
src/callTraceStorage.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _CALLTRACESTORAGE_H
|
||||
#define _CALLTRACESTORAGE_H
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include "arch.h"
|
||||
#include "linearAllocator.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
|
||||
class LongHashTable;
|
||||
|
||||
struct CallTrace {
|
||||
int num_frames;
|
||||
ASGCT_CallFrame frames[1];
|
||||
};
|
||||
|
||||
struct CallTraceSample {
|
||||
CallTrace* trace;
|
||||
u64 samples;
|
||||
u64 counter;
|
||||
};
|
||||
|
||||
class CallTraceStorage {
|
||||
private:
|
||||
LinearAllocator _allocator;
|
||||
LongHashTable* _current_table;
|
||||
|
||||
u64 calcHash(int num_frames, ASGCT_CallFrame* frames);
|
||||
CallTrace* storeCallTrace(int num_frames, ASGCT_CallFrame* frames);
|
||||
CallTrace* findCallTrace(LongHashTable* table, u64 hash);
|
||||
|
||||
public:
|
||||
CallTraceStorage();
|
||||
~CallTraceStorage();
|
||||
|
||||
void clear();
|
||||
void collectTraces(std::map<u32, CallTrace*>& map);
|
||||
void collectSamples(std::vector<CallTraceSample*>& samples);
|
||||
|
||||
u32 put(int num_frames, ASGCT_CallFrame* frames, u64 counter);
|
||||
};
|
||||
|
||||
#endif // _CALLTRACESTORAGE
|
||||
@@ -138,7 +138,10 @@ const void* NativeCodeCache::findSymbol(const char* name) {
|
||||
}
|
||||
|
||||
const void* NativeCodeCache::findSymbolByPrefix(const char* prefix) {
|
||||
int prefix_len = strlen(prefix);
|
||||
return findSymbolByPrefix(prefix, strlen(prefix));
|
||||
}
|
||||
|
||||
const void* NativeCodeCache::findSymbolByPrefix(const char* prefix, int prefix_len) {
|
||||
for (int i = 0; i < _count; i++) {
|
||||
const char* blob_name = (const char*)_blobs[i]._method;
|
||||
if (blob_name != NULL && strncmp(blob_name, prefix, prefix_len) == 0) {
|
||||
|
||||
@@ -101,6 +101,7 @@ class NativeCodeCache : public CodeCache {
|
||||
const char* binarySearch(const void* address);
|
||||
const void* findSymbol(const char* name);
|
||||
const void* findSymbolByPrefix(const char* prefix);
|
||||
const void* findSymbolByPrefix(const char* prefix, int prefix_len);
|
||||
};
|
||||
|
||||
#endif // _CODECACHE_H
|
||||
|
||||
@@ -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.
|
||||
@@ -15,38 +15,41 @@
|
||||
*/
|
||||
|
||||
import one.jfr.ClassRef;
|
||||
import one.jfr.Frame;
|
||||
import one.jfr.Dictionary;
|
||||
import one.jfr.JfrReader;
|
||||
import one.jfr.MethodRef;
|
||||
import one.jfr.StackTrace;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Converts .jfr output produced by async-profiler to HTML Flame Graph.
|
||||
*/
|
||||
public class jfr2flame {
|
||||
|
||||
private static final int FRAME_KERNEL = 6;
|
||||
private static final int FRAME_KERNEL = 5;
|
||||
|
||||
private final JfrReader jfr;
|
||||
private final Map<Long, String> methodNames = new HashMap<>();
|
||||
private final Dictionary<String> methodNames = new Dictionary<>();
|
||||
|
||||
public jfr2flame(JfrReader jfr) {
|
||||
this.jfr = jfr;
|
||||
}
|
||||
|
||||
public void convert(final FlameGraph fg) {
|
||||
for (StackTrace stackTrace : jfr.stackTraces.values()) {
|
||||
Frame[] frames = stackTrace.frames;
|
||||
String[] trace = new String[frames.length];
|
||||
for (int i = 0; i < frames.length; i++) {
|
||||
trace[trace.length - 1 - i] = getMethodName(frames[i].method, frames[i].type);
|
||||
// Don't use lambda for faster startup
|
||||
jfr.stackTraces.forEach(new Dictionary.Visitor<StackTrace>() {
|
||||
@Override
|
||||
public void visit(long id, StackTrace stackTrace) {
|
||||
long[] methods = stackTrace.methods;
|
||||
byte[] types = stackTrace.types;
|
||||
String[] trace = new String[methods.length];
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
trace[trace.length - 1 - i] = getMethodName(methods[i], types[i]);
|
||||
}
|
||||
fg.addSample(trace, stackTrace.samples);
|
||||
}
|
||||
fg.addSample(trace, stackTrace.samples);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getMethodName(long methodId, int type) {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import one.jfr.ClassRef;
|
||||
import one.jfr.Frame;
|
||||
import one.jfr.Dictionary;
|
||||
import one.jfr.JfrReader;
|
||||
import one.jfr.MethodRef;
|
||||
import one.jfr.Sample;
|
||||
@@ -27,7 +27,6 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Converts .jfr output produced by async-profiler to nflxprofile format
|
||||
@@ -36,7 +35,7 @@ import java.util.Map;
|
||||
*/
|
||||
public class jfr2nflx {
|
||||
|
||||
private static final String[] FRAME_TYPE = {"user", "jit", "jit", "inlined", "user", "user", "kernel"};
|
||||
private static final String[] FRAME_TYPE = {"jit", "jit", "inlined", "user", "user", "kernel"};
|
||||
private static final byte[] NO_STACK = "[no_stack]".getBytes();
|
||||
|
||||
private final JfrReader jfr;
|
||||
@@ -48,9 +47,12 @@ public class jfr2nflx {
|
||||
public void dump(OutputStream out) throws IOException {
|
||||
long startTime = System.nanoTime();
|
||||
|
||||
Proto profile = new Proto(200000)
|
||||
int samples = jfr.samples.size();
|
||||
long durationTicks = samples == 0 ? 0 : jfr.samples.get(samples - 1).time - jfr.startTicks + 1;
|
||||
|
||||
final Proto profile = new Proto(200000)
|
||||
.field(1, 0.0)
|
||||
.field(2, (jfr.stopNanos - jfr.startNanos) / 1e9)
|
||||
.field(2, Math.max(jfr.durationNanos / 1e9, durationTicks / (double) jfr.ticksPerSec))
|
||||
.field(3, packSamples())
|
||||
.field(4, packDeltas())
|
||||
.field(6, "async-profiler")
|
||||
@@ -58,16 +60,20 @@ public class jfr2nflx {
|
||||
.field(8, new Proto(32).field(1, "has_samples_tid").field(2, "true"))
|
||||
.field(11, packTids());
|
||||
|
||||
Proto nodes = new Proto(10000);
|
||||
Proto node = new Proto(10000);
|
||||
final Proto nodes = new Proto(10000);
|
||||
final Proto node = new Proto(10000);
|
||||
|
||||
for (Map.Entry<Integer, StackTrace> entry : jfr.stackTraces.entrySet()) {
|
||||
profile.field(5, nodes
|
||||
.field(1, entry.getKey())
|
||||
.field(2, packNode(node, entry.getValue().frames)));
|
||||
nodes.reset();
|
||||
node.reset();
|
||||
}
|
||||
// Don't use lambda for faster startup
|
||||
jfr.stackTraces.forEach(new Dictionary.Visitor<StackTrace>() {
|
||||
@Override
|
||||
public void visit(long id, StackTrace stackTrace) {
|
||||
profile.field(5, nodes
|
||||
.field(1, (int) id)
|
||||
.field(2, packNode(node, stackTrace)));
|
||||
nodes.reset();
|
||||
node.reset();
|
||||
}
|
||||
});
|
||||
|
||||
out.write(profile.buffer(), 0, profile.size());
|
||||
|
||||
@@ -75,16 +81,19 @@ public class jfr2nflx {
|
||||
System.out.println("Wrote " + profile.size() + " bytes in " + (endTime - startTime) / 1e9 + " s");
|
||||
}
|
||||
|
||||
private Proto packNode(Proto node, Frame[] frames) {
|
||||
int top = frames.length - 1;
|
||||
node.field(1, top >= 0 ? getMethodName(frames[top].method) : NO_STACK);
|
||||
private Proto packNode(Proto node, StackTrace stackTrace) {
|
||||
long[] methods = stackTrace.methods;
|
||||
byte[] types = stackTrace.types;
|
||||
int top = methods.length - 1;
|
||||
|
||||
node.field(1, top >= 0 ? getMethodName(methods[top]) : NO_STACK);
|
||||
node.field(2, 1);
|
||||
node.field(4, top >= 0 ? FRAME_TYPE[frames[top].type] : "user");
|
||||
node.field(4, top >= 0 ? FRAME_TYPE[types[top]] : "user");
|
||||
|
||||
for (Proto frame = new Proto(100); --top >= 0; frame.reset()) {
|
||||
node.field(10, frame
|
||||
.field(1, getMethodName(frames[top].method))
|
||||
.field(2, FRAME_TYPE[frames[top].type]));
|
||||
.field(1, getMethodName(methods[top]))
|
||||
.field(2, FRAME_TYPE[types[top]]));
|
||||
}
|
||||
|
||||
return node;
|
||||
@@ -100,9 +109,10 @@ public class jfr2nflx {
|
||||
|
||||
private Proto packDeltas() {
|
||||
Proto proto = new Proto(10000);
|
||||
long prevTime = jfr.startNanos;
|
||||
double ticksPerSec = jfr.ticksPerSec;
|
||||
long prevTime = jfr.startTicks;
|
||||
for (Sample sample : jfr.samples) {
|
||||
proto.writeDouble((sample.time - prevTime) / 1e9);
|
||||
proto.writeDouble((sample.time - prevTime) / ticksPerSec);
|
||||
prevTime = sample.time;
|
||||
}
|
||||
return proto;
|
||||
|
||||
107
src/converter/one/jfr/Dictionary.java
Normal file
107
src/converter/one/jfr/Dictionary.java
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
|
||||
/**
|
||||
* Fast and compact long->Object map.
|
||||
*/
|
||||
public class Dictionary<T> {
|
||||
private static final int INITIAL_CAPACITY = 16;
|
||||
|
||||
private long[] keys;
|
||||
private Object[] values;
|
||||
private int size;
|
||||
|
||||
public Dictionary() {
|
||||
this.keys = new long[INITIAL_CAPACITY];
|
||||
this.values = new Object[INITIAL_CAPACITY];
|
||||
}
|
||||
|
||||
public void put(long key, T value) {
|
||||
if (key == 0) {
|
||||
throw new IllegalArgumentException("Zero key not allowed");
|
||||
}
|
||||
|
||||
if (++size * 2 > keys.length) {
|
||||
resize(keys.length * 2);
|
||||
}
|
||||
|
||||
int mask = keys.length - 1;
|
||||
int i = hashCode(key) & mask;
|
||||
while (keys[i] != 0 && keys[i] != key) {
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
keys[i] = key;
|
||||
values[i] = value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T get(long key) {
|
||||
int mask = keys.length - 1;
|
||||
int i = hashCode(key) & mask;
|
||||
while (keys[i] != key && keys[i] != 0) {
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
return (T) values[i];
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void forEach(Visitor<T> visitor) {
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (keys[i] != 0) {
|
||||
visitor.visit(keys[i], (T) values[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int preallocate(int count) {
|
||||
int newSize = size + count;
|
||||
if (newSize * 2 > keys.length) {
|
||||
resize(Integer.highestOneBit(newSize * 4 - 1));
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private void resize(int newCapacity) {
|
||||
long[] newKeys = new long[newCapacity];
|
||||
Object[] newValues = new Object[newCapacity];
|
||||
int mask = newKeys.length - 1;
|
||||
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (keys[i] != 0) {
|
||||
for (int j = hashCode(keys[i]) & mask; ; j = (j + 1) & mask) {
|
||||
if (newKeys[j] == 0) {
|
||||
newKeys[j] = keys[i];
|
||||
newValues[j] = values[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keys = newKeys;
|
||||
values = newValues;
|
||||
}
|
||||
|
||||
private static int hashCode(long key) {
|
||||
return (int) (key ^ (key >>> 32));
|
||||
}
|
||||
|
||||
public interface Visitor<T> {
|
||||
void visit(long key, T value);
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,8 @@
|
||||
|
||||
package one.jfr;
|
||||
|
||||
public class Frame {
|
||||
public final long method;
|
||||
public final byte type;
|
||||
class Element {
|
||||
|
||||
public Frame(long method, byte type) {
|
||||
this.method = method;
|
||||
this.type = type;
|
||||
void addChild(Element e) {
|
||||
}
|
||||
}
|
||||
49
src/converter/one/jfr/JfrClass.java
Normal file
49
src/converter/one/jfr/JfrClass.java
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class JfrClass extends Element {
|
||||
final int id;
|
||||
final String name;
|
||||
final List<JfrField> fields;
|
||||
|
||||
JfrClass(Map<String, String> attributes) {
|
||||
this.id = Integer.parseInt(attributes.get("id"));
|
||||
this.name = attributes.get("name");
|
||||
this.fields = new ArrayList<>(2);
|
||||
}
|
||||
|
||||
@Override
|
||||
void addChild(Element e) {
|
||||
if (e instanceof JfrField) {
|
||||
fields.add((JfrField) e);
|
||||
}
|
||||
}
|
||||
|
||||
JfrField field(String name) {
|
||||
for (JfrField field : fields) {
|
||||
if (field.name.equals(name)) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
31
src/converter/one/jfr/JfrField.java
Normal file
31
src/converter/one/jfr/JfrField.java
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
class JfrField extends Element {
|
||||
final String name;
|
||||
final int type;
|
||||
final boolean constantPool;
|
||||
|
||||
JfrField(Map<String, String> attributes) {
|
||||
this.name = attributes.get("name");
|
||||
this.type = Integer.parseInt(attributes.get("class"));
|
||||
this.constantPool = "true".equals(attributes.get("constantPool"));
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
@@ -30,44 +31,52 @@ import java.util.Map;
|
||||
|
||||
/**
|
||||
* Parses JFR output produced by async-profiler.
|
||||
* Note: this class is not supposed to read JFR files produced by other tools.
|
||||
*/
|
||||
public class JfrReader implements Closeable {
|
||||
|
||||
private static final int
|
||||
CONTENT_THREAD = 7,
|
||||
CONTENT_STACKTRACE = 9,
|
||||
CONTENT_CLASS = 10,
|
||||
CONTENT_METHOD = 32,
|
||||
CONTENT_SYMBOL = 33,
|
||||
CONTENT_STATE = 34,
|
||||
CONTENT_FRAME_TYPE = 47;
|
||||
|
||||
private static final int
|
||||
EVENT_EXECUTION_SAMPLE = 20;
|
||||
private static final int CHUNK_HEADER_SIZE = 68;
|
||||
private static final int CPOOL_OFFSET = 16;
|
||||
private static final int META_OFFSET = 24;
|
||||
|
||||
private final FileChannel ch;
|
||||
private final ByteBuffer buf;
|
||||
|
||||
public final long startNanos;
|
||||
public final long stopNanos;
|
||||
public final Map<Integer, StackTrace> stackTraces = new HashMap<>();
|
||||
public final Map<Long, MethodRef> methods = new HashMap<>();
|
||||
public final Map<Long, ClassRef> classes = new HashMap<>();
|
||||
public final Map<Long, byte[]> symbols = new HashMap<>();
|
||||
public final Map<Integer, byte[]> threads = new HashMap<>();
|
||||
public final long durationNanos;
|
||||
public final long startTicks;
|
||||
public final long ticksPerSec;
|
||||
|
||||
public final Dictionary<JfrClass> types = new Dictionary<>();
|
||||
public final Map<String, JfrClass> typesByName = new HashMap<>();
|
||||
public final Dictionary<String> threads = new Dictionary<>();
|
||||
public final Dictionary<ClassRef> classes = new Dictionary<>();
|
||||
public final Dictionary<byte[]> symbols = new Dictionary<>();
|
||||
public final Dictionary<MethodRef> methods = new Dictionary<>();
|
||||
public final Dictionary<StackTrace> stackTraces = new Dictionary<>();
|
||||
public final Map<Integer, String> frameTypes = new HashMap<>();
|
||||
public final Map<Integer, String> threadStates = new HashMap<>();
|
||||
public final List<Sample> samples = new ArrayList<>();
|
||||
|
||||
public JfrReader(String fileName) throws IOException {
|
||||
this.ch = FileChannel.open(Paths.get(fileName), StandardOpenOption.READ);
|
||||
this.buf = ch.map(FileChannel.MapMode.READ_ONLY, 0, ch.size());
|
||||
|
||||
int checkpointOffset = buf.getInt(buf.capacity() - 4);
|
||||
this.startNanos = buf.getLong(buf.capacity() - 24);
|
||||
this.stopNanos = buf.getLong(checkpointOffset + 8);
|
||||
if (buf.getInt(0) != 0x464c5200) {
|
||||
throw new IOException("Not a valid JFR file");
|
||||
}
|
||||
|
||||
readCheckpoint(checkpointOffset);
|
||||
readEvents(checkpointOffset);
|
||||
int version = buf.getInt(4);
|
||||
if (version < 0x20000 || version > 0x2ffff) {
|
||||
throw new IOException("Unsupported JFR version: " + (version >>> 16) + "." + (version & 0xffff));
|
||||
}
|
||||
|
||||
this.startNanos = buf.getLong(32);
|
||||
this.durationNanos = buf.getLong(40);
|
||||
this.startTicks = buf.getLong(48);
|
||||
this.ticksPerSec = buf.getLong(56);
|
||||
|
||||
readMeta();
|
||||
readConstantPool();
|
||||
readEvents();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,16 +84,229 @@ public class JfrReader implements Closeable {
|
||||
ch.close();
|
||||
}
|
||||
|
||||
private void readEvents(int checkpointOffset) {
|
||||
buf.position(16);
|
||||
private void readMeta() {
|
||||
buf.position(buf.getInt(META_OFFSET + 4));
|
||||
getVarint();
|
||||
getVarint();
|
||||
getVarlong();
|
||||
getVarlong();
|
||||
getVarlong();
|
||||
|
||||
while (buf.position() < checkpointOffset) {
|
||||
int size = buf.getInt();
|
||||
int type = buf.getInt();
|
||||
if (type == EVENT_EXECUTION_SAMPLE) {
|
||||
String[] strings = new String[getVarint()];
|
||||
for (int i = 0; i < strings.length; i++) {
|
||||
strings[i] = getString();
|
||||
}
|
||||
readElement(strings);
|
||||
}
|
||||
|
||||
private Element readElement(String[] strings) {
|
||||
String name = strings[getVarint()];
|
||||
|
||||
int attributeCount = getVarint();
|
||||
Map<String, String> attributes = new HashMap<>(attributeCount);
|
||||
for (int i = 0; i < attributeCount; i++) {
|
||||
attributes.put(strings[getVarint()], strings[getVarint()]);
|
||||
}
|
||||
|
||||
Element e = createElement(name, attributes);
|
||||
int childCount = getVarint();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
e.addChild(readElement(strings));
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
private Element createElement(String name, Map<String, String> attributes) {
|
||||
switch (name) {
|
||||
case "class": {
|
||||
JfrClass type = new JfrClass(attributes);
|
||||
if (!attributes.containsKey("superType")) {
|
||||
types.put(type.id, type);
|
||||
}
|
||||
typesByName.put(type.name, type);
|
||||
return type;
|
||||
}
|
||||
case "field":
|
||||
return new JfrField(attributes);
|
||||
default:
|
||||
return new Element();
|
||||
}
|
||||
}
|
||||
|
||||
private void readConstantPool() {
|
||||
int offset = buf.getInt(CPOOL_OFFSET + 4);
|
||||
while (true) {
|
||||
buf.position(offset);
|
||||
getVarint();
|
||||
getVarint();
|
||||
getVarlong();
|
||||
getVarlong();
|
||||
long delta = getVarlong();
|
||||
getVarint();
|
||||
|
||||
int poolCount = getVarint();
|
||||
for (int i = 0; i < poolCount; i++) {
|
||||
int type = getVarint();
|
||||
readConstants(types.get(type));
|
||||
}
|
||||
|
||||
if (delta == 0) {
|
||||
break;
|
||||
}
|
||||
offset += delta;
|
||||
}
|
||||
}
|
||||
|
||||
private void readConstants(JfrClass type) {
|
||||
switch (type.name) {
|
||||
case "jdk.types.ChunkHeader":
|
||||
buf.position(buf.position() + (CHUNK_HEADER_SIZE + 3));
|
||||
break;
|
||||
case "java.lang.Thread":
|
||||
readThreads(type.field("group") != null);
|
||||
break;
|
||||
case "java.lang.Class":
|
||||
readClasses(type.field("hidden") != null);
|
||||
break;
|
||||
case "jdk.types.Symbol":
|
||||
readSymbols();
|
||||
break;
|
||||
case "jdk.types.Method":
|
||||
readMethods();
|
||||
break;
|
||||
case "jdk.types.StackTrace":
|
||||
readStackTraces();
|
||||
break;
|
||||
case "jdk.types.FrameType":
|
||||
readMap(frameTypes);
|
||||
break;
|
||||
case "jdk.types.ThreadState":
|
||||
readMap(threadStates);
|
||||
break;
|
||||
default:
|
||||
readOtherConstants(type.fields);
|
||||
}
|
||||
}
|
||||
|
||||
private void readThreads(boolean hasGroup) {
|
||||
int count = threads.preallocate(getVarint());
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = getVarlong();
|
||||
String osName = getString();
|
||||
int osThreadId = getVarint();
|
||||
String javaName = getString();
|
||||
long javaThreadId = getVarlong();
|
||||
if (hasGroup) getVarlong();
|
||||
threads.put(id, javaName != null ? javaName : osName);
|
||||
}
|
||||
}
|
||||
|
||||
private void readClasses(boolean hasHidden) {
|
||||
int count = classes.preallocate(getVarint());
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = getVarlong();
|
||||
long loader = getVarlong();
|
||||
long name = getVarlong();
|
||||
long pkg = getVarlong();
|
||||
int modifiers = getVarint();
|
||||
if (hasHidden) getVarint();
|
||||
classes.put(id, new ClassRef(name));
|
||||
}
|
||||
}
|
||||
|
||||
private void readMethods() {
|
||||
int count = methods.preallocate(getVarint());
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = getVarlong();
|
||||
long cls = getVarlong();
|
||||
long name = getVarlong();
|
||||
long sig = getVarlong();
|
||||
int modifiers = getVarint();
|
||||
int hidden = getVarint();
|
||||
methods.put(id, new MethodRef(cls, name, sig));
|
||||
}
|
||||
}
|
||||
|
||||
private void readStackTraces() {
|
||||
int count = stackTraces.preallocate(getVarint());
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = getVarlong();
|
||||
int truncated = getVarint();
|
||||
StackTrace stackTrace = readStackTrace();
|
||||
stackTraces.put(id, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
private StackTrace readStackTrace() {
|
||||
int depth = getVarint();
|
||||
long[] methods = new long[depth];
|
||||
byte[] types = new byte[depth];
|
||||
for (int i = 0; i < depth; i++) {
|
||||
methods[i] = getVarlong();
|
||||
int line = getVarint();
|
||||
int bci = getVarint();
|
||||
types[i] = buf.get();
|
||||
}
|
||||
return new StackTrace(methods, types);
|
||||
}
|
||||
|
||||
private void readSymbols() {
|
||||
int count = symbols.preallocate(getVarint());
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = getVarlong();
|
||||
if (buf.get() != 3) {
|
||||
throw new IllegalArgumentException("Invalid symbol encoding");
|
||||
}
|
||||
symbols.put(id, getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
private void readMap(Map<Integer, String> map) {
|
||||
int count = getVarint();
|
||||
for (int i = 0; i < count; i++) {
|
||||
map.put(getVarint(), getString());
|
||||
}
|
||||
}
|
||||
|
||||
private void readOtherConstants(List<JfrField> fields) {
|
||||
int stringType = getTypeId("java.lang.String");
|
||||
|
||||
boolean[] numeric = new boolean[fields.size()];
|
||||
for (int i = 0; i < numeric.length; i++) {
|
||||
JfrField f = fields.get(i);
|
||||
numeric[i] = f.constantPool || f.type != stringType;
|
||||
}
|
||||
|
||||
int count = getVarint();
|
||||
for (int i = 0; i < count; i++) {
|
||||
getVarlong();
|
||||
readFields(numeric);
|
||||
}
|
||||
}
|
||||
|
||||
private void readFields(boolean[] numeric) {
|
||||
for (boolean n : numeric) {
|
||||
if (n) {
|
||||
getVarlong();
|
||||
} else {
|
||||
getString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readEvents() {
|
||||
int executionSample = getTypeId("jdk.ExecutionSample");
|
||||
int nativeMethodSample = getTypeId("jdk.NativeMethodSample");
|
||||
|
||||
buf.position(CHUNK_HEADER_SIZE);
|
||||
while (buf.hasRemaining()) {
|
||||
int position = buf.position();
|
||||
int size = getVarint();
|
||||
int type = getVarint();
|
||||
if (type == executionSample || type == nativeMethodSample) {
|
||||
readExecutionSample();
|
||||
} else {
|
||||
buf.position(buf.position() + size - 8);
|
||||
buf.position(position + size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,10 +314,10 @@ public class JfrReader implements Closeable {
|
||||
}
|
||||
|
||||
private void readExecutionSample() {
|
||||
long time = buf.getLong();
|
||||
int tid = buf.getInt();
|
||||
int stackTraceId = (int) buf.getLong();
|
||||
short threadState = buf.getShort();
|
||||
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);
|
||||
@@ -104,102 +326,59 @@ public class JfrReader implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private void readCheckpoint(int checkpointOffset) {
|
||||
buf.position(checkpointOffset + 24);
|
||||
|
||||
readFrameTypes();
|
||||
readThreadStates();
|
||||
readStackTraces();
|
||||
readMethods();
|
||||
readClasses();
|
||||
readSymbols();
|
||||
readThreads();
|
||||
private int getTypeId(String typeName) {
|
||||
JfrClass type = typesByName.get(typeName);
|
||||
return type != null ? type.id : -1;
|
||||
}
|
||||
|
||||
private void readFrameTypes() {
|
||||
int count = getTableSize(CONTENT_FRAME_TYPE);
|
||||
for (int i = 0; i < count; i++) {
|
||||
buf.get();
|
||||
getSymbol();
|
||||
}
|
||||
}
|
||||
|
||||
private void readThreadStates() {
|
||||
int count = getTableSize(CONTENT_STATE);
|
||||
for (int i = 0; i < count; i++) {
|
||||
buf.getShort();
|
||||
getSymbol();
|
||||
}
|
||||
}
|
||||
|
||||
private void readStackTraces() {
|
||||
int count = getTableSize(CONTENT_STACKTRACE);
|
||||
for (int i = 0; i < count; i++) {
|
||||
int id = (int) buf.getLong();
|
||||
byte truncated = buf.get();
|
||||
Frame[] frames = new Frame[buf.getInt()];
|
||||
for (int j = 0; j < frames.length; j++) {
|
||||
long method = buf.getLong();
|
||||
int bci = buf.getInt();
|
||||
byte type = buf.get();
|
||||
frames[j] = new Frame(method, type);
|
||||
private int getVarint() {
|
||||
int result = 0;
|
||||
for (int shift = 0; ; shift += 7) {
|
||||
byte b = buf.get();
|
||||
result |= (b & 0x7f) << shift;
|
||||
if (b >= 0) {
|
||||
return result;
|
||||
}
|
||||
stackTraces.put(id, new StackTrace(frames));
|
||||
}
|
||||
}
|
||||
|
||||
private void readMethods() {
|
||||
int count = getTableSize(CONTENT_METHOD);
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = buf.getLong();
|
||||
long cls = buf.getLong();
|
||||
long name = buf.getLong();
|
||||
long sig = buf.getLong();
|
||||
short modifiers = buf.getShort();
|
||||
byte hidden = buf.get();
|
||||
methods.put(id, new MethodRef(cls, name, sig));
|
||||
private long getVarlong() {
|
||||
long result = 0;
|
||||
for (int shift = 0; shift < 56; shift += 7) {
|
||||
byte b = buf.get();
|
||||
result |= (b & 0x7fL) << shift;
|
||||
if (b >= 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result | (buf.get() & 0xffL) << 56;
|
||||
}
|
||||
|
||||
private String getString() {
|
||||
switch (buf.get()) {
|
||||
case 0:
|
||||
return null;
|
||||
case 1:
|
||||
return "";
|
||||
case 3:
|
||||
return new String(getBytes(), StandardCharsets.UTF_8);
|
||||
case 4: {
|
||||
char[] chars = new char[getVarint()];
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
chars[i] = (char) getVarint();
|
||||
}
|
||||
return new String(chars);
|
||||
}
|
||||
case 5:
|
||||
return new String(getBytes(), StandardCharsets.ISO_8859_1);
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid string encoding");
|
||||
}
|
||||
}
|
||||
|
||||
private void readClasses() {
|
||||
int count = getTableSize(CONTENT_CLASS);
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = buf.getLong();
|
||||
long loader = buf.getLong();
|
||||
long name = buf.getLong();
|
||||
short modifiers = buf.getShort();
|
||||
classes.put(id, new ClassRef(name));
|
||||
}
|
||||
}
|
||||
|
||||
private void readSymbols() {
|
||||
int count = getTableSize(CONTENT_SYMBOL);
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = buf.getLong();
|
||||
byte[] symbol = getSymbol();
|
||||
symbols.put(id, symbol);
|
||||
}
|
||||
}
|
||||
|
||||
private void readThreads() {
|
||||
int count = getTableSize(CONTENT_THREAD);
|
||||
for (int i = 0; i < count; i++) {
|
||||
int id = buf.getInt();
|
||||
byte[] name = getSymbol();
|
||||
threads.put(id, name);
|
||||
}
|
||||
}
|
||||
|
||||
private int getTableSize(int contentType) {
|
||||
if (buf.getInt() != contentType) {
|
||||
throw new IllegalArgumentException("Expected content type " + contentType);
|
||||
}
|
||||
return buf.getInt();
|
||||
}
|
||||
|
||||
private byte[] getSymbol() {
|
||||
byte[] symbol = new byte[buf.getShort() & 0xffff];
|
||||
buf.get(symbol);
|
||||
return symbol;
|
||||
private byte[] getBytes() {
|
||||
byte[] bytes = new byte[getVarint()];
|
||||
buf.get(bytes);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ public class Sample implements Comparable<Sample> {
|
||||
public final long time;
|
||||
public final int tid;
|
||||
public final int stackTraceId;
|
||||
public final short threadState;
|
||||
public final int threadState;
|
||||
|
||||
public Sample(long time, int tid, int stackTraceId, short threadState) {
|
||||
public Sample(long time, int tid, int stackTraceId, int threadState) {
|
||||
this.time = time;
|
||||
this.tid = tid;
|
||||
this.stackTraceId = stackTraceId;
|
||||
|
||||
@@ -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.
|
||||
@@ -17,10 +17,12 @@
|
||||
package one.jfr;
|
||||
|
||||
public class StackTrace {
|
||||
public final Frame[] frames;
|
||||
public final long[] methods;
|
||||
public final byte[] types;
|
||||
public long samples;
|
||||
|
||||
public StackTrace(Frame[] frames) {
|
||||
this.frames = frames;
|
||||
public StackTrace(long[] methods, byte[] types) {
|
||||
this.methods = methods;
|
||||
this.types = types;
|
||||
}
|
||||
}
|
||||
|
||||
126
src/dictionary.cpp
Normal file
126
src/dictionary.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "dictionary.h"
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
static inline char* allocateKey(const char* key, size_t length) {
|
||||
char* result = (char*)malloc(length + 1);
|
||||
memcpy(result, key, length);
|
||||
result[length] = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline bool keyEquals(const char* candidate, const char* key, size_t length) {
|
||||
return strncmp(candidate, key, length) == 0 && candidate[length] == 0;
|
||||
}
|
||||
|
||||
|
||||
Dictionary::Dictionary() {
|
||||
_table = (DictTable*)calloc(1, sizeof(DictTable));
|
||||
_table->base_index = _base_index = 1;
|
||||
}
|
||||
|
||||
Dictionary::~Dictionary() {
|
||||
clear(_table);
|
||||
free(_table);
|
||||
}
|
||||
|
||||
void Dictionary::clear() {
|
||||
clear(_table);
|
||||
memset(_table, 0, sizeof(DictTable));
|
||||
_table->base_index = _base_index = 1;
|
||||
}
|
||||
|
||||
void Dictionary::clear(DictTable* table) {
|
||||
for (int i = 0; i < ROWS; i++) {
|
||||
DictRow* row = &table->rows[i];
|
||||
for (int j = 0; j < CELLS; j++) {
|
||||
free(row->keys[j]);
|
||||
}
|
||||
if (row->next != NULL) {
|
||||
clear(row->next);
|
||||
free(row->next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Many popular symbols are quite short, e.g. "[B", "()V" etc.
|
||||
// FNV-1a is reasonably fast and sufficiently random.
|
||||
unsigned int Dictionary::hash(const char* key, size_t length) {
|
||||
unsigned int h = 2166136261U;
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
h = (h ^ key[i]) * 16777619;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
unsigned int Dictionary::lookup(const char* key) {
|
||||
return lookup(key, strlen(key));
|
||||
}
|
||||
|
||||
unsigned int Dictionary::lookup(const char* key, size_t length) {
|
||||
DictTable* table = _table;
|
||||
unsigned int h = hash(key, length);
|
||||
|
||||
while (true) {
|
||||
DictRow* row = &table->rows[h % ROWS];
|
||||
for (int c = 0; c < CELLS; c++) {
|
||||
if (row->keys[c] == NULL) {
|
||||
char* new_key = allocateKey(key, length);
|
||||
if (__sync_bool_compare_and_swap(&row->keys[c], NULL, new_key)) {
|
||||
return table->index(h % ROWS, c);
|
||||
}
|
||||
free(new_key);
|
||||
}
|
||||
if (keyEquals(row->keys[c], key, length)) {
|
||||
return table->index(h % ROWS, c);
|
||||
}
|
||||
}
|
||||
|
||||
if (row->next == NULL) {
|
||||
DictTable* new_table = (DictTable*)calloc(1, sizeof(DictTable));
|
||||
new_table->base_index = __sync_add_and_fetch(&_base_index, TABLE_CAPACITY);
|
||||
if (!__sync_bool_compare_and_swap(&row->next, NULL, new_table)) {
|
||||
free(new_table);
|
||||
}
|
||||
}
|
||||
|
||||
table = row->next;
|
||||
h = (h >> ROW_BITS) | (h << (32 - ROW_BITS));
|
||||
}
|
||||
}
|
||||
|
||||
void Dictionary::collect(std::map<unsigned int, const char*>& map) {
|
||||
collect(map, _table);
|
||||
}
|
||||
|
||||
void Dictionary::collect(std::map<unsigned int, const char*>& map, DictTable* table) {
|
||||
for (int i = 0; i < ROWS; i++) {
|
||||
DictRow* row = &table->rows[i];
|
||||
for (int j = 0; j < CELLS; j++) {
|
||||
if (row->keys[j] != NULL) {
|
||||
map[table->index(i, j)] = row->keys[j];
|
||||
}
|
||||
}
|
||||
if (row->next != NULL) {
|
||||
collect(map, row->next);
|
||||
}
|
||||
}
|
||||
}
|
||||
70
src/dictionary.h
Normal file
70
src/dictionary.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _DICTIONARY_H
|
||||
#define _DICTIONARY_H
|
||||
|
||||
#include <map>
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
#define ROW_BITS 7
|
||||
#define ROWS (1 << ROW_BITS)
|
||||
#define CELLS 3
|
||||
#define TABLE_CAPACITY (ROWS * CELLS)
|
||||
|
||||
|
||||
struct DictTable;
|
||||
|
||||
struct DictRow {
|
||||
char* keys[CELLS];
|
||||
DictTable* next;
|
||||
};
|
||||
|
||||
struct DictTable {
|
||||
DictRow rows[ROWS];
|
||||
unsigned int base_index;
|
||||
|
||||
unsigned int index(int row, int col) {
|
||||
return base_index + (col << ROW_BITS) + row;
|
||||
}
|
||||
};
|
||||
|
||||
// Append-only concurrent hash table based on multi-level arrays
|
||||
class Dictionary {
|
||||
private:
|
||||
DictTable* _table;
|
||||
volatile unsigned int _base_index;
|
||||
|
||||
static void clear(DictTable* table);
|
||||
|
||||
static unsigned int hash(const char* key, size_t length);
|
||||
|
||||
static void collect(std::map<unsigned int, const char*>& map, DictTable* table);
|
||||
|
||||
public:
|
||||
Dictionary();
|
||||
~Dictionary();
|
||||
|
||||
void clear();
|
||||
|
||||
unsigned int lookup(const char* key);
|
||||
unsigned int lookup(const char* key, size_t length);
|
||||
|
||||
void collect(std::map<unsigned int, const char*>& map);
|
||||
};
|
||||
|
||||
#endif // _DICTIONARY_H
|
||||
@@ -16,9 +16,10 @@
|
||||
|
||||
#include "engine.h"
|
||||
#include "stackFrame.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
volatile bool Engine::_enabled;
|
||||
|
||||
Error Engine::check(Arguments& args) {
|
||||
return Error::OK;
|
||||
}
|
||||
@@ -27,7 +28,8 @@ CStack Engine::cstack() {
|
||||
return CSTACK_FP;
|
||||
}
|
||||
|
||||
int Engine::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth) {
|
||||
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;
|
||||
@@ -43,11 +45,11 @@ int Engine::getNativeTrace(void* ucontext, int tid, const void** callchain, int
|
||||
}
|
||||
|
||||
int depth = 0;
|
||||
const void* const valid_pc = (const void*)0x1000;
|
||||
const void* const valid_pc = (const void* const)0x1000;
|
||||
|
||||
// Walk until the bottom of the stack or until the first Java frame
|
||||
while (depth < max_depth && pc >= valid_pc) {
|
||||
if (CodeHeap::contains(pc)) {
|
||||
if (java_methods->contains(pc) || runtime_stubs->contains(pc)) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
36
src/engine.h
36
src/engine.h
@@ -18,9 +18,13 @@
|
||||
#define _ENGINE_H
|
||||
|
||||
#include "arguments.h"
|
||||
#include "codeCache.h"
|
||||
|
||||
|
||||
class Engine {
|
||||
protected:
|
||||
static volatile bool _enabled;
|
||||
|
||||
public:
|
||||
virtual const char* name() = 0;
|
||||
virtual const char* units() = 0;
|
||||
@@ -29,11 +33,35 @@ class Engine {
|
||||
virtual Error start(Arguments& args) = 0;
|
||||
virtual void stop() = 0;
|
||||
|
||||
virtual void onThreadStart(int tid) {}
|
||||
virtual void onThreadEnd(int tid) {}
|
||||
|
||||
virtual CStack cstack();
|
||||
virtual int getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth);
|
||||
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
|
||||
|
||||
55
src/event.h
Normal file
55
src/event.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _EVENT_H
|
||||
#define _EVENT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "os.h"
|
||||
|
||||
|
||||
class Event {
|
||||
public:
|
||||
u32 id() {
|
||||
return *(u32*)this;
|
||||
}
|
||||
};
|
||||
|
||||
class ExecutionEvent : public Event {
|
||||
public:
|
||||
ThreadState _thread_state;
|
||||
|
||||
ExecutionEvent() : _thread_state(THREAD_RUNNING) {
|
||||
}
|
||||
};
|
||||
|
||||
class AllocEvent : public Event {
|
||||
public:
|
||||
u32 _class_id;
|
||||
u64 _total_size;
|
||||
u64 _instance_size;
|
||||
};
|
||||
|
||||
class LockEvent : public Event {
|
||||
public:
|
||||
u32 _class_id;
|
||||
u64 _start_time;
|
||||
u64 _end_time;
|
||||
uintptr_t _address;
|
||||
long long _timeout;
|
||||
};
|
||||
|
||||
#endif // _EVENT_H
|
||||
@@ -1,378 +1,254 @@
|
||||
/*
|
||||
* Copyright 2018 Andrei Pangin
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* This is a specialized C++ port of the FlameGraph script available at
|
||||
* https://github.com/brendangregg/FlameGraph/blob/master/flamegraph.pl
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* Copyright 2016 Netflix, Inc.
|
||||
* Copyright 2011 Joyent, Inc. All rights reserved.
|
||||
* Copyright 2011 Brendan Gregg. All rights reserved.
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at docs/cddl1.txt or
|
||||
* http://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at docs/cddl1.txt.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <iomanip>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "flameGraph.h"
|
||||
|
||||
|
||||
static const char SVG_HEADER[] =
|
||||
"<?xml version=\"1.0\" standalone=\"no\"?>\n"
|
||||
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"
|
||||
"<svg version=\"1.1\" width=\"%d\" height=\"%d\" onload=\"init(evt)\" viewBox=\"0 0 %d %d\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n"
|
||||
"<style type=\"text/css\">\n"
|
||||
"\ttext { font-family:Verdana; font-size:12px; fill:rgb(0,0,0); }\n"
|
||||
"\t#search { opacity:0.1; cursor:pointer; }\n"
|
||||
"\t#search:hover, #search.show { opacity:1; }\n"
|
||||
"\t#subtitle { text-anchor:middle; font-color:rgb(160,160,160); }\n"
|
||||
"\t#title { text-anchor:middle; font-size:17px}\n"
|
||||
"\t#unzoom { cursor:pointer; }\n"
|
||||
"\t#frames > *:hover { stroke:black; stroke-width:0.5; cursor:pointer; }\n"
|
||||
"\t.hide { display:none; }\n"
|
||||
"\t.parent { opacity:0.5; }\n"
|
||||
static const char FLAMEGRAPH_HEADER[] =
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html lang='en'>\n"
|
||||
"<head>\n"
|
||||
"<meta charset='utf-8'>\n"
|
||||
"<style>\n"
|
||||
"\tbody {margin: 0; padding: 10px; background-color: #ffffff}\n"
|
||||
"\th1 {margin: 5px 0 0 0; font-size: 18px; font-weight: normal; text-align: center}\n"
|
||||
"\theader {margin: -24px 0 5px 0; line-height: 24px}\n"
|
||||
"\tbutton {font: 12px sans-serif; cursor: pointer}\n"
|
||||
"\tp {margin: 5px 0 5px 0}\n"
|
||||
"\ta {color: #0366d6}\n"
|
||||
"\t#hl {position: absolute; display: none; overflow: hidden; white-space: nowrap; pointer-events: none; background-color: #ffffe0; outline: 1px solid #ffc000; height: 15px}\n"
|
||||
"\t#hl span {padding: 0 3px 0 3px}\n"
|
||||
"\t#status {overflow: hidden; white-space: nowrap}\n"
|
||||
"\t#match {overflow: hidden; white-space: nowrap; display: none; float: right; text-align: right}\n"
|
||||
"\t#reset {cursor: pointer}\n"
|
||||
"</style>\n"
|
||||
"<script type=\"text/ecmascript\">\n"
|
||||
"<![CDATA[\n"
|
||||
"\t\"use strict\";\n"
|
||||
"\tvar details, searchbtn, unzoombtn, matchedtxt, svg, searching;\n"
|
||||
"\tfunction init(evt) {\n"
|
||||
"\t\tdetails = document.getElementById(\"details\").firstChild;\n"
|
||||
"\t\tsearchbtn = document.getElementById(\"search\");\n"
|
||||
"\t\tunzoombtn = document.getElementById(\"unzoom\");\n"
|
||||
"\t\tmatchedtxt = document.getElementById(\"matched\");\n"
|
||||
"\t\tsvg = document.getElementsByTagName(\"svg\")[0];\n"
|
||||
"\t\tsearching = 0;\n"
|
||||
"</head>\n"
|
||||
"<body style='font: 12px Verdana, sans-serif'>\n"
|
||||
"<h1>%s</h1>\n"
|
||||
"<header style='text-align: left'><button id='reverse' title='Reverse'>🔻</button> <button id='search' title='Search'>🔍</button></header>\n"
|
||||
"<header style='text-align: right'>Produced by <a href='https://github.com/jvm-profiling-tools/async-profiler'>async-profiler</a></header>\n"
|
||||
"<canvas id='canvas' style='width: 100%%; height: %dpx'></canvas>\n"
|
||||
"<div id='hl'><span></span></div>\n"
|
||||
"<p id='match'>Matched: <span id='matchval'></span> <span id='reset' title='Clear'>❌</span></p>\n"
|
||||
"<p id='status'> </p>\n"
|
||||
"<script>\n"
|
||||
"\t// Copyright 2020 Andrei Pangin\n"
|
||||
"\t// Licensed under the Apache License, Version 2.0.\n"
|
||||
"\t'use strict';\n"
|
||||
"\tvar root, rootLevel, px, pattern;\n"
|
||||
"\tvar reverse = %s;\n"
|
||||
"\tconst levels = Array(%d);\n"
|
||||
"\tfor (let h = 0; h < levels.length; h++) {\n"
|
||||
"\t\tlevels[h] = [];\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\twindow.addEventListener(\"click\", function(e) {\n"
|
||||
"\t\tvar target = find_group(e.target);\n"
|
||||
"\t\tif (target) {\n"
|
||||
"\t\t\tif (target.nodeName == \"a\") {\n"
|
||||
"\t\t\t\tif (e.ctrlKey === false) return;\n"
|
||||
"\t\t\t\te.preventDefault();\n"
|
||||
"\tconst canvas = document.getElementById('canvas');\n"
|
||||
"\tconst c = canvas.getContext('2d');\n"
|
||||
"\tconst hl = document.getElementById('hl');\n"
|
||||
"\tconst status = document.getElementById('status');\n"
|
||||
"\n"
|
||||
"\tconst canvasWidth = canvas.offsetWidth;\n"
|
||||
"\tconst canvasHeight = canvas.offsetHeight;\n"
|
||||
"\tcanvas.style.width = canvasWidth + 'px';\n"
|
||||
"\tcanvas.width = canvasWidth * (devicePixelRatio || 1);\n"
|
||||
"\tcanvas.height = canvasHeight * (devicePixelRatio || 1);\n"
|
||||
"\tif (devicePixelRatio) c.scale(devicePixelRatio, devicePixelRatio);\n"
|
||||
"\tc.font = document.body.style.font;\n"
|
||||
"\n"
|
||||
"\tconst palette = [\n"
|
||||
"\t\t[0x50e150, 30, 30, 30],\n"
|
||||
"\t\t[0x50bebe, 30, 30, 30],\n"
|
||||
"\t\t[0xe17d00, 30, 30, 0],\n"
|
||||
"\t\t[0xc8c83c, 30, 30, 10],\n"
|
||||
"\t\t[0xe15a5a, 30, 40, 40],\n"
|
||||
"\t];\n"
|
||||
"\n"
|
||||
"\tfunction getColor(p) {\n"
|
||||
"\t\tconst v = Math.random();\n"
|
||||
"\t\treturn '#' + (p[0] + ((p[1] * v) << 16 | (p[2] * v) << 8 | (p[3] * v))).toString(16);\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction f(level, left, width, type, title) {\n"
|
||||
"\t\tlevels[level].push({left: left, width: width, color: getColor(palette[type]), title: title});\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction samples(n) {\n"
|
||||
"\t\treturn n === 1 ? '1 sample' : n.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',') + ' samples';\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction pct(a, b) {\n"
|
||||
"\t\treturn a >= b ? '100' : (100 * a / b).toFixed(2);\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction findFrame(frames, x) {\n"
|
||||
"\t\tlet left = 0;\n"
|
||||
"\t\tlet right = frames.length - 1;\n"
|
||||
"\n"
|
||||
"\t\twhile (left <= right) {\n"
|
||||
"\t\t\tconst mid = (left + right) >>> 1;\n"
|
||||
"\t\t\tconst f = frames[mid];\n"
|
||||
"\n"
|
||||
"\t\t\tif (f.left > x) {\n"
|
||||
"\t\t\t\tright = mid - 1;\n"
|
||||
"\t\t\t} else if (f.left + f.width <= x) {\n"
|
||||
"\t\t\t\tleft = mid + 1;\n"
|
||||
"\t\t\t} else {\n"
|
||||
"\t\t\t\treturn f;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\tif (target.classList.contains(\"parent\")) unzoom();\n"
|
||||
"\t\t\tzoom(target);\n"
|
||||
"\t\t}\n"
|
||||
"\t\telse if (e.target.id == \"unzoom\") unzoom();\n"
|
||||
"\t\telse if (e.target.id == \"search\") search_prompt();\n"
|
||||
"\t}, false)\n"
|
||||
"\n"
|
||||
"\t// mouse-over for info\n"
|
||||
"\t// show\n"
|
||||
"\twindow.addEventListener(\"mouseover\", function(e) {\n"
|
||||
"\t\tvar target = find_group(e.target);\n"
|
||||
"\t\tif (target) details.nodeValue = \"Function: \" + g_to_text(target);\n"
|
||||
"\t}, false)\n"
|
||||
"\t\tif (frames[left] && (frames[left].left - x) * px < 0.5) return frames[left];\n"
|
||||
"\t\tif (frames[right] && (x - (frames[right].left + frames[right].width)) * px < 0.5) return frames[right];\n"
|
||||
"\n"
|
||||
"\t// clear\n"
|
||||
"\twindow.addEventListener(\"mouseout\", function(e) {\n"
|
||||
"\t\tvar target = find_group(e.target);\n"
|
||||
"\t\tif (target) details.nodeValue = ' ';\n"
|
||||
"\t}, false)\n"
|
||||
"\t\treturn null;\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\t// ctrl-F for search\n"
|
||||
"\twindow.addEventListener(\"keydown\",function (e) {\n"
|
||||
"\t\tif (e.keyCode === 114 || (e.ctrlKey && e.keyCode === 70)) {\n"
|
||||
"\t\t\te.preventDefault();\n"
|
||||
"\t\t\tsearch_prompt();\n"
|
||||
"\t\t}\n"
|
||||
"\t}, false)\n"
|
||||
"\n"
|
||||
"\t// functions\n"
|
||||
"\tfunction find_child(node, selector) {\n"
|
||||
"\t\tvar children = node.querySelectorAll(selector);\n"
|
||||
"\t\tif (children.length) return children[0];\n"
|
||||
"\t\treturn;\n"
|
||||
"\t}\n"
|
||||
"\tfunction find_group(node) {\n"
|
||||
"\t\tvar parent = node.parentElement;\n"
|
||||
"\t\tif (!parent) return;\n"
|
||||
"\t\tif (parent.id == \"frames\") return node;\n"
|
||||
"\t\treturn find_group(parent);\n"
|
||||
"\t}\n"
|
||||
"\tfunction orig_save(e, attr, val) {\n"
|
||||
"\t\tif (e.attributes[\"_orig_\" + attr] != undefined) return;\n"
|
||||
"\t\tif (e.attributes[attr] == undefined) return;\n"
|
||||
"\t\tif (val == undefined) val = e.attributes[attr].value;\n"
|
||||
"\t\te.setAttribute(\"_orig_\" + attr, val);\n"
|
||||
"\t}\n"
|
||||
"\tfunction orig_load(e, attr) {\n"
|
||||
"\t\tif (e.attributes[\"_orig_\"+attr] == undefined) return;\n"
|
||||
"\t\te.attributes[attr].value = e.attributes[\"_orig_\" + attr].value;\n"
|
||||
"\t\te.removeAttribute(\"_orig_\"+attr);\n"
|
||||
"\t}\n"
|
||||
"\tfunction g_to_text(e) {\n"
|
||||
"\t\tvar text = find_child(e, \"title\").firstChild.nodeValue;\n"
|
||||
"\t\treturn (text)\n"
|
||||
"\t}\n"
|
||||
"\tfunction g_to_func(e) {\n"
|
||||
"\t\tvar func = g_to_text(e);\n"
|
||||
"\t\t// if there's any manipulation we want to do to the function\n"
|
||||
"\t\t// name before it's searched, do it here before returning.\n"
|
||||
"\t\treturn (func);\n"
|
||||
"\t}\n"
|
||||
"\tfunction update_text(e) {\n"
|
||||
"\t\tvar r = find_child(e, \"rect\");\n"
|
||||
"\t\tvar t = find_child(e, \"text\");\n"
|
||||
"\t\tvar w = parseFloat(r.attributes.width.value) -3;\n"
|
||||
"\t\tvar txt = find_child(e, \"title\").textContent.replace(/\\([^(]*\\)$/,\"\");\n"
|
||||
"\t\tt.attributes.x.value = parseFloat(r.attributes.x.value) + 3;\n"
|
||||
"\n"
|
||||
"\t\t// Smaller than this size won't fit anything\n"
|
||||
"\t\tif (w < 2 * 12 * 0.59) {\n"
|
||||
"\t\t\tt.textContent = \"\";\n"
|
||||
"\tfunction search(r) {\n"
|
||||
"\t\tif (r && (r = prompt('Enter regexp to search:', '')) === null) {\n"
|
||||
"\t\t\treturn;\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tt.textContent = txt;\n"
|
||||
"\t\t// Fit in full text width\n"
|
||||
"\t\tif (/^ *$/.test(txt) || t.getSubStringLength(0, txt.length) < w)\n"
|
||||
"\t\t\treturn;\n"
|
||||
"\t\tpattern = r ? RegExp(r) : undefined;\n"
|
||||
"\t\tconst matched = render(root, rootLevel);\n"
|
||||
"\t\tdocument.getElementById('matchval').textContent = pct(matched, root.width) + '%%';\n"
|
||||
"\t\tdocument.getElementById('match').style.display = r ? 'inherit' : 'none';\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\t\tfor (var x = txt.length - 2; x > 0; x--) {\n"
|
||||
"\t\t\tif (t.getSubStringLength(0, x + 2) <= w) {\n"
|
||||
"\t\t\t\tt.textContent = txt.substring(0, x) + \"..\";\n"
|
||||
"\tfunction render(newRoot, newLevel) {\n"
|
||||
"\t\tif (root) {\n"
|
||||
"\t\t\tc.fillStyle = '#ffffff';\n"
|
||||
"\t\t\tc.fillRect(0, 0, canvasWidth, canvasHeight);\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\troot = newRoot || levels[0][0];\n"
|
||||
"\t\trootLevel = newLevel || 0;\n"
|
||||
"\t\tpx = canvasWidth / root.width;\n"
|
||||
"\n"
|
||||
"\t\tconst x0 = root.left;\n"
|
||||
"\t\tconst x1 = x0 + root.width;\n"
|
||||
"\t\tconst marked = [];\n"
|
||||
"\n"
|
||||
"\t\tfunction mark(f) {\n"
|
||||
"\t\t\treturn marked[f.left] >= f.width || (marked[f.left] = f.width);\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tfunction totalMarked() {\n"
|
||||
"\t\t\tlet total = 0;\n"
|
||||
"\t\t\tlet left = 0;\n"
|
||||
"\t\t\tfor (let x in marked) {\n"
|
||||
"\t\t\t\tif (+x >= left) {\n"
|
||||
"\t\t\t\t\ttotal += marked[x];\n"
|
||||
"\t\t\t\t\tleft = +x + marked[x];\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\treturn total;\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tfunction drawFrame(f, y, alpha) {\n"
|
||||
"\t\t\tif (f.left < x1 && f.left + f.width > x0) {\n"
|
||||
"\t\t\t\tc.fillStyle = pattern && f.title.match(pattern) && mark(f) ? '#ee00ee' : f.color;\n"
|
||||
"\t\t\t\tc.fillRect((f.left - x0) * px, y, f.width * px, 15);\n"
|
||||
"\n"
|
||||
"\t\t\t\tif (f.width * px >= 21) {\n"
|
||||
"\t\t\t\t\tconst chars = Math.floor(f.width * px / 7);\n"
|
||||
"\t\t\t\t\tconst title = f.title.length <= chars ? f.title : f.title.substring(0, chars - 2) + '..';\n"
|
||||
"\t\t\t\t\tc.fillStyle = '#000000';\n"
|
||||
"\t\t\t\t\tc.fillText(title, Math.max(f.left - x0, 0) * px + 3, y + 12, f.width * px - 6);\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\n"
|
||||
"\t\t\t\tif (alpha) {\n"
|
||||
"\t\t\t\t\tc.fillStyle = 'rgba(255, 255, 255, 0.5)';\n"
|
||||
"\t\t\t\t\tc.fillRect((f.left - x0) * px, y, f.width * px, 15);\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tfor (let h = 0; h < levels.length; h++) {\n"
|
||||
"\t\t\tconst y = reverse ? h * 16 : canvasHeight - (h + 1) * 16;\n"
|
||||
"\t\t\tconst frames = levels[h];\n"
|
||||
"\t\t\tfor (let i = 0; i < frames.length; i++) {\n"
|
||||
"\t\t\t\tdrawFrame(frames[i], y, h < rootLevel);\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\treturn totalMarked();\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tcanvas.onmousemove = function() {\n"
|
||||
"\t\tconst h = Math.floor((reverse ? event.offsetY : (canvasHeight - event.offsetY)) / 16);\n"
|
||||
"\t\tif (h >= 0 && h < levels.length) {\n"
|
||||
"\t\t\tconst f = findFrame(levels[h], event.offsetX / px + root.left);\n"
|
||||
"\t\t\tif (f) {\n"
|
||||
"\t\t\t\thl.style.left = (Math.max(f.left - root.left, 0) * px + canvas.offsetLeft) + 'px';\n"
|
||||
"\t\t\t\thl.style.width = (Math.min(f.width, root.width) * px) + 'px';\n"
|
||||
"\t\t\t\thl.style.top = ((reverse ? h * 16 : canvasHeight - (h + 1) * 16) + canvas.offsetTop) + 'px';\n"
|
||||
"\t\t\t\thl.firstChild.textContent = f.title;\n"
|
||||
"\t\t\t\thl.style.display = 'block';\n"
|
||||
"\t\t\t\tcanvas.title = f.title + '\\n(' + samples(f.width) + ', ' + pct(f.width, levels[0][0].width) + '%%)';\n"
|
||||
"\t\t\t\tcanvas.style.cursor = 'pointer';\n"
|
||||
"\t\t\t\tcanvas.onclick = function() {\n"
|
||||
"\t\t\t\t\tif (f != root) {\n"
|
||||
"\t\t\t\t\t\trender(f, h);\n"
|
||||
"\t\t\t\t\t\tcanvas.onmousemove();\n"
|
||||
"\t\t\t\t\t}\n"
|
||||
"\t\t\t\t};\n"
|
||||
"\t\t\t\tstatus.textContent = 'Function: ' + canvas.title;\n"
|
||||
"\t\t\t\treturn;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\t\tt.textContent = \"\";\n"
|
||||
"\t\tcanvas.onmouseout();\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\t// zoom\n"
|
||||
"\tfunction zoom_reset(e) {\n"
|
||||
"\t\tif (e.attributes != undefined) {\n"
|
||||
"\t\t\torig_load(e, \"x\");\n"
|
||||
"\t\t\torig_load(e, \"width\");\n"
|
||||
"\t\t}\n"
|
||||
"\t\tif (e.childNodes == undefined) return;\n"
|
||||
"\t\tfor (var i = 0, c = e.childNodes; i < c.length; i++) {\n"
|
||||
"\t\t\tzoom_reset(c[i]);\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n"
|
||||
"\tfunction zoom_child(e, x, ratio) {\n"
|
||||
"\t\tif (e.attributes != undefined) {\n"
|
||||
"\t\t\tif (e.attributes.x != undefined) {\n"
|
||||
"\t\t\t\torig_save(e, \"x\");\n"
|
||||
"\t\t\t\te.attributes.x.value = (parseFloat(e.attributes.x.value) - x - 10) * ratio + 10;\n"
|
||||
"\t\t\t\tif (e.tagName == \"text\")\n"
|
||||
"\t\t\t\t\te.attributes.x.value = find_child(e.parentNode, \"rect[x]\").attributes.x.value + 3;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\tif (e.attributes.width != undefined) {\n"
|
||||
"\t\t\t\torig_save(e, \"width\");\n"
|
||||
"\t\t\t\te.attributes.width.value = parseFloat(e.attributes.width.value) * ratio;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tif (e.childNodes == undefined) return;\n"
|
||||
"\t\tfor (var i = 0, c = e.childNodes; i < c.length; i++) {\n"
|
||||
"\t\t\tzoom_child(c[i], x - 10, ratio);\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n"
|
||||
"\tfunction zoom_parent(e) {\n"
|
||||
"\t\tif (e.attributes) {\n"
|
||||
"\t\t\tif (e.attributes.x != undefined) {\n"
|
||||
"\t\t\t\torig_save(e, \"x\");\n"
|
||||
"\t\t\t\te.attributes.x.value = 10;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\tif (e.attributes.width != undefined) {\n"
|
||||
"\t\t\t\torig_save(e, \"width\");\n"
|
||||
"\t\t\t\te.attributes.width.value = parseInt(svg.width.baseVal.value) - (10 * 2);\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\t\tif (e.childNodes == undefined) return;\n"
|
||||
"\t\tfor (var i = 0, c = e.childNodes; i < c.length; i++) {\n"
|
||||
"\t\t\tzoom_parent(c[i]);\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n"
|
||||
"\tfunction zoom(node) {\n"
|
||||
"\t\tvar attr = find_child(node, \"rect\").attributes;\n"
|
||||
"\t\tvar width = parseFloat(attr.width.value);\n"
|
||||
"\t\tvar xmin = parseFloat(attr.x.value);\n"
|
||||
"\t\tvar xmax = parseFloat(xmin + width);\n"
|
||||
"\t\tvar ymin = parseFloat(attr.y.value);\n"
|
||||
"\t\tvar ratio = (svg.width.baseVal.value - 2 * 10) / width;\n"
|
||||
"\n"
|
||||
"\t\t// XXX: Workaround for JavaScript float issues (fix me)\n"
|
||||
"\t\tvar fudge = 0.0001;\n"
|
||||
"\n"
|
||||
"\t\tunzoombtn.classList.remove(\"hide\");\n"
|
||||
"\n"
|
||||
"\t\tvar el = document.getElementById(\"frames\").children;\n"
|
||||
"\t\tfor (var i = 0; i < el.length; i++) {\n"
|
||||
"\t\t\tvar e = el[i];\n"
|
||||
"\t\t\tvar a = find_child(e, \"rect\").attributes;\n"
|
||||
"\t\t\tvar ex = parseFloat(a.x.value);\n"
|
||||
"\t\t\tvar ew = parseFloat(a.width.value);\n"
|
||||
"\t\t\tvar upstack;\n"
|
||||
"\t\t\t// Is it an ancestor\n"
|
||||
"\t\t\tif (%d == 0) {\n"
|
||||
"\t\t\t\tupstack = parseFloat(a.y.value) > ymin;\n"
|
||||
"\t\t\t} else {\n"
|
||||
"\t\t\t\tupstack = parseFloat(a.y.value) < ymin;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\tif (upstack) {\n"
|
||||
"\t\t\t\t// Direct ancestor\n"
|
||||
"\t\t\t\tif (ex <= xmin && (ex+ew+fudge) >= xmax) {\n"
|
||||
"\t\t\t\t\te.classList.add(\"parent\");\n"
|
||||
"\t\t\t\t\tzoom_parent(e);\n"
|
||||
"\t\t\t\t\tupdate_text(e);\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t\t// not in current path\n"
|
||||
"\t\t\t\telse\n"
|
||||
"\t\t\t\t\te.classList.add(\"hide\");\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\t// Children maybe\n"
|
||||
"\t\t\telse {\n"
|
||||
"\t\t\t\t// no common path\n"
|
||||
"\t\t\t\tif (ex < xmin || ex + fudge >= xmax) {\n"
|
||||
"\t\t\t\t\te.classList.add(\"hide\");\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t\telse {\n"
|
||||
"\t\t\t\t\tzoom_child(e, xmin, ratio);\n"
|
||||
"\t\t\t\t\tupdate_text(e);\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n"
|
||||
"\tfunction unzoom() {\n"
|
||||
"\t\tunzoombtn.classList.add(\"hide\");\n"
|
||||
"\t\tvar el = document.getElementById(\"frames\").children;\n"
|
||||
"\t\tfor(var i = 0; i < el.length; i++) {\n"
|
||||
"\t\t\tel[i].classList.remove(\"parent\");\n"
|
||||
"\t\t\tel[i].classList.remove(\"hide\");\n"
|
||||
"\t\t\tzoom_reset(el[i]);\n"
|
||||
"\t\t\tupdate_text(el[i]);\n"
|
||||
"\t\t}\n"
|
||||
"\tcanvas.onmouseout = function() {\n"
|
||||
"\t\thl.style.display = 'none';\n"
|
||||
"\t\tstatus.textContent = '\\xa0';\n"
|
||||
"\t\tcanvas.title = '';\n"
|
||||
"\t\tcanvas.style.cursor = '';\n"
|
||||
"\t\tcanvas.onclick = '';\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\t// search\n"
|
||||
"\tfunction reset_search() {\n"
|
||||
"\t\tvar el = document.querySelectorAll(\"#frames rect\");\n"
|
||||
"\t\tfor (var i = 0; i < el.length; i++) {\n"
|
||||
"\t\t\torig_load(el[i], \"fill\")\n"
|
||||
"\t\t}\n"
|
||||
"\tdocument.getElementById('reverse').onclick = function() {\n"
|
||||
"\t\treverse = !reverse;\n"
|
||||
"\t\trender();\n"
|
||||
"\t}\n"
|
||||
"\tfunction search_prompt() {\n"
|
||||
"\t\tif (!searching) {\n"
|
||||
"\t\t\tvar term = prompt(\"Enter a search term (regexp \" +\n"
|
||||
"\t\t\t \"allowed, eg: ^ext4_)\", \"\");\n"
|
||||
"\t\t\tif (term != null) {\n"
|
||||
"\t\t\t\tsearch(term)\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t} else {\n"
|
||||
"\t\t\treset_search();\n"
|
||||
"\t\t\tsearching = 0;\n"
|
||||
"\t\t\tsearchbtn.classList.remove(\"show\");\n"
|
||||
"\t\t\tsearchbtn.firstChild.nodeValue = \"Search\"\n"
|
||||
"\t\t\tmatchedtxt.classList.add(\"hide\");\n"
|
||||
"\t\t\tmatchedtxt.firstChild.nodeValue = \"\"\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\tdocument.getElementById('search').onclick = function() {\n"
|
||||
"\t\tsearch(true);\n"
|
||||
"\t}\n"
|
||||
"\tfunction search(term) {\n"
|
||||
"\t\tvar re = new RegExp(term);\n"
|
||||
"\t\tvar el = document.getElementById(\"frames\").children;\n"
|
||||
"\t\tvar matches = new Object();\n"
|
||||
"\t\tvar maxwidth = 0;\n"
|
||||
"\t\tfor (var i = 0; i < el.length; i++) {\n"
|
||||
"\t\t\tvar e = el[i];\n"
|
||||
"\t\t\tvar func = g_to_func(e);\n"
|
||||
"\t\t\tvar rect = find_child(e, \"rect\");\n"
|
||||
"\t\t\tif (func == null || rect == null)\n"
|
||||
"\t\t\t\tcontinue;\n"
|
||||
"\n"
|
||||
"\t\t\t// Save max width. Only works as we have a root frame\n"
|
||||
"\t\t\tvar w = parseFloat(rect.attributes.width.value);\n"
|
||||
"\t\t\tif (w > maxwidth)\n"
|
||||
"\t\t\t\tmaxwidth = w;\n"
|
||||
"\n"
|
||||
"\t\t\tif (func.match(re)) {\n"
|
||||
"\t\t\t\t// highlight\n"
|
||||
"\t\t\t\tvar x = parseFloat(rect.attributes.x.value);\n"
|
||||
"\t\t\t\torig_save(rect, \"fill\");\n"
|
||||
"\t\t\t\trect.attributes.fill.value = \"rgb(230,0,230)\";\n"
|
||||
"\n"
|
||||
"\t\t\t\t// remember matches\n"
|
||||
"\t\t\t\tif (matches[x] == undefined) {\n"
|
||||
"\t\t\t\t\tmatches[x] = w;\n"
|
||||
"\t\t\t\t} else {\n"
|
||||
"\t\t\t\t\tif (w > matches[x]) {\n"
|
||||
"\t\t\t\t\t\t// overwrite with parent\n"
|
||||
"\t\t\t\t\t\tmatches[x] = w;\n"
|
||||
"\t\t\t\t\t}\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t\tsearching = 1;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\t\tif (!searching)\n"
|
||||
"\t\t\treturn;\n"
|
||||
"\n"
|
||||
"\t\tsearchbtn.classList.add(\"show\");\n"
|
||||
"\t\tsearchbtn.firstChild.nodeValue = \"Reset Search\";\n"
|
||||
"\n"
|
||||
"\t\t// calculate percent matched, excluding vertical overlap\n"
|
||||
"\t\tvar count = 0;\n"
|
||||
"\t\tvar lastx = -1;\n"
|
||||
"\t\tvar lastw = 0;\n"
|
||||
"\t\tvar keys = Array();\n"
|
||||
"\t\tfor (k in matches) {\n"
|
||||
"\t\t\tif (matches.hasOwnProperty(k))\n"
|
||||
"\t\t\t\tkeys.push(k);\n"
|
||||
"\t\t}\n"
|
||||
"\t\t// sort the matched frames by their x location\n"
|
||||
"\t\t// ascending, then width descending\n"
|
||||
"\t\tkeys.sort(function(a, b){\n"
|
||||
"\t\t\treturn a - b;\n"
|
||||
"\t\t});\n"
|
||||
"\t\t// Step through frames saving only the biggest bottom-up frames\n"
|
||||
"\t\t// thanks to the sort order. This relies on the tree property\n"
|
||||
"\t\t// where children are always smaller than their parents.\n"
|
||||
"\t\tvar fudge = 0.0001;\t// JavaScript floating point\n"
|
||||
"\t\tfor (var k in keys) {\n"
|
||||
"\t\t\tvar x = parseFloat(keys[k]);\n"
|
||||
"\t\t\tvar w = matches[keys[k]];\n"
|
||||
"\t\t\tif (x >= lastx + lastw - fudge) {\n"
|
||||
"\t\t\t\tcount += w;\n"
|
||||
"\t\t\t\tlastx = x;\n"
|
||||
"\t\t\t\tlastw = w;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\t\t// display matched percent\n"
|
||||
"\t\tmatchedtxt.classList.remove(\"hide\");\n"
|
||||
"\t\tvar pct = 100 * count / maxwidth;\n"
|
||||
"\t\tif (pct != 100) pct = pct.toFixed(1)\n"
|
||||
"\t\tmatchedtxt.firstChild.nodeValue = \"Matched: \" + pct + \"%%\";\n"
|
||||
"\tdocument.getElementById('reset').onclick = function() {\n"
|
||||
"\t\tsearch(false);\n"
|
||||
"\t}\n"
|
||||
"]]>\n"
|
||||
"</script>\n"
|
||||
"<rect x=\"0\" y=\"0\" width=\"100%%\" height=\"100%%\" fill=\"rgb(240,240,220)\"/>\n"
|
||||
"<text id=\"title\" x=\"%d\" y=\"%d\">%s</text>\n"
|
||||
"<text id=\"details\" x=\"%d\" y=\"%d\"> </text>\n"
|
||||
"<text id=\"unzoom\" x=\"%d\" y=\"%d\" class=\"hide\">Reset Zoom</text>\n"
|
||||
"<text id=\"search\" x=\"%d\" y=\"%d\">Search</text>\n"
|
||||
"<text id=\"matched\" x=\"%d\" y=\"%d\"> </text>\n"
|
||||
"<g id=\"frames\">\n";
|
||||
"\n"
|
||||
"\twindow.onkeydown = function() {\n"
|
||||
"\t\tif (event.ctrlKey && event.keyCode === 70) {\n"
|
||||
"\t\t\tevent.preventDefault();\n"
|
||||
"\t\t\tsearch(true);\n"
|
||||
"\t\t} else if (event.keyCode === 27) {\n"
|
||||
"\t\t\tsearch(false);\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n";
|
||||
|
||||
static const char FLAMEGRAPH_FOOTER[] =
|
||||
"render();\n"
|
||||
"</script></body></html>\n";
|
||||
|
||||
|
||||
static const char TREE_HEADER[] =
|
||||
"<!DOCTYPE html>\n"
|
||||
@@ -418,19 +294,19 @@ static const char TREE_HEADER[] =
|
||||
" font-weight: bold;\n"
|
||||
" background-color: #D9D9D9;\n"
|
||||
"}\n"
|
||||
".green {\n"
|
||||
".t0 {\n"
|
||||
" color: #32c832;\n"
|
||||
"}\n"
|
||||
".aqua {\n"
|
||||
".t1 {\n"
|
||||
" color: #32a5a5;\n"
|
||||
"}\n"
|
||||
".brown {\n"
|
||||
".t2 {\n"
|
||||
" color: #be5a00;\n"
|
||||
"}\n"
|
||||
".yellow {\n"
|
||||
".t3 {\n"
|
||||
" color: #afaf32;\n"
|
||||
"}\n"
|
||||
".red {\n"
|
||||
".t4 {\n"
|
||||
" color: #c83232;\n"
|
||||
"}\n"
|
||||
"ul.tree li > div {\n"
|
||||
@@ -537,27 +413,11 @@ class StringUtils {
|
||||
return len >= suffixlen && s.compare(len - suffixlen, suffixlen, suffix) == 0;
|
||||
}
|
||||
|
||||
static std::string trim(const std::string& s, size_t maxchars) {
|
||||
if (maxchars < 3) {
|
||||
return "";
|
||||
} else if (s.length() > maxchars) {
|
||||
return s.substr(0, maxchars - 2) + "..";
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
static void replace(std::string& s, char c, const char* replacement) {
|
||||
for (size_t i = 0; (i = s.find(c, i)) != std::string::npos; i++) {
|
||||
s.replace(i, 1, replacement);
|
||||
}
|
||||
}
|
||||
|
||||
static void escape(std::string& s) {
|
||||
replace(s, '&', "&");
|
||||
replace(s, '<', "<");
|
||||
replace(s, '>', ">");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -588,183 +448,129 @@ class Format {
|
||||
};
|
||||
|
||||
|
||||
class Palette {
|
||||
private:
|
||||
const char* _name;
|
||||
int _base;
|
||||
int _r, _g, _b;
|
||||
|
||||
class Node {
|
||||
public:
|
||||
Palette(const char* name, int base, int r, int g, int b) : _name(name), _base(base), _r(r), _g(g), _b(b) {
|
||||
std::string _name;
|
||||
const Trie* _trie;
|
||||
|
||||
Node(const std::string& name, const Trie& trie) : _name(name), _trie(&trie) {
|
||||
}
|
||||
|
||||
const char* name() const {
|
||||
return _name;
|
||||
}
|
||||
|
||||
int pickColor() const {
|
||||
double value = double(rand()) / RAND_MAX;
|
||||
return _base + (int(_r * value) << 16 | int(_g * value) << 8 | int(_b * value));
|
||||
bool operator<(const Node& other) const {
|
||||
return _trie->_total > other._trie->_total;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void FlameGraph::dump(std::ostream& out, bool tree) {
|
||||
_scale = (_imagewidth - 20) / (double)_root._total;
|
||||
_pct = 100 / (double)_root._total;
|
||||
|
||||
u64 cutoff = (u64)ceil(_minwidth / _scale);
|
||||
_imageheight = _frameheight * _root.depth(cutoff) + 70;
|
||||
_mintotal = _minwidth == 0 && tree ? _root._total / 1000 : (u64)(_root._total * _minwidth / 100);
|
||||
int depth = _root.depth(_mintotal);
|
||||
|
||||
if (tree) {
|
||||
printTreeHeader(out);
|
||||
char buf[sizeof(TREE_HEADER) + 256];
|
||||
snprintf(buf, sizeof(buf) - 1, TREE_HEADER,
|
||||
_reverse ? "Backtrace" : "Call tree",
|
||||
_counter == COUNTER_SAMPLES ? "samples" : "counter",
|
||||
Format().thousands(_root._total));
|
||||
out << buf;
|
||||
|
||||
printTreeFrame(out, _root, 0);
|
||||
printTreeFooter(out);
|
||||
|
||||
out << TREE_FOOTER;
|
||||
} else {
|
||||
printHeader(out);
|
||||
printFrame(out, "all", _root, 10, _reverse ? 35 : (_imageheight - _frameheight - 35));
|
||||
printFooter(out);
|
||||
char buf[sizeof(FLAMEGRAPH_HEADER) + 256];
|
||||
snprintf(buf, sizeof(buf) - 1, FLAMEGRAPH_HEADER, _title, depth * 16, _reverse ? "true" : "false", depth);
|
||||
out << buf;
|
||||
|
||||
printFrame(out, "all", _root, 0, 0);
|
||||
|
||||
out << FLAMEGRAPH_FOOTER;
|
||||
}
|
||||
}
|
||||
|
||||
void FlameGraph::printHeader(std::ostream& out) {
|
||||
char buf[sizeof(SVG_HEADER) + 256];
|
||||
int x0 = _imagewidth / 2;
|
||||
int x1 = 10;
|
||||
int x2 = _imagewidth - 110;
|
||||
int y0 = 24;
|
||||
int y1 = _imageheight - 17;
|
||||
void FlameGraph::printFrame(std::ostream& out, const std::string& name, const Trie& f, int level, u64 x) {
|
||||
std::string name_copy = name;
|
||||
int type = frameType(name_copy);
|
||||
StringUtils::replace(name_copy, '\'', "\\'");
|
||||
|
||||
sprintf(buf, SVG_HEADER,
|
||||
_imagewidth, _imageheight, _imagewidth, _imageheight, _reverse,
|
||||
x0, y0, _title, x1, y1, x1, y0, x2, y0, x2, y1);
|
||||
out << buf;
|
||||
}
|
||||
snprintf(_buf, sizeof(_buf) - 1, "f(%d,%llu,%llu,%d,'%s')\n", level, x, f._total, type, name_copy.c_str());
|
||||
out << _buf;
|
||||
|
||||
void FlameGraph::printFooter(std::ostream& out) {
|
||||
out << "</g>\n</svg>\n";
|
||||
}
|
||||
|
||||
double FlameGraph::printFrame(std::ostream& out, const std::string& name, const Trie& f, double x, double y) {
|
||||
double framewidth = f._total * _scale;
|
||||
|
||||
// Skip too narrow frames, they are not important
|
||||
if (framewidth >= _minwidth) {
|
||||
std::string full_title = name;
|
||||
int color = selectFramePalette(full_title).pickColor();
|
||||
std::string short_title = StringUtils::trim(full_title, size_t(framewidth / 7));
|
||||
StringUtils::escape(full_title);
|
||||
StringUtils::escape(short_title);
|
||||
|
||||
// Compensate rounding error in frame width
|
||||
double w = (round((x + framewidth) * 10) - round(x * 10)) / 10.0;
|
||||
|
||||
snprintf(_buf, sizeof(_buf) - 1,
|
||||
"<g>\n"
|
||||
"<title>%s (%s samples, %.2f%%)</title><rect x=\"%.1f\" y=\"%.1f\" width=\"%.1f\" height=\"%d\" fill=\"#%06x\" rx=\"2\" ry=\"2\"/>\n"
|
||||
"<text x=\"%.1f\" y=\"%.1f\">%s</text>\n"
|
||||
"</g>\n",
|
||||
full_title.c_str(), Format().thousands(f._total), f._total * _pct, x, y, w, _frameheight - 1, color,
|
||||
x + 3, y + 3 + _frameheight * 0.5, short_title.c_str());
|
||||
out << _buf;
|
||||
|
||||
x += f._self * _scale;
|
||||
y += _reverse ? _frameheight : -_frameheight;
|
||||
|
||||
for (std::map<std::string, Trie>::const_iterator it = f._children.begin(); it != f._children.end(); ++it) {
|
||||
x += printFrame(out, it->first, it->second, x, y);
|
||||
x += f._self;
|
||||
for (std::map<std::string, Trie>::const_iterator it = f._children.begin(); it != f._children.end(); ++it) {
|
||||
if (it->second._total >= _mintotal) {
|
||||
printFrame(out, it->first, it->second, level + 1, x);
|
||||
}
|
||||
x += it->second._total;
|
||||
}
|
||||
|
||||
return framewidth;
|
||||
}
|
||||
|
||||
void FlameGraph::printTreeHeader(std::ostream& out) {
|
||||
char buf[sizeof(TREE_HEADER) + 256];
|
||||
const char* title = _reverse ? "Backtrace" : "Call tree";
|
||||
const char* counter = _counter == COUNTER_SAMPLES ? "samples" : "counter";
|
||||
sprintf(buf, TREE_HEADER, title, counter, Format().thousands(_root._total));
|
||||
out << buf;
|
||||
}
|
||||
|
||||
void FlameGraph::printTreeFooter(std::ostream& out) {
|
||||
out << TREE_FOOTER;
|
||||
}
|
||||
|
||||
bool FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int depth) {
|
||||
double framewidth = f._total * _scale;
|
||||
if (framewidth < _minwidth) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int level) {
|
||||
std::vector<Node> subnodes;
|
||||
for (std::map<std::string, Trie>::const_iterator it = f._children.begin(); it != f._children.end(); ++it) {
|
||||
subnodes.push_back(Node(it->first, it->second));
|
||||
}
|
||||
std::sort(subnodes.begin(), subnodes.end());
|
||||
|
||||
double pct = 100.0 / _root._total;
|
||||
for (size_t i = 0; i < subnodes.size(); i++) {
|
||||
std::string full_title = subnodes[i]._name;
|
||||
std::string name = subnodes[i]._name;
|
||||
const Trie* trie = subnodes[i]._trie;
|
||||
const char* color = selectFramePalette(full_title).name();
|
||||
StringUtils::escape(full_title);
|
||||
|
||||
int type = frameType(name);
|
||||
StringUtils::replace(name, '&', "&");
|
||||
StringUtils::replace(name, '<', "<");
|
||||
StringUtils::replace(name, '>', ">");
|
||||
|
||||
if (_reverse) {
|
||||
snprintf(_buf, sizeof(_buf) - 1,
|
||||
"<li><div>[%d] %.2f%% %s</div><span class=\"%s\"> %s</span>\n",
|
||||
depth,
|
||||
trie->_total * _pct, Format().thousands(trie->_total),
|
||||
color, full_title.c_str());
|
||||
"<li><div>[%d] %.2f%% %s</div><span class=\"t%d\"> %s</span>\n",
|
||||
level,
|
||||
trie->_total * pct, Format().thousands(trie->_total),
|
||||
type, name.c_str());
|
||||
} else {
|
||||
snprintf(_buf, sizeof(_buf) - 1,
|
||||
"<li><div>[%d] %.2f%% %s self: %.2f%% %s</div><span class=\"%s\"> %s</span>\n",
|
||||
depth,
|
||||
trie->_total * _pct, Format().thousands(trie->_total),
|
||||
trie->_self * _pct, Format().thousands(trie->_self),
|
||||
color, full_title.c_str());
|
||||
"<li><div>[%d] %.2f%% %s self: %.2f%% %s</div><span class=\"t%d\"> %s</span>\n",
|
||||
level,
|
||||
trie->_total * pct, Format().thousands(trie->_total),
|
||||
trie->_self * pct, Format().thousands(trie->_self),
|
||||
type, name.c_str());
|
||||
}
|
||||
out << _buf;
|
||||
|
||||
if (trie->_children.size() > 0) {
|
||||
out << "<ul>\n";
|
||||
if (!printTreeFrame(out, *trie, depth + 1)) {
|
||||
if (trie->_total >= _mintotal) {
|
||||
printTreeFrame(out, *trie, level + 1);
|
||||
} else {
|
||||
out << "<li>...\n";
|
||||
}
|
||||
out << "</ul>\n";
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const Palette& FlameGraph::selectFramePalette(std::string& name) {
|
||||
static const Palette
|
||||
green ("green", 0x50e150, 30, 30, 30),
|
||||
aqua ("aqua", 0x50bebe, 30, 30, 30),
|
||||
brown ("brown", 0xe17d00, 30, 30, 0),
|
||||
yellow("yellow", 0xc8c83c, 30, 30, 10),
|
||||
red ("red", 0xe15a5a, 30, 40, 40);
|
||||
|
||||
int FlameGraph::frameType(std::string& name) {
|
||||
if (StringUtils::endsWith(name, "_[j]", 4)) {
|
||||
// Java compiled frame
|
||||
name = name.substr(0, name.length() - 4);
|
||||
return green;
|
||||
return 0;
|
||||
} else if (StringUtils::endsWith(name, "_[i]", 4)) {
|
||||
// Java inlined frame
|
||||
name = name.substr(0, name.length() - 4);
|
||||
return aqua;
|
||||
return 1;
|
||||
} else if (StringUtils::endsWith(name, "_[k]", 4)) {
|
||||
// Kernel function
|
||||
name = name.substr(0, name.length() - 4);
|
||||
return brown;
|
||||
return 2;
|
||||
} else if (name.find("::") != std::string::npos || name.compare(0, 2, "-[") == 0 || name.compare(0, 2, "+[") == 0) {
|
||||
// C++ function or Objective C method
|
||||
return yellow;
|
||||
return 3;
|
||||
} else if ((int)name.find('/') > 0 || ((int)name.find('.') > 0 && name[0] >= 'A' && name[0] <= 'Z')) {
|
||||
// Java regular method
|
||||
return green;
|
||||
return 0;
|
||||
} else {
|
||||
// Other native code
|
||||
return red;
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,53 +57,27 @@ class Trie {
|
||||
}
|
||||
};
|
||||
|
||||
class Node {
|
||||
public:
|
||||
std::string _name;
|
||||
const Trie* _trie;
|
||||
|
||||
Node(std::string name, const Trie& trie) : _name(name), _trie(&trie) {
|
||||
}
|
||||
|
||||
bool operator<(const Node& other) const {
|
||||
return _trie->_total > other._trie->_total;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class Palette;
|
||||
|
||||
|
||||
class FlameGraph {
|
||||
private:
|
||||
Trie _root;
|
||||
char _buf[4096];
|
||||
u64 _mintotal;
|
||||
|
||||
const char* _title;
|
||||
Counter _counter;
|
||||
int _imagewidth;
|
||||
int _imageheight;
|
||||
int _frameheight;
|
||||
double _minwidth;
|
||||
double _scale;
|
||||
double _pct;
|
||||
bool _reverse;
|
||||
|
||||
void printHeader(std::ostream& out);
|
||||
void printFooter(std::ostream& out);
|
||||
double printFrame(std::ostream& out, const std::string& name, const Trie& f, double x, double y);
|
||||
void printTreeHeader(std::ostream& out);
|
||||
void printTreeFooter(std::ostream& out);
|
||||
bool printTreeFrame(std::ostream& out, const Trie& f, int depth);
|
||||
const Palette& selectFramePalette(std::string& name);
|
||||
void printFrame(std::ostream& out, const std::string& name, const Trie& f, int level, u64 x);
|
||||
void printTreeFrame(std::ostream& out, const Trie& f, int level);
|
||||
int frameType(std::string& name);
|
||||
|
||||
public:
|
||||
FlameGraph(const char* title, Counter counter, int width, int height, double minwidth, bool reverse) :
|
||||
FlameGraph(const char* title, Counter counter, double minwidth, bool reverse) :
|
||||
_root(),
|
||||
_title(title),
|
||||
_counter(counter),
|
||||
_imagewidth(width),
|
||||
_frameheight(height),
|
||||
_minwidth(minwidth),
|
||||
_reverse(reverse) {
|
||||
_buf[sizeof(_buf) - 1] = 0;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,9 +17,9 @@
|
||||
#ifndef _FLIGHTRECORDER_H
|
||||
#define _FLIGHTRECORDER_H
|
||||
|
||||
#include "arch.h"
|
||||
#include "arguments.h"
|
||||
#include "os.h"
|
||||
|
||||
#include "event.h"
|
||||
|
||||
class Recording;
|
||||
|
||||
@@ -31,10 +31,15 @@ class FlightRecorder {
|
||||
FlightRecorder() : _rec(NULL) {
|
||||
}
|
||||
|
||||
Error start(const char* file, bool reset);
|
||||
Error start(Arguments& args, bool reset);
|
||||
void stop();
|
||||
|
||||
void recordExecutionSample(int lock_index, int tid, int call_trace_id, ThreadState thread_state);
|
||||
bool active() {
|
||||
return _rec != NULL;
|
||||
}
|
||||
|
||||
void recordEvent(int lock_index, int tid, u32 call_trace_id,
|
||||
int event_type, Event* event, u64 counter);
|
||||
};
|
||||
|
||||
#endif // _FLIGHTRECORDER_H
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "frameName.h"
|
||||
#include "profiler.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
@@ -76,6 +77,7 @@ bool Matcher::matches(const char* s) {
|
||||
|
||||
FrameName::FrameName(Arguments& args, int style, Mutex& thread_names_lock, ThreadMap& thread_names) :
|
||||
_cache(),
|
||||
_class_names(),
|
||||
_include(),
|
||||
_exclude(),
|
||||
_style(style),
|
||||
@@ -88,6 +90,8 @@ 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);
|
||||
}
|
||||
|
||||
FrameName::~FrameName() {
|
||||
@@ -209,16 +213,16 @@ const char* FrameName::name(ASGCT_CallFrame& frame, bool for_matching) {
|
||||
case BCI_NATIVE_FRAME:
|
||||
return cppDemangle((const char*)frame.method_id);
|
||||
|
||||
case BCI_SYMBOL: {
|
||||
VMSymbol* symbol = (VMSymbol*)frame.method_id;
|
||||
char* class_name = javaClassName(symbol->body(), symbol->length(), _style | STYLE_DOTTED);
|
||||
return for_matching ? class_name : strcat(class_name, _style & STYLE_DOTTED ? "" : "_[i]");
|
||||
}
|
||||
|
||||
case BCI_SYMBOL_OUTSIDE_TLAB: {
|
||||
VMSymbol* symbol = (VMSymbol*)((uintptr_t)frame.method_id ^ 1);
|
||||
char* class_name = javaClassName(symbol->body(), symbol->length(), _style | STYLE_DOTTED);
|
||||
return for_matching ? class_name : strcat(class_name, _style & STYLE_DOTTED ? " (out)" : "_[k]");
|
||||
case BCI_ALLOC:
|
||||
case BCI_ALLOC_OUTSIDE_TLAB:
|
||||
case BCI_LOCK:
|
||||
case BCI_PARK: {
|
||||
const char* symbol = _class_names[(uintptr_t)frame.method_id];
|
||||
char* class_name = javaClassName(symbol, strlen(symbol), _style | STYLE_DOTTED);
|
||||
if (!for_matching && !(_style & STYLE_DOTTED)) {
|
||||
strcat(class_name, frame.bci == BCI_ALLOC_OUTSIDE_TLAB ? "_[k]" : "_[i]");
|
||||
}
|
||||
return class_name;
|
||||
}
|
||||
|
||||
case BCI_THREAD_ID: {
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
typedef std::map<jmethodID, std::string> JMethodCache;
|
||||
typedef std::map<int, std::string> ThreadMap;
|
||||
typedef std::map<unsigned int, const char*> ClassMap;
|
||||
|
||||
|
||||
enum MatchType {
|
||||
@@ -63,6 +64,7 @@ class Matcher {
|
||||
class FrameName {
|
||||
private:
|
||||
JMethodCache _cache;
|
||||
ClassMap _class_names;
|
||||
std::vector<Matcher> _include;
|
||||
std::vector<Matcher> _exclude;
|
||||
char _buf[800]; // must be large enough for class name + method name + method signature
|
||||
|
||||
@@ -480,7 +480,7 @@ char* Instrument::_target_class = NULL;
|
||||
bool Instrument::_instrument_class_loaded = false;
|
||||
u64 Instrument::_interval;
|
||||
volatile u64 Instrument::_calls;
|
||||
volatile bool Instrument::_enabled;
|
||||
volatile bool Instrument::_running;
|
||||
|
||||
Error Instrument::check(Arguments& args) {
|
||||
if (!_instrument_class_loaded) {
|
||||
@@ -509,10 +509,10 @@ 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;
|
||||
_enabled = true;
|
||||
_running = true;
|
||||
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL);
|
||||
@@ -522,7 +522,7 @@ Error Instrument::start(Arguments& args) {
|
||||
}
|
||||
|
||||
void Instrument::stop() {
|
||||
_enabled = false;
|
||||
_running = false;
|
||||
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
retransformMatchedClasses(jvmti); // undo transformation
|
||||
@@ -575,9 +575,7 @@ void JNICALL Instrument::ClassFileLoadHook(jvmtiEnv* jvmti, JNIEnv* jni,
|
||||
jint class_data_len, const u8* class_data,
|
||||
jint* new_class_data_len, u8** new_class_data) {
|
||||
// Do not retransform if the profiling has stopped
|
||||
if (!_enabled) {
|
||||
return;
|
||||
}
|
||||
if (!_running) return;
|
||||
|
||||
if (name == NULL || strcmp(name, _target_class) == 0) {
|
||||
BytecodeRewriter rewriter(class_data, class_data_len, _target_class);
|
||||
@@ -586,7 +584,10 @@ void JNICALL Instrument::ClassFileLoadHook(jvmtiEnv* jvmti, JNIEnv* jni,
|
||||
}
|
||||
|
||||
void JNICALL Instrument::recordSample(JNIEnv* jni, jobject unused) {
|
||||
if (!_enabled) return;
|
||||
|
||||
if (_interval <= 1 || ((atomicInc(_calls) + 1) % _interval) == 0) {
|
||||
Profiler::_instance.recordSample(NULL, _interval, BCI_INSTRUMENT, NULL);
|
||||
ExecutionEvent event;
|
||||
Profiler::_instance.recordSample(NULL, _interval, BCI_INSTRUMENT, &event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class Instrument : public Engine {
|
||||
static bool _instrument_class_loaded;
|
||||
static u64 _interval;
|
||||
static volatile u64 _calls;
|
||||
static volatile bool _enabled;
|
||||
static volatile bool _running;
|
||||
|
||||
public:
|
||||
const char* name() {
|
||||
|
||||
@@ -24,7 +24,10 @@ long ITimer::_interval;
|
||||
|
||||
|
||||
void ITimer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
Profiler::_instance.recordSample(ucontext, _interval, 0, NULL);
|
||||
if (!_enabled) return;
|
||||
|
||||
ExecutionEvent event;
|
||||
Profiler::_instance.recordSample(ucontext, _interval, 0, &event);
|
||||
}
|
||||
|
||||
Error ITimer::check(Arguments& args) {
|
||||
|
||||
@@ -28,10 +28,11 @@
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_one_profiler_AsyncProfiler_start0(JNIEnv* env, jobject unused, jstring event, jlong interval, jboolean reset) {
|
||||
Arguments args;
|
||||
args._event = env->GetStringUTFChars(event, NULL);
|
||||
const char* event_str = env->GetStringUTFChars(event, NULL);
|
||||
args.addEvent(event_str);
|
||||
args._interval = interval;
|
||||
Error error = Profiler::_instance.start(args, reset);
|
||||
env->ReleaseStringUTFChars(event, args._event);
|
||||
env->ReleaseStringUTFChars(event, event_str);
|
||||
|
||||
if (error) {
|
||||
JavaAPI::throwNew(env, "java/lang/IllegalStateException", error.message());
|
||||
@@ -108,11 +109,11 @@ Java_one_profiler_AsyncProfiler_filterThread0(JNIEnv* env, jobject unused, jthre
|
||||
#define F(name, sig) {(char*)#name, (char*)sig, (void*)Java_one_profiler_AsyncProfiler_##name}
|
||||
|
||||
static const JNINativeMethod profiler_natives[] = {
|
||||
F(start0, "(Ljava/lang/String;JZ)V"),
|
||||
F(stop0, "()V"),
|
||||
F(execute0, "(Ljava/lang/String;)Ljava/lang/String;"),
|
||||
F(getSamples, "()J"),
|
||||
F(filterThread0, "(Ljava/lang/Thread;Z)V"),
|
||||
F(start0, "(Ljava/lang/String;JZ)V"),
|
||||
F(stop0, "()V"),
|
||||
F(execute0, "(Ljava/lang/String;)Ljava/lang/String;"),
|
||||
F(getSamples, "()J"),
|
||||
F(filterThread0, "(Ljava/lang/Thread;Z)V"),
|
||||
};
|
||||
|
||||
#undef F
|
||||
|
||||
213
src/jfrMetadata.cpp
Normal file
213
src/jfrMetadata.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "jfrMetadata.h"
|
||||
|
||||
|
||||
std::map<std::string, int> Element::_string_map;
|
||||
std::vector<std::string> Element::_strings;
|
||||
|
||||
JfrMetadata JfrMetadata::_root;
|
||||
|
||||
JfrMetadata::JfrMetadata() : Element("root") {
|
||||
*this
|
||||
<< (element("metadata")
|
||||
|
||||
<< type("boolean", T_BOOLEAN)
|
||||
<< type("char", T_CHAR)
|
||||
<< type("float", T_FLOAT)
|
||||
<< type("double", T_DOUBLE)
|
||||
<< type("byte", T_BYTE)
|
||||
<< type("short", T_SHORT)
|
||||
<< type("int", T_INT)
|
||||
<< type("long", T_LONG)
|
||||
|
||||
<< type("java.lang.String", T_STRING)
|
||||
|
||||
<< (type("java.lang.Class", T_CLASS, "Java Class")
|
||||
<< field("classLoader", T_CLASS_LOADER, "Class Loader", F_CPOOL)
|
||||
<< field("name", T_SYMBOL, "Name", F_CPOOL)
|
||||
<< field("package", T_PACKAGE, "Package", F_CPOOL)
|
||||
<< field("modifiers", T_INT, "Access Modifiers"))
|
||||
|
||||
<< (type("java.lang.Thread", T_THREAD, "Thread")
|
||||
<< field("osName", T_STRING, "OS Thread Name")
|
||||
<< field("osThreadId", T_LONG, "OS Thread Id")
|
||||
<< field("javaName", T_STRING, "Java Thread Name")
|
||||
<< field("javaThreadId", T_LONG, "Java Thread Id"))
|
||||
|
||||
<< (type("jdk.types.ClassLoader", T_CLASS_LOADER, "Java Class Loader")
|
||||
<< field("type", T_CLASS, "Type", F_CPOOL)
|
||||
<< field("name", T_SYMBOL, "Name", F_CPOOL))
|
||||
|
||||
<< (type("jdk.types.FrameType", T_FRAME_TYPE, "Frame type", true)
|
||||
<< field("description", T_STRING, "Description"))
|
||||
|
||||
<< (type("jdk.types.ThreadState", T_THREAD_STATE, "Java Thread State", true)
|
||||
<< field("name", T_STRING, "Name"))
|
||||
|
||||
<< (type("jdk.types.StackTrace", T_STACK_TRACE, "Stacktrace")
|
||||
<< field("truncated", T_BOOLEAN, "Truncated")
|
||||
<< field("frames", T_STACK_FRAME, "Stack Frames", F_ARRAY))
|
||||
|
||||
<< (type("jdk.types.StackFrame", T_STACK_FRAME)
|
||||
<< field("method", T_METHOD, "Java Method", F_CPOOL)
|
||||
<< field("lineNumber", T_INT, "Line Number")
|
||||
<< field("bytecodeIndex", T_INT, "Bytecode Index")
|
||||
<< field("type", T_FRAME_TYPE, "Frame Type", F_CPOOL))
|
||||
|
||||
<< (type("jdk.types.Method", T_METHOD, "Java Method")
|
||||
<< field("type", T_CLASS, "Type", F_CPOOL)
|
||||
<< field("name", T_SYMBOL, "Name", F_CPOOL)
|
||||
<< field("descriptor", T_SYMBOL, "Descriptor", F_CPOOL)
|
||||
<< field("modifiers", T_INT, "Access Modifiers")
|
||||
<< field("hidden", T_BOOLEAN, "Hidden"))
|
||||
|
||||
<< (type("jdk.types.Package", T_PACKAGE, "Package")
|
||||
<< field("name", T_SYMBOL, "Name", F_CPOOL))
|
||||
|
||||
<< (type("jdk.types.Symbol", T_SYMBOL, "Symbol", true)
|
||||
<< field("string", T_STRING, "String"))
|
||||
|
||||
<< (type("jdk.ExecutionSample", T_EXECUTION_SAMPLE, "Method Profiling Sample")
|
||||
<< category("Java Virtual Machine", "Profiling")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("sampledThread", T_THREAD, "Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("state", T_THREAD_STATE, "Thread State", F_CPOOL))
|
||||
|
||||
<< (type("jdk.ObjectAllocationInNewTLAB", T_ALLOC_IN_NEW_TLAB, "Allocation in new TLAB")
|
||||
<< category("Java Application")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("objectClass", T_CLASS, "Object Class", F_CPOOL)
|
||||
<< field("allocationSize", T_LONG, "Allocation Size", F_BYTES)
|
||||
<< field("tlabSize", T_LONG, "TLAB Size", F_BYTES))
|
||||
|
||||
<< (type("jdk.ObjectAllocationOutsideTLAB", T_ALLOC_OUTSIDE_TLAB, "Allocation outside TLAB")
|
||||
<< category("Java Application")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("objectClass", T_CLASS, "Object Class", F_CPOOL)
|
||||
<< field("allocationSize", T_LONG, "Allocation Size", F_BYTES))
|
||||
|
||||
<< (type("jdk.JavaMonitorEnter", T_MONITOR_ENTER, "Java Monitor Blocked")
|
||||
<< category("Java Application")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("duration", T_LONG, "Duration", F_DURATION_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("monitorClass", T_CLASS, "Monitor Class", F_CPOOL)
|
||||
<< field("address", T_LONG, "Monitor Address", F_ADDRESS))
|
||||
|
||||
<< (type("jdk.ThreadPark", T_THREAD_PARK, "Java Thread Park")
|
||||
<< category("Java Application")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("duration", T_LONG, "Duration", F_DURATION_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("parkedClass", T_CLASS, "Class Parked On", F_CPOOL)
|
||||
<< field("timeout", T_LONG, "Park Timeout", F_DURATION_NANOS)
|
||||
<< field("address", T_LONG, "Address of Object Parked", F_ADDRESS))
|
||||
|
||||
<< (type("jdk.CPULoad", T_CPU_LOAD, "CPU Load")
|
||||
<< category("Operating System", "Processor")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("jvmUser", T_FLOAT, "JVM User", F_PERCENTAGE)
|
||||
<< field("jvmSystem", T_FLOAT, "JVM System", F_PERCENTAGE)
|
||||
<< field("machineTotal", T_FLOAT, "Machine Total", F_PERCENTAGE))
|
||||
|
||||
<< (type("jdk.ActiveRecording", T_ACTIVE_RECORDING, "Flight Recording")
|
||||
<< category("Flight Recorder")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("duration", T_LONG, "Duration", F_DURATION_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("id", T_LONG, "Id")
|
||||
<< field("name", T_STRING, "Name")
|
||||
<< field("destination", T_STRING, "Destination")
|
||||
<< field("maxAge", T_LONG, "Max Age", F_DURATION_MILLIS)
|
||||
<< field("maxSize", T_LONG, "Max Size", F_BYTES)
|
||||
<< field("recordingStart", T_LONG, "Start Time", F_TIME_MILLIS)
|
||||
<< field("recordingDuration", T_LONG, "Recording Duration", F_DURATION_MILLIS))
|
||||
|
||||
<< (type("jdk.ActiveSetting", T_ACTIVE_SETTING, "Recording Setting")
|
||||
<< category("Flight Recorder")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("duration", T_LONG, "Duration", F_DURATION_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("id", T_LONG, "Event Id")
|
||||
<< field("name", T_STRING, "Setting Name")
|
||||
<< field("value", T_STRING, "Setting Value"))
|
||||
|
||||
<< (type("jdk.OSInformation", T_OS_INFORMATION, "OS Information")
|
||||
<< category("Operating System")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("osVersion", T_STRING, "OS Version"))
|
||||
|
||||
<< (type("jdk.CPUInformation", T_CPU_INFORMATION, "CPU Information")
|
||||
<< category("Operating System", "Processor")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("cpu", T_STRING, "Type")
|
||||
<< field("description", T_STRING, "Description")
|
||||
<< field("sockets", T_INT, "Sockets", F_UNSIGNED)
|
||||
<< field("cores", T_INT, "Cores", F_UNSIGNED)
|
||||
<< field("hwThreads", T_INT, "Hardware Threads", F_UNSIGNED))
|
||||
|
||||
<< (type("jdk.JVMInformation", T_JVM_INFORMATION, "JVM Information")
|
||||
<< category("Java Virtual Machine")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("jvmName", T_STRING, "JVM Name")
|
||||
<< field("jvmVersion", T_STRING, "JVM Version")
|
||||
<< field("jvmArguments", T_STRING, "JVM Command Line Arguments")
|
||||
<< field("jvmFlags", T_STRING, "JVM Settings File Arguments")
|
||||
<< field("javaArguments", T_STRING, "Java Application Arguments")
|
||||
<< field("jvmStartTime", T_LONG, "JVM Start Time", F_TIME_MILLIS)
|
||||
<< field("pid", T_LONG, "Process Identifier"))
|
||||
|
||||
<< (type("jdk.InitialSystemProperty", T_INITIAL_SYSTEM_PROPERTY, "Initial System Property")
|
||||
<< category("Java Virtual Machine")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("key", T_STRING, "Key")
|
||||
<< field("value", T_STRING, "Value"))
|
||||
|
||||
<< (type("jdk.jfr.Label", T_LABEL, NULL)
|
||||
<< field("value", T_STRING))
|
||||
|
||||
<< (type("jdk.jfr.Category", T_CATEGORY, NULL)
|
||||
<< field("value", T_STRING, NULL, F_ARRAY))
|
||||
|
||||
<< (type("jdk.jfr.Timestamp", T_TIMESTAMP, "Timestamp")
|
||||
<< field("value", T_STRING))
|
||||
|
||||
<< (type("jdk.jfr.Timespan", T_TIMESPAN, "Timespan")
|
||||
<< field("value", T_STRING))
|
||||
|
||||
<< (type("jdk.jfr.DataAmount", T_DATA_AMOUNT, "Data Amount")
|
||||
<< field("value", T_STRING))
|
||||
|
||||
<< type("jdk.jfr.MemoryAddress", T_MEMORY_ADDRESS, "Memory Address")
|
||||
|
||||
<< type("jdk.jfr.Unsigned", T_UNSIGNED, "Unsigned Value")
|
||||
|
||||
<< type("jdk.jfr.Percentage", T_PERCENTAGE, "Percentage"))
|
||||
|
||||
<< element("region").attribute("locale", "en_US").attribute("gmtOffset", "0");
|
||||
|
||||
// The map is used only during construction
|
||||
_string_map.clear();
|
||||
}
|
||||
231
src/jfrMetadata.h
Normal file
231
src/jfrMetadata.h
Normal file
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _JFRMETADATA_H
|
||||
#define _JFRMETADATA_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
enum JfrType {
|
||||
T_METADATA = 0,
|
||||
T_CPOOL = 1,
|
||||
|
||||
T_BOOLEAN = 4,
|
||||
T_CHAR = 5,
|
||||
T_FLOAT = 6,
|
||||
T_DOUBLE = 7,
|
||||
T_BYTE = 8,
|
||||
T_SHORT = 9,
|
||||
T_INT = 10,
|
||||
T_LONG = 11,
|
||||
|
||||
T_STRING = 20,
|
||||
T_CLASS = 21,
|
||||
T_THREAD = 22,
|
||||
T_CLASS_LOADER = 23,
|
||||
T_FRAME_TYPE = 24,
|
||||
T_THREAD_STATE = 25,
|
||||
T_STACK_TRACE = 26,
|
||||
T_STACK_FRAME = 27,
|
||||
T_METHOD = 28,
|
||||
T_PACKAGE = 29,
|
||||
T_SYMBOL = 30,
|
||||
|
||||
T_EVENT = 100,
|
||||
T_EXECUTION_SAMPLE = 101,
|
||||
T_ALLOC_IN_NEW_TLAB = 102,
|
||||
T_ALLOC_OUTSIDE_TLAB = 103,
|
||||
T_MONITOR_ENTER = 104,
|
||||
T_THREAD_PARK = 105,
|
||||
T_CPU_LOAD = 106,
|
||||
T_ACTIVE_RECORDING = 107,
|
||||
T_ACTIVE_SETTING = 108,
|
||||
T_OS_INFORMATION = 109,
|
||||
T_CPU_INFORMATION = 110,
|
||||
T_JVM_INFORMATION = 111,
|
||||
T_INITIAL_SYSTEM_PROPERTY = 112,
|
||||
|
||||
T_ANNOTATION = 200,
|
||||
T_LABEL = 201,
|
||||
T_CATEGORY = 202,
|
||||
T_TIMESTAMP = 203,
|
||||
T_TIMESPAN = 204,
|
||||
T_DATA_AMOUNT = 205,
|
||||
T_MEMORY_ADDRESS = 206,
|
||||
T_UNSIGNED = 207,
|
||||
T_PERCENTAGE = 208,
|
||||
};
|
||||
|
||||
|
||||
class Attribute {
|
||||
public:
|
||||
int _key;
|
||||
int _value;
|
||||
|
||||
Attribute(int key, int value) : _key(key), _value(value) {
|
||||
}
|
||||
};
|
||||
|
||||
class Element {
|
||||
protected:
|
||||
static std::map<std::string, int> _string_map;
|
||||
static std::vector<std::string> _strings;
|
||||
|
||||
static int getId(const char* s) {
|
||||
std::string str(s);
|
||||
int id = _string_map[str];
|
||||
if (id == 0) {
|
||||
id = _string_map[str] = _string_map.size();
|
||||
_strings.push_back(str);
|
||||
}
|
||||
return id - 1;
|
||||
}
|
||||
|
||||
public:
|
||||
const int _name;
|
||||
std::vector<Attribute> _attributes;
|
||||
std::vector<const Element*> _children;
|
||||
|
||||
Element(const char* name) : _name(getId(name)), _attributes(), _children() {
|
||||
}
|
||||
|
||||
Element& attribute(const char* key, const char* value) {
|
||||
_attributes.push_back(Attribute(getId(key), getId(value)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Element& attribute(const char* key, JfrType value) {
|
||||
char value_str[16];
|
||||
sprintf(value_str, "%d", value);
|
||||
return attribute(key, value_str);
|
||||
}
|
||||
|
||||
Element& operator<<(const Element& child) {
|
||||
_children.push_back(&child);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
class JfrMetadata : Element {
|
||||
private:
|
||||
static JfrMetadata _root;
|
||||
|
||||
enum FieldFlags {
|
||||
F_CPOOL = 0x1,
|
||||
F_ARRAY = 0x2,
|
||||
F_UNSIGNED = 0x4,
|
||||
F_BYTES = 0x8,
|
||||
F_TIME_TICKS = 0x10,
|
||||
F_TIME_MILLIS = 0x20,
|
||||
F_DURATION_TICKS = 0x40,
|
||||
F_DURATION_NANOS = 0x80,
|
||||
F_DURATION_MILLIS = 0x100,
|
||||
F_ADDRESS = 0x200,
|
||||
F_PERCENTAGE = 0x400,
|
||||
};
|
||||
|
||||
static Element& element(const char* name) {
|
||||
return *new Element(name);
|
||||
}
|
||||
|
||||
static Element& type(const char* name, JfrType id, const char* label = NULL, bool simple = false) {
|
||||
Element& e = element("class");
|
||||
e.attribute("name", name);
|
||||
e.attribute("id", id);
|
||||
if (simple) {
|
||||
e.attribute("simpleType", "true");
|
||||
} else if (id > T_ANNOTATION) {
|
||||
e.attribute("superType", "java.lang.annotation.Annotation");
|
||||
} else if (id > T_EVENT) {
|
||||
e.attribute("superType", "jdk.jfr.Event");
|
||||
}
|
||||
if (label != NULL) {
|
||||
e << annotation(T_LABEL, label);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
static Element& field(const char* name, JfrType type, const char* label = NULL, int flags = 0) {
|
||||
Element& e = element("field");
|
||||
e.attribute("name", name);
|
||||
e.attribute("class", type);
|
||||
if (flags & F_CPOOL) {
|
||||
e.attribute("constantPool", "true");
|
||||
}
|
||||
if (flags & F_ARRAY) {
|
||||
e.attribute("dimension", "1");
|
||||
}
|
||||
if (label != NULL) {
|
||||
e << annotation(T_LABEL, label);
|
||||
}
|
||||
if (flags & F_UNSIGNED) {
|
||||
e << annotation(T_UNSIGNED);
|
||||
} else if (flags & F_BYTES) {
|
||||
e << annotation(T_UNSIGNED) << annotation(T_DATA_AMOUNT, "BYTES");
|
||||
} else if (flags & F_TIME_TICKS) {
|
||||
e << annotation(T_TIMESTAMP, "TICKS");
|
||||
} else if (flags & F_TIME_MILLIS) {
|
||||
e << annotation(T_TIMESTAMP, "MILLISECONDS_SINCE_EPOCH");
|
||||
} else if (flags & F_DURATION_TICKS) {
|
||||
e << annotation(T_TIMESPAN, "TICKS");
|
||||
} else if (flags & F_DURATION_NANOS) {
|
||||
e << annotation(T_TIMESPAN, "NANOSECONDS");
|
||||
} else if (flags & F_DURATION_MILLIS) {
|
||||
e << annotation(T_TIMESPAN, "MILLISECONDS");
|
||||
} else if (flags & F_ADDRESS) {
|
||||
e << annotation(T_UNSIGNED) << annotation(T_MEMORY_ADDRESS);
|
||||
} else if (flags & F_PERCENTAGE) {
|
||||
e << annotation(T_PERCENTAGE);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
static Element& annotation(JfrType type, const char* value = NULL) {
|
||||
Element& e = element("annotation");
|
||||
e.attribute("class", type);
|
||||
if (value != NULL) {
|
||||
e.attribute("value", value);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
static Element& category(const char* value0, const char* value1 = NULL) {
|
||||
Element& e = annotation(T_CATEGORY);
|
||||
e.attribute("value-0", value0);
|
||||
if (value1 != NULL) {
|
||||
e.attribute("value-1", value1);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
public:
|
||||
JfrMetadata();
|
||||
|
||||
static Element* root() {
|
||||
return &_root;
|
||||
}
|
||||
|
||||
static std::vector<std::string>& strings() {
|
||||
return _strings;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _JFRMETADATA_H
|
||||
71
src/jstack.cpp
Normal file
71
src/jstack.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include "jstack.h"
|
||||
#include "profiler.h"
|
||||
|
||||
|
||||
// Wait at most this number of milliseconds to finish processing of pending signals
|
||||
const int MAX_WAIT_MILLIS = 2000;
|
||||
|
||||
|
||||
void JStack::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
ExecutionEvent event;
|
||||
event._thread_state = Profiler::_instance.getThreadState(ucontext);
|
||||
Profiler::_instance.recordSample(ucontext, 1, 0, &event);
|
||||
}
|
||||
|
||||
Error JStack::start(Arguments& args) {
|
||||
OS::installSignalHandler(SIGVTALRM, signalHandler);
|
||||
|
||||
int self = OS::threadId();
|
||||
u64 required_samples = Profiler::_instance.total_samples();
|
||||
ThreadFilter* thread_filter = Profiler::_instance.threadFilter();
|
||||
bool thread_filter_enabled = thread_filter->enabled();
|
||||
|
||||
ThreadList* thread_list = OS::listThreads();
|
||||
int thread_id;
|
||||
while ((thread_id = thread_list->next()) != -1) {
|
||||
if (thread_id != self && (!thread_filter_enabled || thread_filter->accept(thread_id))) {
|
||||
if (OS::sendSignalToThread(thread_id, SIGVTALRM)) {
|
||||
required_samples++;
|
||||
}
|
||||
}
|
||||
}
|
||||
delete thread_list;
|
||||
|
||||
// Get our own stack trace after all other threads
|
||||
if (!thread_filter_enabled || thread_filter->accept(self)) {
|
||||
ExecutionEvent event;
|
||||
event._thread_state = THREAD_RUNNING;
|
||||
Profiler::_instance.recordSample(NULL, 1, 0, &event);
|
||||
required_samples++;
|
||||
}
|
||||
|
||||
// Wait until all asynchronous stack traces collected
|
||||
for (int i = 0; Profiler::_instance.total_samples() < required_samples && i < MAX_WAIT_MILLIS; i++) {
|
||||
struct timespec timeout = {0, 1000000};
|
||||
nanosleep(&timeout, NULL);
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void JStack::stop() {
|
||||
// Nothing to do
|
||||
}
|
||||
41
src/jstack.h
Normal file
41
src/jstack.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _JSTACK_H
|
||||
#define _JSTACK_H
|
||||
|
||||
#include <signal.h>
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
class JStack : public Engine {
|
||||
private:
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
|
||||
public:
|
||||
const char* name() {
|
||||
return EVENT_JSTACK;
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "samples";
|
||||
}
|
||||
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
};
|
||||
|
||||
#endif // _JSTACK_H
|
||||
106
src/linearAllocator.cpp
Normal file
106
src/linearAllocator.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "linearAllocator.h"
|
||||
#include "os.h"
|
||||
|
||||
|
||||
LinearAllocator::LinearAllocator(size_t chunk_size) {
|
||||
_chunk_size = chunk_size;
|
||||
_reserve = _tail = allocateChunk(NULL);
|
||||
}
|
||||
|
||||
LinearAllocator::~LinearAllocator() {
|
||||
clear();
|
||||
freeChunk(_tail);
|
||||
}
|
||||
|
||||
void LinearAllocator::clear() {
|
||||
if (_reserve->prev == _tail) {
|
||||
freeChunk(_reserve);
|
||||
}
|
||||
while (_tail->prev != NULL) {
|
||||
Chunk* current = _tail;
|
||||
_tail = _tail->prev;
|
||||
freeChunk(current);
|
||||
}
|
||||
_reserve = _tail;
|
||||
_tail->offs = sizeof(Chunk);
|
||||
}
|
||||
|
||||
void* LinearAllocator::alloc(size_t size) {
|
||||
Chunk* chunk = _tail;
|
||||
|
||||
do {
|
||||
// Fast path: bump a pointer with CAS
|
||||
for (size_t offs = chunk->offs; offs + size <= _chunk_size; offs = chunk->offs) {
|
||||
if (__sync_bool_compare_and_swap(&chunk->offs, offs, offs + size)) {
|
||||
if (_chunk_size / 2 - offs < size) {
|
||||
// Stepped over a middle of the chunk - it's time to prepare a new one
|
||||
reserveChunk(chunk);
|
||||
}
|
||||
return (char*)chunk + offs;
|
||||
}
|
||||
}
|
||||
} while ((chunk = getNextChunk(chunk)) != NULL);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Chunk* LinearAllocator::allocateChunk(Chunk* current) {
|
||||
Chunk* chunk = (Chunk*)OS::safeAlloc(_chunk_size);
|
||||
if (chunk != NULL) {
|
||||
chunk->prev = current;
|
||||
chunk->offs = sizeof(Chunk);
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
|
||||
void LinearAllocator::freeChunk(Chunk* current) {
|
||||
OS::safeFree(current, _chunk_size);
|
||||
}
|
||||
|
||||
void LinearAllocator::reserveChunk(Chunk* current) {
|
||||
Chunk* reserve = allocateChunk(current);
|
||||
if (reserve != NULL && !__sync_bool_compare_and_swap(&_reserve, current, reserve)) {
|
||||
// Unlikely case that we are too late
|
||||
freeChunk(reserve);
|
||||
}
|
||||
}
|
||||
|
||||
Chunk* LinearAllocator::getNextChunk(Chunk* current) {
|
||||
Chunk* reserve = _reserve;
|
||||
|
||||
if (reserve == current) {
|
||||
// Unlikely case: no reserve yet.
|
||||
// It's probably being allocated right now, so let's compete
|
||||
reserve = allocateChunk(current);
|
||||
if (reserve == NULL) {
|
||||
// Not enough memory
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Chunk* prev_reserve = __sync_val_compare_and_swap(&_reserve, current, reserve);
|
||||
if (prev_reserve != current) {
|
||||
freeChunk(reserve);
|
||||
reserve = prev_reserve;
|
||||
}
|
||||
}
|
||||
|
||||
// Expected case: a new chunk is already reserved
|
||||
Chunk* tail = __sync_val_compare_and_swap(&_tail, current, reserve);
|
||||
return tail == current ? reserve : tail;
|
||||
}
|
||||
50
src/linearAllocator.h
Normal file
50
src/linearAllocator.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _LINEARALLOCATOR_H
|
||||
#define _LINEARALLOCATOR_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
struct Chunk {
|
||||
Chunk* prev;
|
||||
volatile size_t offs;
|
||||
// To avoid false sharing
|
||||
char _padding[56];
|
||||
};
|
||||
|
||||
class LinearAllocator {
|
||||
private:
|
||||
size_t _chunk_size;
|
||||
Chunk* _tail;
|
||||
Chunk* _reserve;
|
||||
|
||||
Chunk* allocateChunk(Chunk* current);
|
||||
void freeChunk(Chunk* current);
|
||||
void reserveChunk(Chunk* current);
|
||||
Chunk* getNextChunk(Chunk* current);
|
||||
|
||||
public:
|
||||
LinearAllocator(size_t chunk_size);
|
||||
~LinearAllocator();
|
||||
|
||||
void clear();
|
||||
|
||||
void* alloc(size_t size);
|
||||
};
|
||||
|
||||
#endif // _LINEARALLOCATOR_H
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <string.h>
|
||||
#include "lockTracer.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
@@ -29,7 +30,7 @@ Error LockTracer::start(Arguments& args) {
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTER, NULL);
|
||||
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, NULL);
|
||||
jvmti->GetTime(&_start_time);
|
||||
_start_time = OS::nanotime();
|
||||
|
||||
if (_getBlocker == NULL) {
|
||||
JNIEnv* env = VM::jni();
|
||||
@@ -58,75 +59,88 @@ void LockTracer::stop() {
|
||||
}
|
||||
|
||||
void JNICALL LockTracer::MonitorContendedEnter(jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jobject object) {
|
||||
jlong enter_time;
|
||||
jvmti->GetTime(&enter_time);
|
||||
jlong enter_time = OS::nanotime();
|
||||
jvmti->SetTag(thread, enter_time);
|
||||
}
|
||||
|
||||
void JNICALL LockTracer::MonitorContendedEntered(jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jobject object) {
|
||||
jlong enter_time, entered_time;
|
||||
jvmti->GetTime(&entered_time);
|
||||
jlong entered_time = OS::nanotime();
|
||||
jlong enter_time;
|
||||
jvmti->GetTag(thread, &enter_time);
|
||||
|
||||
// Time is meaningless if lock attempt has started before profiling
|
||||
if (enter_time >= _start_time) {
|
||||
recordContendedLock(env, env->GetObjectClass(object), entered_time - enter_time);
|
||||
if (_enabled && enter_time >= _start_time) {
|
||||
char* lock_name = getLockName(jvmti, env, object);
|
||||
recordContendedLock(BCI_LOCK, enter_time, entered_time, lock_name, object, 0);
|
||||
jvmti->Deallocate((unsigned char*)lock_name);
|
||||
}
|
||||
}
|
||||
|
||||
void JNICALL LockTracer::UnsafeParkTrap(JNIEnv* env, jobject instance, jboolean isAbsolute, jlong time) {
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jclass lock_class = getParkBlockerClass(jvmti, env);
|
||||
jobject park_blocker = _enabled ? getParkBlocker(jvmti, env) : NULL;
|
||||
jlong park_start_time, park_end_time;
|
||||
|
||||
if (lock_class != NULL) {
|
||||
jvmti->GetTime(&park_start_time);
|
||||
if (park_blocker != NULL) {
|
||||
park_start_time = OS::nanotime();
|
||||
}
|
||||
|
||||
|
||||
VMStructs::_unsafe_park(env, instance, isAbsolute, time);
|
||||
|
||||
if (lock_class != NULL) {
|
||||
jvmti->GetTime(&park_end_time);
|
||||
recordContendedLock(env, lock_class, park_end_time - park_start_time);
|
||||
if (park_blocker != NULL) {
|
||||
park_end_time = OS::nanotime();
|
||||
|
||||
char* lock_name = getLockName(jvmti, env, park_blocker);
|
||||
if (lock_name == NULL || isConcurrentLock(lock_name)) {
|
||||
recordContendedLock(BCI_PARK, park_start_time, park_end_time, lock_name, park_blocker, time);
|
||||
}
|
||||
jvmti->Deallocate((unsigned char*)lock_name);
|
||||
}
|
||||
}
|
||||
|
||||
jclass LockTracer::getParkBlockerClass(jvmtiEnv* jvmti, JNIEnv* env) {
|
||||
jobject LockTracer::getParkBlocker(jvmtiEnv* jvmti, JNIEnv* env) {
|
||||
jthread thread;
|
||||
if (jvmti->GetCurrentThread(&thread) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Call LockSupport.getBlocker(Thread.currentThread())
|
||||
jobject park_blocker = env->CallStaticObjectMethod(_LockSupport, _getBlocker, thread);
|
||||
if (park_blocker == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
jclass lock_class = env->GetObjectClass(park_blocker);
|
||||
char* class_name;
|
||||
if (jvmti->GetClassSignature(lock_class, &class_name, NULL) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Do not count synchronizers other than ReentrantLock, ReentrantReadWriteLock and Semaphore
|
||||
if (strncmp(class_name, "Ljava/util/concurrent/locks/ReentrantLock", 41) != 0 &&
|
||||
strncmp(class_name, "Ljava/util/concurrent/locks/ReentrantReadWriteLock", 50) != 0 &&
|
||||
strncmp(class_name, "Ljava/util/concurrent/Semaphore", 31) != 0) {
|
||||
lock_class = NULL;
|
||||
}
|
||||
|
||||
jvmti->Deallocate((unsigned char*)class_name);
|
||||
return lock_class;
|
||||
return env->CallStaticObjectMethod(_LockSupport, _getBlocker, thread);
|
||||
}
|
||||
|
||||
void LockTracer::recordContendedLock(JNIEnv* env, jclass lock_class, jlong time) {
|
||||
if (VMStructs::hasClassNames()) {
|
||||
VMSymbol* lock_name = VMKlass::fromJavaClass(env, lock_class)->name();
|
||||
Profiler::_instance.recordSample(NULL, time, BCI_SYMBOL, (jmethodID)lock_name);
|
||||
} else {
|
||||
Profiler::_instance.recordSample(NULL, time, BCI_SYMBOL, NULL);
|
||||
char* LockTracer::getLockName(jvmtiEnv* jvmti, JNIEnv* env, jobject lock) {
|
||||
char* class_name;
|
||||
if (jvmti->GetClassSignature(env->GetObjectClass(lock), &class_name, NULL) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
return class_name;
|
||||
}
|
||||
|
||||
bool LockTracer::isConcurrentLock(const char* lock_name) {
|
||||
// Do not count synchronizers other than ReentrantLock, ReentrantReadWriteLock and Semaphore
|
||||
return strncmp(lock_name, "Ljava/util/concurrent/locks/ReentrantLock", 41) == 0 ||
|
||||
strncmp(lock_name, "Ljava/util/concurrent/locks/ReentrantReadWriteLock", 50) == 0 ||
|
||||
strncmp(lock_name, "Ljava/util/concurrent/Semaphore", 31) == 0;
|
||||
}
|
||||
|
||||
void LockTracer::recordContendedLock(int event_type, u64 start_time, u64 end_time,
|
||||
const char* lock_name, jobject lock, jlong timeout) {
|
||||
LockEvent event;
|
||||
event._class_id = 0;
|
||||
event._start_time = start_time;
|
||||
event._end_time = end_time;
|
||||
event._address = *(uintptr_t*)lock;
|
||||
event._timeout = timeout;
|
||||
|
||||
if (lock_name != NULL) {
|
||||
if (lock_name[0] == 'L') {
|
||||
event._class_id = Profiler::_instance.classMap()->lookup(lock_name + 1, strlen(lock_name) - 2);
|
||||
} else {
|
||||
event._class_id = Profiler::_instance.classMap()->lookup(lock_name);
|
||||
}
|
||||
}
|
||||
|
||||
Profiler::_instance.recordSample(NULL, end_time - start_time, event_type, &event);
|
||||
}
|
||||
|
||||
void LockTracer::bindUnsafePark(UnsafeParkFunc entry) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#define _LOCKTRACER_H
|
||||
|
||||
#include <jvmti.h>
|
||||
#include "arch.h"
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
@@ -29,8 +30,11 @@ class LockTracer : public Engine {
|
||||
static jclass _LockSupport;
|
||||
static jmethodID _getBlocker;
|
||||
|
||||
static jclass getParkBlockerClass(jvmtiEnv* jvmti, JNIEnv* env);
|
||||
static void recordContendedLock(JNIEnv* env, jclass lock_class, jlong time);
|
||||
static jobject getParkBlocker(jvmtiEnv* jvmti, JNIEnv* env);
|
||||
static char* getLockName(jvmtiEnv* jvmti, JNIEnv* env, jobject lock);
|
||||
static bool isConcurrentLock(const char* lock_name);
|
||||
static void recordContendedLock(int event_type, u64 start_time, u64 end_time,
|
||||
const char* lock_name, jobject lock, jlong timeout);
|
||||
static void bindUnsafePark(UnsafeParkFunc entry);
|
||||
|
||||
public:
|
||||
|
||||
17
src/os.h
17
src/os.h
@@ -29,6 +29,10 @@ enum ThreadState {
|
||||
};
|
||||
|
||||
|
||||
class Timer {
|
||||
};
|
||||
|
||||
|
||||
class ThreadList {
|
||||
public:
|
||||
virtual ~ThreadList() {}
|
||||
@@ -42,15 +46,18 @@ class OS {
|
||||
private:
|
||||
typedef void (*SigAction)(int, siginfo_t*, void*);
|
||||
typedef void (*SigHandler)(int);
|
||||
typedef void (*TimerCallback)(void*);
|
||||
|
||||
public:
|
||||
static u64 nanotime();
|
||||
static u64 millis();
|
||||
static u64 processStartTime();
|
||||
|
||||
static u64 hton64(u64 x);
|
||||
static u64 ntoh64(u64 x);
|
||||
|
||||
static int getMaxThreadId();
|
||||
static int processId();
|
||||
static int threadId();
|
||||
static bool threadName(int thread_id, char* name_buf, size_t name_len);
|
||||
static ThreadState threadState(int thread_id);
|
||||
@@ -60,6 +67,16 @@ class OS {
|
||||
|
||||
static void installSignalHandler(int signo, SigAction action, SigHandler handler = NULL);
|
||||
static bool sendSignalToThread(int thread_id, int signo);
|
||||
|
||||
static void* safeAlloc(size_t size);
|
||||
static void safeFree(void* addr, size_t size);
|
||||
|
||||
static Timer* startTimer(u64 interval, TimerCallback callback, void* arg);
|
||||
static void stopTimer(Timer* timer);
|
||||
|
||||
static bool getCpuDescription(char* buf, size_t size);
|
||||
static u64 getProcessCpuTime(u64* utime, u64* stime);
|
||||
static u64 getTotalCpuTime(u64* utime, u64* stime);
|
||||
};
|
||||
|
||||
#endif // _OS_H
|
||||
|
||||
122
src/os_linux.cpp
122
src/os_linux.cpp
@@ -23,15 +23,24 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/times.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include "os.h"
|
||||
|
||||
|
||||
#ifdef __LP64__
|
||||
# define MMAP_SYSCALL __NR_mmap
|
||||
#else
|
||||
# define MMAP_SYSCALL __NR_mmap2
|
||||
#endif
|
||||
|
||||
|
||||
class LinuxThreadList : public ThreadList {
|
||||
private:
|
||||
DIR* _dir;
|
||||
@@ -110,6 +119,22 @@ u64 OS::millis() {
|
||||
return (u64)tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
||||
}
|
||||
|
||||
u64 OS::processStartTime() {
|
||||
static u64 start_time = 0;
|
||||
|
||||
if (start_time == 0) {
|
||||
char buf[64];
|
||||
sprintf(buf, "/proc/%d", processId());
|
||||
|
||||
struct stat st;
|
||||
if (stat(buf, &st) == 0) {
|
||||
start_time = (u64)st.st_mtim.tv_sec * 1000 + st.st_mtim.tv_nsec / 1000000;
|
||||
}
|
||||
}
|
||||
|
||||
return start_time;
|
||||
}
|
||||
|
||||
u64 OS::hton64(u64 x) {
|
||||
return htonl(1) == 1 ? x : bswap_64(x);
|
||||
}
|
||||
@@ -129,6 +154,12 @@ int OS::getMaxThreadId() {
|
||||
return atoi(buf);
|
||||
}
|
||||
|
||||
int OS::processId() {
|
||||
static const int self_pid = getpid();
|
||||
|
||||
return self_pid;
|
||||
}
|
||||
|
||||
int OS::threadId() {
|
||||
return syscall(__NR_gettid);
|
||||
}
|
||||
@@ -193,9 +224,96 @@ void OS::installSignalHandler(int signo, SigAction action, SigHandler handler) {
|
||||
}
|
||||
|
||||
bool OS::sendSignalToThread(int thread_id, int signo) {
|
||||
static const int self_pid = getpid();
|
||||
return syscall(__NR_tgkill, processId(), thread_id, signo) == 0;
|
||||
}
|
||||
|
||||
return syscall(__NR_tgkill, self_pid, thread_id, signo) == 0;
|
||||
void* OS::safeAlloc(size_t size) {
|
||||
// Naked syscall can be used inside a signal handler.
|
||||
// Also, we don't want to catch our own calls when profiling mmap.
|
||||
intptr_t result = syscall(MMAP_SYSCALL, NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (result < 0 && result > -4096) {
|
||||
return NULL;
|
||||
}
|
||||
return (void*)result;
|
||||
}
|
||||
|
||||
void OS::safeFree(void* addr, size_t size) {
|
||||
syscall(__NR_munmap, addr, size);
|
||||
}
|
||||
|
||||
Timer* OS::startTimer(u64 interval, TimerCallback callback, void* arg) {
|
||||
struct sigevent sev;
|
||||
sev.sigev_notify = SIGEV_THREAD;
|
||||
sev.sigev_value.sival_ptr = arg;
|
||||
sev.sigev_notify_function = (void (*)(union sigval)) callback;
|
||||
sev.sigev_notify_attributes = NULL;
|
||||
|
||||
timer_t timer;
|
||||
if (timer_create(CLOCK_MONOTONIC, &sev, &timer) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct itimerspec spec;
|
||||
spec.it_interval.tv_sec = spec.it_value.tv_sec = interval / 1000000000;
|
||||
spec.it_interval.tv_nsec = spec.it_value.tv_nsec = interval % 1000000000;
|
||||
timer_settime(timer, 0, &spec, NULL);
|
||||
|
||||
return (Timer*)timer;
|
||||
}
|
||||
|
||||
void OS::stopTimer(Timer* timer) {
|
||||
timer_delete((timer_t)timer);
|
||||
}
|
||||
|
||||
bool OS::getCpuDescription(char* buf, size_t size) {
|
||||
int fd = open("/proc/cpuinfo", O_RDONLY);
|
||||
if (fd == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t r = read(fd, buf, size);
|
||||
close(fd);
|
||||
if (r <= 0) {
|
||||
return false;
|
||||
}
|
||||
buf[r < size ? r : size - 1] = 0;
|
||||
|
||||
char* c;
|
||||
do {
|
||||
c = strchr(buf, '\n');
|
||||
} while (c != NULL && *(buf = c + 1) != '\n');
|
||||
|
||||
*buf = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
u64 OS::getProcessCpuTime(u64* utime, u64* stime) {
|
||||
struct tms buf;
|
||||
clock_t real = times(&buf);
|
||||
*utime = buf.tms_utime;
|
||||
*stime = buf.tms_stime;
|
||||
return real;
|
||||
}
|
||||
|
||||
u64 OS::getTotalCpuTime(u64* utime, u64* stime) {
|
||||
int fd = open("/proc/stat", O_RDONLY);
|
||||
if (fd == -1) {
|
||||
return (u64)-1;
|
||||
}
|
||||
|
||||
u64 real = (u64)-1;
|
||||
char buf[512];
|
||||
if (read(fd, buf, sizeof(buf)) >= 12) {
|
||||
u64 user, nice, system, idle;
|
||||
if (sscanf(buf + 4, "%llu %llu %llu %llu", &user, &nice, &system, &idle) == 4) {
|
||||
*utime = user + nice;
|
||||
*stime = system;
|
||||
real = user + nice + system + idle;
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return real;
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
102
src/os_macos.cpp
102
src/os_macos.cpp
@@ -16,11 +16,18 @@
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <libkern/OSByteOrder.h>
|
||||
#include <libproc.h>
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_host.h>
|
||||
#include <mach/mach_time.h>
|
||||
#include <mach/processor_info.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/times.h>
|
||||
#include "os.h"
|
||||
|
||||
|
||||
@@ -54,7 +61,7 @@ class MacThreadList : public ThreadList {
|
||||
for (int i = 0; i < _thread_count; i++) {
|
||||
mach_port_deallocate(_task, _thread_array[i]);
|
||||
}
|
||||
vm_deallocate(_task, (vm_address_t)_thread_array, sizeof(thread_t) * _thread_count);
|
||||
vm_deallocate(_task, (vm_address_t)_thread_array, _thread_count * sizeof(thread_t));
|
||||
_thread_array = NULL;
|
||||
}
|
||||
}
|
||||
@@ -89,6 +96,19 @@ u64 OS::millis() {
|
||||
return (u64)tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
||||
}
|
||||
|
||||
u64 OS::processStartTime() {
|
||||
static u64 start_time = 0;
|
||||
|
||||
if (start_time == 0) {
|
||||
struct proc_bsdinfo info;
|
||||
if (proc_pidinfo(processId(), PROC_PIDTBSDINFO, 0, &info, sizeof(info)) > 0) {
|
||||
start_time = (u64)info.pbi_start_tvsec * 1000 + info.pbi_start_tvusec / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
return start_time;
|
||||
}
|
||||
|
||||
u64 OS::hton64(u64 x) {
|
||||
return OSSwapHostToBigInt64(x);
|
||||
}
|
||||
@@ -101,6 +121,12 @@ int OS::getMaxThreadId() {
|
||||
return 0x7fffffff;
|
||||
}
|
||||
|
||||
int OS::processId() {
|
||||
static const int self_pid = getpid();
|
||||
|
||||
return self_pid;
|
||||
}
|
||||
|
||||
int OS::threadId() {
|
||||
// Used to be pthread_mach_thread_np(pthread_self()),
|
||||
// but pthread_mach_thread_np is not async signal safe
|
||||
@@ -155,4 +181,78 @@ bool OS::sendSignalToThread(int thread_id, int signo) {
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
void* OS::safeAlloc(size_t size) {
|
||||
// mmap() is not guaranteed to be async signal safe, but in practice, it is.
|
||||
// There is no a reasonable alternative anyway.
|
||||
void* result = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (result == MAP_FAILED) {
|
||||
return NULL;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void OS::safeFree(void* addr, size_t size) {
|
||||
munmap(addr, size);
|
||||
}
|
||||
|
||||
Timer* OS::startTimer(u64 interval, TimerCallback callback, void* arg) {
|
||||
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
|
||||
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
|
||||
if (source != NULL) {
|
||||
dispatch_source_set_timer(source, dispatch_time(DISPATCH_TIME_NOW, interval), interval, 0);
|
||||
dispatch_source_set_event_handler_f(source, callback);
|
||||
dispatch_set_context(source, arg);
|
||||
dispatch_resume(source);
|
||||
}
|
||||
return (Timer*)source;
|
||||
}
|
||||
|
||||
void OS::stopTimer(Timer* timer) {
|
||||
dispatch_source_t source = (dispatch_source_t)timer;
|
||||
if (source != NULL) {
|
||||
dispatch_source_cancel(source);
|
||||
dispatch_release(source);
|
||||
}
|
||||
}
|
||||
|
||||
bool OS::getCpuDescription(char* buf, size_t size) {
|
||||
return sysctlbyname("machdep.cpu.brand_string", buf, &size, NULL, 0) == 0;
|
||||
}
|
||||
|
||||
u64 OS::getProcessCpuTime(u64* utime, u64* stime) {
|
||||
struct tms buf;
|
||||
clock_t real = times(&buf);
|
||||
*utime = buf.tms_utime;
|
||||
*stime = buf.tms_stime;
|
||||
return real;
|
||||
}
|
||||
|
||||
u64 OS::getTotalCpuTime(u64* utime, u64* stime) {
|
||||
natural_t cpu_count;
|
||||
processor_info_array_t cpu_info_array;
|
||||
mach_msg_type_number_t cpu_info_count;
|
||||
|
||||
host_name_port_t host = mach_host_self();
|
||||
kern_return_t ret = host_processor_info(host, PROCESSOR_CPU_LOAD_INFO, &cpu_count, &cpu_info_array, &cpu_info_count);
|
||||
mach_port_deallocate(mach_task_self(), host);
|
||||
if (ret != 0) {
|
||||
return (u64)-1;
|
||||
}
|
||||
|
||||
processor_cpu_load_info_data_t* cpu_load = (processor_cpu_load_info_data_t*)cpu_info_array;
|
||||
u64 user = 0;
|
||||
u64 system = 0;
|
||||
u64 idle = 0;
|
||||
for (natural_t i = 0; i < cpu_count; i++) {
|
||||
user += cpu_load[i].cpu_ticks[CPU_STATE_USER] + cpu_load[i].cpu_ticks[CPU_STATE_NICE];
|
||||
system += cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM];
|
||||
idle += cpu_load[i].cpu_ticks[CPU_STATE_IDLE];
|
||||
}
|
||||
vm_deallocate(mach_task_self(), (vm_address_t)cpu_info_array, cpu_info_count * sizeof(int));
|
||||
|
||||
*utime = user;
|
||||
*stime = system;
|
||||
return user + system + idle;
|
||||
}
|
||||
|
||||
#endif // __APPLE__
|
||||
|
||||
@@ -34,8 +34,6 @@ class PerfEvents : public Engine {
|
||||
static CStack _cstack;
|
||||
static bool _print_extended_warning;
|
||||
|
||||
static bool createForThread(int tid);
|
||||
static void destroyForThread(int tid);
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
|
||||
public:
|
||||
@@ -49,18 +47,14 @@ class PerfEvents : public Engine {
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
void onThreadStart(int tid) {
|
||||
createForThread(tid);
|
||||
}
|
||||
|
||||
void onThreadEnd(int tid) {
|
||||
destroyForThread(tid);
|
||||
}
|
||||
|
||||
int getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth);
|
||||
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 bool createForThread(int tid);
|
||||
static void destroyForThread(int tid);
|
||||
};
|
||||
|
||||
#endif // _PERFEVENTS_H
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
#include "spinLock.h"
|
||||
#include "stackFrame.h"
|
||||
#include "symbols.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
// Ancient fcntl.h does not define F_SETOWN_EX constants and structures
|
||||
@@ -113,22 +112,6 @@ struct PerfEventType {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mangle(char* name, char* buf, size_t size) {
|
||||
char* buf_end = buf + size;
|
||||
strcpy(buf, "_ZN");
|
||||
buf += 3;
|
||||
|
||||
for (char* c; (c = strstr(name, "::")) != NULL && buf < buf_end; name = c + 2) {
|
||||
*c = 0;
|
||||
buf += snprintf(buf, buf_end - buf, "%d%s", (int)strlen(name), name);
|
||||
}
|
||||
|
||||
if (buf < buf_end) {
|
||||
snprintf(buf, buf_end - buf, "%d%sE", (int)strlen(name), name);
|
||||
}
|
||||
buf_end[-1] = 0;
|
||||
}
|
||||
|
||||
static PerfEventType* findByType(__u32 type) {
|
||||
for (PerfEventType* event = AVAILABLE_EVENTS; ; event++) {
|
||||
if (event->type == type) {
|
||||
@@ -178,20 +161,10 @@ struct PerfEventType {
|
||||
__u64 addr;
|
||||
if (strncmp(buf, "0x", 2) == 0) {
|
||||
addr = (__u64)strtoll(buf, NULL, 0);
|
||||
} else if (strstr(buf, "::") != NULL) {
|
||||
char mangled_name[256];
|
||||
mangle(buf, mangled_name, sizeof(mangled_name));
|
||||
addr = (__u64)(uintptr_t)Profiler::_instance.findSymbolByPrefix(mangled_name);
|
||||
} else {
|
||||
addr = (__u64)(uintptr_t)dlsym(RTLD_DEFAULT, buf);
|
||||
if (addr == 0) {
|
||||
size_t len = strlen(buf);
|
||||
if (len > 0 && buf[len - 1] == '*') {
|
||||
buf[len - 1] = 0;
|
||||
addr = (__u64)(uintptr_t)Profiler::_instance.findSymbolByPrefix(buf);
|
||||
} else {
|
||||
addr = (__u64)(uintptr_t)Profiler::_instance.findSymbol(buf);
|
||||
}
|
||||
addr = (__u64)(uintptr_t)Profiler::_instance.resolveSymbol(buf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,19 +419,23 @@ void PerfEvents::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
return;
|
||||
}
|
||||
|
||||
u64 counter;
|
||||
switch (_event_type->counter_arg) {
|
||||
case 1: counter = StackFrame(ucontext).arg0(); break;
|
||||
case 2: counter = StackFrame(ucontext).arg1(); break;
|
||||
case 3: counter = StackFrame(ucontext).arg2(); break;
|
||||
case 4: counter = StackFrame(ucontext).arg3(); break;
|
||||
default:
|
||||
if (read(siginfo->si_fd, &counter, sizeof(counter)) != sizeof(counter)) {
|
||||
counter = 1;
|
||||
}
|
||||
if (_enabled) {
|
||||
u64 counter;
|
||||
switch (_event_type->counter_arg) {
|
||||
case 1: counter = StackFrame(ucontext).arg0(); break;
|
||||
case 2: counter = StackFrame(ucontext).arg1(); break;
|
||||
case 3: counter = StackFrame(ucontext).arg2(); break;
|
||||
case 4: counter = StackFrame(ucontext).arg3(); break;
|
||||
default:
|
||||
if (read(siginfo->si_fd, &counter, sizeof(counter)) != sizeof(counter)) {
|
||||
counter = 1;
|
||||
}
|
||||
}
|
||||
|
||||
ExecutionEvent event;
|
||||
Profiler::_instance.recordSample(ucontext, counter, 0, &event);
|
||||
}
|
||||
|
||||
Profiler::_instance.recordSample(ucontext, counter, 0, NULL);
|
||||
ioctl(siginfo->si_fd, PERF_EVENT_IOC_RESET, 0);
|
||||
ioctl(siginfo->si_fd, PERF_EVENT_IOC_REFRESH, 1);
|
||||
}
|
||||
@@ -475,7 +452,7 @@ const char* PerfEvents::units() {
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
@@ -524,7 +501,7 @@ 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");
|
||||
}
|
||||
@@ -536,11 +513,9 @@ Error PerfEvents::start(Arguments& args) {
|
||||
|
||||
_ring = args._ring;
|
||||
if (_ring != RING_USER && !Symbols::haveKernelSymbols()) {
|
||||
if (args._log) {
|
||||
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");
|
||||
}
|
||||
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;
|
||||
@@ -579,7 +554,8 @@ void PerfEvents::stop() {
|
||||
}
|
||||
}
|
||||
|
||||
int PerfEvents::getNativeTrace(void* ucontext, 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
|
||||
@@ -603,7 +579,7 @@ int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain,
|
||||
u64 ip = ring.next();
|
||||
if (ip < PERF_CONTEXT_MAX) {
|
||||
const void* iptr = (const void*)ip;
|
||||
if (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;
|
||||
}
|
||||
@@ -616,7 +592,7 @@ int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain,
|
||||
|
||||
// Last userspace PC is stored right after branch stack
|
||||
const void* pc = (const void*)ring.peek(bnr * 3 + 2);
|
||||
if (CodeHeap::contains(pc) || depth >= max_depth) {
|
||||
if (java_methods->contains(pc) || runtime_stubs->contains(pc) || depth >= max_depth) {
|
||||
goto stack_complete;
|
||||
}
|
||||
callchain[depth++] = pc;
|
||||
@@ -626,12 +602,12 @@ int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain,
|
||||
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;
|
||||
|
||||
@@ -27,9 +27,8 @@ Ring PerfEvents::_ring;
|
||||
bool PerfEvents::_print_extended_warning;
|
||||
|
||||
|
||||
bool PerfEvents::createForThread(int tid) { return false; }
|
||||
void PerfEvents::destroyForThread(int tid) {}
|
||||
void PerfEvents::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {}
|
||||
void PerfEvents::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
}
|
||||
|
||||
const char* PerfEvents::units() {
|
||||
return "ns";
|
||||
@@ -46,7 +45,8 @@ Error PerfEvents::start(Arguments& args) {
|
||||
void PerfEvents::stop() {
|
||||
}
|
||||
|
||||
int PerfEvents::getNativeTrace(void* ucontext, 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;
|
||||
}
|
||||
|
||||
@@ -58,4 +58,11 @@ const char* PerfEvents::getEventName(int event_id) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool PerfEvents::createForThread(int tid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void PerfEvents::destroyForThread(int tid) {
|
||||
}
|
||||
|
||||
#endif // __APPLE__
|
||||
|
||||
718
src/profiler.cpp
718
src/profiler.cpp
File diff suppressed because it is too large
Load Diff
@@ -22,31 +22,29 @@
|
||||
#include <time.h>
|
||||
#include "arch.h"
|
||||
#include "arguments.h"
|
||||
#include "callTraceStorage.h"
|
||||
#include "codeCache.h"
|
||||
#include "dictionary.h"
|
||||
#include "engine.h"
|
||||
#include "event.h"
|
||||
#include "flightRecorder.h"
|
||||
#include "mutex.h"
|
||||
#include "spinLock.h"
|
||||
#include "threadFilter.h"
|
||||
#include "trap.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
|
||||
const char FULL_VERSION_STRING[] =
|
||||
"Async-profiler " PROFILER_VERSION " built on " __DATE__ "\n"
|
||||
"Copyright 2016-2020 Andrei Pangin\n";
|
||||
"Copyright 2016-2021 Andrei Pangin\n";
|
||||
|
||||
const int MAX_CALLTRACES = 65536;
|
||||
const int MAX_NATIVE_FRAMES = 128;
|
||||
const int RESERVED_FRAMES = 4;
|
||||
const int MAX_NATIVE_LIBS = 2048;
|
||||
const int CONCURRENCY_LEVEL = 16;
|
||||
|
||||
|
||||
static inline int cmp64(u64 a, u64 b) {
|
||||
return a > b ? 1 : a == b ? 0 : -1;
|
||||
}
|
||||
|
||||
|
||||
enum AddressType {
|
||||
ADDR_UNKNOWN,
|
||||
ADDR_JIT,
|
||||
@@ -61,37 +59,6 @@ union CallTraceBuffer {
|
||||
};
|
||||
|
||||
|
||||
class CallTraceSample {
|
||||
private:
|
||||
u64 _samples;
|
||||
u64 _counter;
|
||||
int _start_frame; // Offset in frame buffer
|
||||
int _num_frames;
|
||||
|
||||
public:
|
||||
static int comparator(const void* s1, const void* s2) {
|
||||
return cmp64((*(CallTraceSample**)s2)->_counter, (*(CallTraceSample**)s1)->_counter);
|
||||
}
|
||||
|
||||
friend class Profiler;
|
||||
friend class Recording;
|
||||
};
|
||||
|
||||
class MethodSample {
|
||||
private:
|
||||
u64 _samples;
|
||||
u64 _counter;
|
||||
ASGCT_CallFrame _method;
|
||||
|
||||
public:
|
||||
static int comparator(const void* s1, const void* s2) {
|
||||
return cmp64((*(MethodSample**)s2)->_counter, (*(MethodSample**)s1)->_counter);
|
||||
}
|
||||
|
||||
friend class Profiler;
|
||||
};
|
||||
|
||||
|
||||
class FrameName;
|
||||
|
||||
enum State {
|
||||
@@ -104,30 +71,29 @@ class Profiler {
|
||||
private:
|
||||
Mutex _state_lock;
|
||||
State _state;
|
||||
Trap _begin_trap;
|
||||
Trap _end_trap;
|
||||
Mutex _thread_names_lock;
|
||||
// TODO: single map?
|
||||
std::map<int, std::string> _thread_names;
|
||||
std::map<jlong, int> _thread_ids;
|
||||
std::map<int, jlong> _thread_ids;
|
||||
Dictionary _class_map;
|
||||
Dictionary _symbol_map;
|
||||
ThreadFilter _thread_filter;
|
||||
CallTraceStorage _call_trace_storage;
|
||||
FlightRecorder _jfr;
|
||||
Engine* _engine;
|
||||
int _events;
|
||||
time_t _start_time;
|
||||
|
||||
u64 _total_samples;
|
||||
u64 _total_counter;
|
||||
u64 _failures[ASGCT_FAILURE_TYPES];
|
||||
u64 _hashes[MAX_CALLTRACES];
|
||||
CallTraceSample _traces[MAX_CALLTRACES];
|
||||
MethodSample _methods[MAX_CALLTRACES];
|
||||
|
||||
SpinLock _locks[CONCURRENCY_LEVEL];
|
||||
CallTraceBuffer* _calltrace_buffer[CONCURRENCY_LEVEL];
|
||||
ASGCT_CallFrame* _frame_buffer;
|
||||
int _frame_buffer_size;
|
||||
int _max_stack_depth;
|
||||
int _safe_mode;
|
||||
CStack _cstack;
|
||||
volatile int _frame_buffer_index;
|
||||
bool _frame_buffer_overflow;
|
||||
bool _add_thread_frame;
|
||||
bool _update_thread_names;
|
||||
volatile bool _thread_events_state;
|
||||
@@ -143,8 +109,8 @@ class Profiler {
|
||||
JNINativeMethod _load_method;
|
||||
void* _original_NativeLibrary_load;
|
||||
void* _trapped_NativeLibrary_load;
|
||||
static jboolean JNICALL NativeLibraryLoadTrap(JNIEnv* env, jobject self, jstring name, jboolean builtin, jboolean throws);
|
||||
static jboolean JNICALL NativeLibrariesLoadTrap(JNIEnv* env, jobject self, jobject lib, jstring name, jboolean builtin, jboolean jni, jboolean throws);
|
||||
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()
|
||||
@@ -154,6 +120,11 @@ class Profiler {
|
||||
|
||||
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);
|
||||
@@ -162,36 +133,33 @@ class Profiler {
|
||||
void onThreadEnd(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread);
|
||||
|
||||
const char* asgctError(int code);
|
||||
bool inJavaCode(void* ucontext);
|
||||
u32 getLockIndex(int tid);
|
||||
int getNativeTrace(void* ucontext, ASGCT_CallFrame* frames, int tid);
|
||||
int getJavaTraceAsync(void* ucontext, ASGCT_CallFrame* frames, int max_depth);
|
||||
int getJavaTraceJvmti(jvmtiFrameInfo* jvmti_frames, ASGCT_CallFrame* frames, int max_depth);
|
||||
int makeEventFrame(ASGCT_CallFrame* frames, jint event_type, jmethodID event);
|
||||
int makeEventFrame(ASGCT_CallFrame* frames, jint event_type, uintptr_t id);
|
||||
bool fillTopFrame(const void* pc, ASGCT_CallFrame* frame);
|
||||
AddressType getAddressType(instruction_t* pc);
|
||||
u64 hashCallTrace(int num_frames, ASGCT_CallFrame* frames);
|
||||
int storeCallTrace(int num_frames, ASGCT_CallFrame* frames, u64 counter);
|
||||
void copyToFrameBuffer(int num_frames, ASGCT_CallFrame* frames, CallTraceSample* trace);
|
||||
u64 hashMethod(jmethodID method);
|
||||
void storeMethod(jmethodID method, jint bci, u64 counter);
|
||||
void setThreadInfo(int tid, const char* name, jlong java_thread_id);
|
||||
void updateThreadName(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread);
|
||||
void updateJavaThreadNames();
|
||||
void updateNativeThreadNames();
|
||||
bool excludeTrace(FrameName* fn, CallTraceSample* trace);
|
||||
bool excludeTrace(FrameName* fn, CallTrace* trace);
|
||||
void mangle(const char* name, char* buf, size_t size);
|
||||
Engine* selectEngine(const char* event_name);
|
||||
Error checkJvmCapabilities(bool print_warnings);
|
||||
Error checkJvmCapabilities();
|
||||
|
||||
public:
|
||||
static Profiler _instance;
|
||||
|
||||
Profiler() :
|
||||
_state(IDLE),
|
||||
_begin_trap(),
|
||||
_end_trap(),
|
||||
_thread_filter(),
|
||||
_call_trace_storage(),
|
||||
_jfr(),
|
||||
_start_time(0),
|
||||
_frame_buffer(NULL),
|
||||
_frame_buffer_size(0),
|
||||
_max_stack_depth(0),
|
||||
_safe_mode(0),
|
||||
_thread_events_state(JVMTI_DISABLE),
|
||||
@@ -208,9 +176,9 @@ class Profiler {
|
||||
}
|
||||
|
||||
u64 total_samples() { return _total_samples; }
|
||||
u64 total_counter() { return _total_counter; }
|
||||
time_t uptime() { return time(NULL) - _start_time; }
|
||||
|
||||
Dictionary* classMap() { return &_class_map; }
|
||||
ThreadFilter* threadFilter() { return &_thread_filter; }
|
||||
|
||||
void run(Arguments& args);
|
||||
@@ -220,16 +188,15 @@ class Profiler {
|
||||
Error start(Arguments& args, bool reset);
|
||||
Error stop();
|
||||
void switchThreadEvents(jvmtiEventMode mode);
|
||||
void dumpSummary(std::ostream& out);
|
||||
void dump(std::ostream& out, Arguments& args);
|
||||
void dumpCollapsed(std::ostream& out, Arguments& args);
|
||||
void dumpFlameGraph(std::ostream& out, Arguments& args, bool tree);
|
||||
void dumpTraces(std::ostream& out, Arguments& args);
|
||||
void dumpFlat(std::ostream& out, Arguments& args);
|
||||
void recordSample(void* ucontext, u64 counter, jint event_type, jmethodID event, ThreadState thread_state = THREAD_RUNNING);
|
||||
ThreadState getThreadState(void* ucontext);
|
||||
void recordSample(void* ucontext, u64 counter, jint event_type, Event* event);
|
||||
|
||||
void updateSymbols(bool kernel_symbols);
|
||||
const void* findSymbol(const char* name);
|
||||
const void* findSymbolByPrefix(const char* name);
|
||||
const void* resolveSymbol(const char* name);
|
||||
NativeCodeCache* findNativeLibrary(const void* address);
|
||||
const char* findNativeMethod(const void* address);
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class SpinLock {
|
||||
volatile int _lock;
|
||||
|
||||
public:
|
||||
SpinLock() : _lock(0) {
|
||||
SpinLock(int initial_state = 0) : _lock(initial_state) {
|
||||
}
|
||||
|
||||
void reset() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include "threadFilter.h"
|
||||
#include "os.h"
|
||||
|
||||
|
||||
ThreadFilter::ThreadFilter() {
|
||||
memset(_bitmap, 0, sizeof(_bitmap));
|
||||
_bitmap[0] = (u32*)mmap(NULL, BITMAP_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
_bitmap[0] = (u32*)OS::safeAlloc(BITMAP_SIZE);
|
||||
|
||||
_enabled = false;
|
||||
_size = 0;
|
||||
@@ -31,7 +31,7 @@ ThreadFilter::ThreadFilter() {
|
||||
ThreadFilter::~ThreadFilter() {
|
||||
for (int i = 0; i < MAX_BITMAPS; i++) {
|
||||
if (_bitmap[i] != NULL) {
|
||||
munmap(_bitmap[i], BITMAP_SIZE);
|
||||
OS::safeFree(_bitmap[i], BITMAP_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,11 +81,10 @@ bool ThreadFilter::accept(int thread_id) {
|
||||
void ThreadFilter::add(int thread_id) {
|
||||
u32* b = bitmap(thread_id);
|
||||
if (b == NULL) {
|
||||
// Use mmap() rather than malloc() to allow calling from signal handler
|
||||
b = (u32*)mmap(NULL, BITMAP_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
b = (u32*)OS::safeAlloc(BITMAP_SIZE);
|
||||
u32* oldb = __sync_val_compare_and_swap(&_bitmap[(u32)thread_id / BITMAP_CAPACITY], NULL, b);
|
||||
if (oldb != NULL) {
|
||||
munmap(b, BITMAP_SIZE);
|
||||
OS::safeFree(b, BITMAP_SIZE);
|
||||
b = oldb;
|
||||
}
|
||||
}
|
||||
@@ -108,9 +107,7 @@ void ThreadFilter::remove(int thread_id) {
|
||||
}
|
||||
}
|
||||
|
||||
int ThreadFilter::collect(int* array, int max_count) {
|
||||
int count = 0;
|
||||
|
||||
void ThreadFilter::collect(std::vector<int>& v) {
|
||||
for (int i = 0; i < MAX_BITMAPS; i++) {
|
||||
u32* b = _bitmap[i];
|
||||
if (b != NULL) {
|
||||
@@ -120,14 +117,11 @@ int ThreadFilter::collect(int* array, int max_count) {
|
||||
if (word) {
|
||||
for (int bit = 0; bit < 32; bit++) {
|
||||
if (word & (1 << bit)) {
|
||||
if (count >= max_count) return count;
|
||||
array[count++] = start_id + j * 32 + bit;
|
||||
v.push_back(start_id + j * 32 + bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifndef _THREADFILTER_H
|
||||
#define _THREADFILTER_H
|
||||
|
||||
#include <vector>
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
@@ -63,7 +64,7 @@ class ThreadFilter {
|
||||
void add(int thread_id);
|
||||
void remove(int thread_id);
|
||||
|
||||
int collect(int* array, int max_count);
|
||||
void collect(std::vector<int>& v);
|
||||
};
|
||||
|
||||
#endif // _THREADFILTER_H
|
||||
|
||||
63
src/trap.cpp
Normal file
63
src/trap.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include "trap.h"
|
||||
|
||||
|
||||
bool Trap::assign(const void* address) {
|
||||
uintptr_t entry = (uintptr_t)address;
|
||||
if (entry == 0) {
|
||||
_entry = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(__arm__) || defined(__thumb__)
|
||||
if (entry & 1) {
|
||||
entry ^= 1;
|
||||
_breakpoint_insn = BREAKPOINT_THUMB;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (entry != _entry) {
|
||||
// Make the entry point writable, so we can rewrite instructions
|
||||
long page_size = sysconf(_SC_PAGESIZE);
|
||||
if (mprotect((void*)(entry & -page_size), page_size, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) {
|
||||
return false;
|
||||
}
|
||||
_entry = entry;
|
||||
_saved_insn = *(instruction_t*)entry;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Insert breakpoint at the very first instruction
|
||||
void Trap::install() {
|
||||
if (_entry) {
|
||||
*(instruction_t*)_entry = _breakpoint_insn;
|
||||
flushCache(_entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear breakpoint - restore the original instruction
|
||||
void Trap::uninstall() {
|
||||
if (_entry) {
|
||||
*(instruction_t*)_entry = _saved_insn;
|
||||
flushCache(_entry);
|
||||
}
|
||||
}
|
||||
48
src/trap.h
Normal file
48
src/trap.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _TRAP_H
|
||||
#define _TRAP_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
class Trap {
|
||||
private:
|
||||
uintptr_t _entry;
|
||||
instruction_t _breakpoint_insn;
|
||||
instruction_t _saved_insn;
|
||||
|
||||
public:
|
||||
Trap() : _entry(0), _breakpoint_insn(BREAKPOINT) {
|
||||
}
|
||||
|
||||
uintptr_t entry() {
|
||||
return _entry;
|
||||
}
|
||||
|
||||
bool covers(uintptr_t pc) {
|
||||
// PC points either to BREAKPOINT instruction or to the next one
|
||||
return pc - _entry <= sizeof(instruction_t);
|
||||
}
|
||||
|
||||
bool assign(const void* address);
|
||||
void install();
|
||||
void uninstall();
|
||||
};
|
||||
|
||||
#endif // _TRAP_H
|
||||
@@ -33,8 +33,6 @@ static Arguments _agent_args;
|
||||
JavaVM* VM::_vm;
|
||||
jvmtiEnv* VM::_jvmti = NULL;
|
||||
int VM::_hotspot_version = 0;
|
||||
int VM::_hotspot_minor = 0;
|
||||
int VM::_safe_mode = 0;
|
||||
void* VM::_libjvm;
|
||||
void* VM::_libjava;
|
||||
AsyncGetCallTrace VM::_asyncGetCallTrace;
|
||||
@@ -66,14 +64,17 @@ void VM::init(JavaVM* vm, bool attach) {
|
||||
_hotspot_version = 6;
|
||||
} else if ((_hotspot_version = atoi(prop)) < 9) {
|
||||
_hotspot_version = 9;
|
||||
} else {
|
||||
const char* p = strchr(prop, '.');
|
||||
if (p != NULL && (p = strchr(p + 1, '.')) != NULL) {
|
||||
_hotspot_minor = atoi(p + 1);
|
||||
}
|
||||
}
|
||||
_jvmti->Deallocate((unsigned char*)prop);
|
||||
}
|
||||
|
||||
if (is_hotspot) {
|
||||
JVMTIFunctions* functions = *(JVMTIFunctions**)_jvmti;
|
||||
_orig_RedefineClasses = functions->RedefineClasses;
|
||||
_orig_RetransformClasses = functions->RetransformClasses;
|
||||
functions->RedefineClasses = RedefineClassesHook;
|
||||
functions->RetransformClasses = RetransformClassesHook;
|
||||
}
|
||||
}
|
||||
|
||||
_libjvm = getLibraryHandle("libjvm.so");
|
||||
@@ -116,16 +117,12 @@ void VM::init(JavaVM* vm, bool attach) {
|
||||
_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);
|
||||
if ((_safe_mode & 14) != 14) { // POP_FRAME + SCAN_STACK + LAST_JAVA_PC
|
||||
// Workaround for JDK-8173361: avoid CompiledMethodLoad events when they are not needed
|
||||
_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_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 (attach) {
|
||||
loadAllMethodIDs(jvmti(), jni());
|
||||
DisableSweeper ds;
|
||||
_jvmti->GenerateEvents(JVMTI_EVENT_DYNAMIC_CODE_GENERATED);
|
||||
_jvmti->GenerateEvents(JVMTI_EVENT_COMPILED_METHOD_LOAD);
|
||||
}
|
||||
@@ -140,13 +137,6 @@ void VM::ready() {
|
||||
}
|
||||
|
||||
_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) {
|
||||
@@ -274,24 +264,7 @@ Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||
JNIEnv* jni;
|
||||
if (vm->GetEnv((void**)&jni, JNI_VERSION_1_6) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
jclass cls = jni->FindClass("java/lang/System");
|
||||
jmethodID getProperty = jni->GetStaticMethodID(cls, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
|
||||
jstring prop = (jstring)jni->CallStaticObjectMethod(cls, getProperty, jni->NewStringUTF("AsyncProfiler.safemode"));
|
||||
|
||||
const char* chars;
|
||||
if (prop != NULL && (chars = jni->GetStringUTFChars(prop, NULL)) != NULL) {
|
||||
VM::_safe_mode = strtol(chars, NULL, 0);
|
||||
jni->ReleaseStringUTFChars(prop, chars);
|
||||
} else {
|
||||
jni->ExceptionClear();
|
||||
}
|
||||
|
||||
VM::init(vm, true);
|
||||
JavaAPI::registerNatives(VM::jvmti(), jni);
|
||||
JavaAPI::registerNatives(VM::jvmti(), VM::jni());
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
@@ -28,12 +28,14 @@
|
||||
|
||||
// Denotes ASGCT_CallFrame where method_id has special meaning (not jmethodID)
|
||||
enum ASGCT_CallFrameType {
|
||||
BCI_NATIVE_FRAME = -10, // method_id is native function name (char*)
|
||||
BCI_SYMBOL = -11, // method_id is VMSymbol*
|
||||
BCI_SYMBOL_OUTSIDE_TLAB = -12, // VMSymbol* specifically for allocations outside TLAB
|
||||
BCI_THREAD_ID = -13, // method_id designates a thread
|
||||
BCI_ERROR = -14, // method_id is error string
|
||||
BCI_INSTRUMENT = -15, // synthetic method_id that should not appear in the call stack
|
||||
BCI_NATIVE_FRAME = -10, // native function name (char*)
|
||||
BCI_ALLOC = -11, // name of the allocated class
|
||||
BCI_ALLOC_OUTSIDE_TLAB = -12, // name of the class allocated outside TLAB
|
||||
BCI_LOCK = -13, // class name of the locked object
|
||||
BCI_PARK = -14, // class name of the park() blocker
|
||||
BCI_THREAD_ID = -15, // method_id designates a thread
|
||||
BCI_ERROR = -16, // method_id is an error string
|
||||
BCI_INSTRUMENT = -17, // synthetic method_id that should not appear in the call stack
|
||||
};
|
||||
|
||||
// See hotspot/src/share/vm/prims/forte.cpp
|
||||
@@ -90,7 +92,6 @@ class VM {
|
||||
static jvmtiError (JNICALL *_orig_RetransformClasses)(jvmtiEnv*, jint, const jclass* classes);
|
||||
static volatile int _in_redefine_classes;
|
||||
static int _hotspot_version;
|
||||
static int _hotspot_minor;
|
||||
|
||||
static void ready();
|
||||
static void* getLibraryHandle(const char* name);
|
||||
@@ -98,7 +99,6 @@ class VM {
|
||||
static void loadAllMethodIDs(jvmtiEnv* jvmti, JNIEnv* jni);
|
||||
|
||||
public:
|
||||
static int _safe_mode;
|
||||
static void* _libjvm;
|
||||
static void* _libjava;
|
||||
static AsyncGetCallTrace _asyncGetCallTrace;
|
||||
@@ -122,10 +122,6 @@ class VM {
|
||||
return _hotspot_version;
|
||||
}
|
||||
|
||||
static int hotspot_minor() {
|
||||
return _hotspot_minor;
|
||||
}
|
||||
|
||||
static bool inRedefineClasses() {
|
||||
return _in_redefine_classes > 0;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "vmStructs.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
@@ -44,8 +43,6 @@ int VMStructs::_anchor_pc_offset = -1;
|
||||
int VMStructs::_frame_size_offset = -1;
|
||||
int VMStructs::_is_gc_active_offset = -1;
|
||||
char* VMStructs::_collected_heap_addr = NULL;
|
||||
const void* VMStructs::_code_heap_low = NO_MIN_ADDRESS;
|
||||
const void* VMStructs::_code_heap_high = NO_MAX_ADDRESS;
|
||||
|
||||
jfieldID VMStructs::_eetop;
|
||||
jfieldID VMStructs::_tid;
|
||||
@@ -58,8 +55,6 @@ VMStructs::UnsafeParkFunc VMStructs::_unsafe_park = NULL;
|
||||
VMStructs::FindBlobFunc VMStructs::_find_blob = NULL;
|
||||
VMStructs::LockFunc VMStructs::_lock_func;
|
||||
VMStructs::LockFunc VMStructs::_unlock_func;
|
||||
char* VMStructs::_method_flushing = NULL;
|
||||
int* VMStructs::_sweep_started = NULL;
|
||||
|
||||
|
||||
uintptr_t VMStructs::readSymbol(const char* symbol_name) {
|
||||
@@ -95,11 +90,6 @@ void VMStructs::initOffsets() {
|
||||
return;
|
||||
}
|
||||
|
||||
char* code_heap_addr = NULL;
|
||||
int code_heap_memory_offset = -1;
|
||||
int vs_low_offset = -1;
|
||||
int vs_high_offset = -1;
|
||||
|
||||
while (true) {
|
||||
const char* type = *(const char**)(entry + type_offset);
|
||||
const char* field = *(const char**)(entry + field_offset);
|
||||
@@ -152,24 +142,6 @@ void VMStructs::initOffsets() {
|
||||
if (strcmp(field, "_frame_size") == 0) {
|
||||
_frame_size_offset = *(int*)(entry + offset_offset);
|
||||
}
|
||||
} else if (strcmp(type, "CodeCache") == 0) {
|
||||
if (strcmp(field, "_heap") == 0) {
|
||||
code_heap_addr = **(char***)(entry + address_offset);
|
||||
} else if (strcmp(field, "_high_bound") == 0) {
|
||||
_code_heap_high = **(const void***)(entry + address_offset);
|
||||
} else if (strcmp(field, "_low_bound") == 0) {
|
||||
_code_heap_low = **(const void***)(entry + address_offset);
|
||||
}
|
||||
} else if (strcmp(type, "CodeHeap") == 0) {
|
||||
if (strcmp(field, "_memory") == 0) {
|
||||
code_heap_memory_offset = *(int*)(entry + offset_offset);
|
||||
}
|
||||
} else if (strcmp(type, "VirtualSpace") == 0) {
|
||||
if (strcmp(field, "_low_boundary") == 0) {
|
||||
vs_low_offset = *(int*)(entry + offset_offset);
|
||||
} else if (strcmp(field, "_high_boundary") == 0) {
|
||||
vs_high_offset = *(int*)(entry + offset_offset);
|
||||
}
|
||||
} else if (strcmp(type, "Universe") == 0) {
|
||||
if (strcmp(field, "_collectedHeap") == 0) {
|
||||
_collected_heap_addr = **(char***)(entry + address_offset);
|
||||
@@ -189,11 +161,6 @@ void VMStructs::initOffsets() {
|
||||
&& (_symbol_length_offset >= 0 || _symbol_length_and_refcount_offset >= 0)
|
||||
&& _symbol_body_offset >= 0
|
||||
&& _klass != NULL;
|
||||
|
||||
if (code_heap_addr != NULL && code_heap_memory_offset >= 0 && vs_low_offset >= 0 && vs_high_offset >= 0) {
|
||||
_code_heap_low = *(const void**)(code_heap_addr + code_heap_memory_offset + vs_low_offset);
|
||||
_code_heap_high = *(const void**)(code_heap_addr + code_heap_memory_offset + vs_high_offset);
|
||||
}
|
||||
}
|
||||
|
||||
void VMStructs::initJvmFunctions() {
|
||||
@@ -220,12 +187,6 @@ void VMStructs::initJvmFunctions() {
|
||||
_unlock_func = (LockFunc)_libjvm->findSymbol("_ZN7Monitor6unlockEv");
|
||||
_has_class_loader_data = _lock_func != NULL && _unlock_func != NULL;
|
||||
}
|
||||
|
||||
if ((VM::hotspot_version() > 0 && VM::hotspot_version() < 11) ||
|
||||
(VM::hotspot_version() == 11 && VM::hotspot_minor() < 10)) {
|
||||
_method_flushing = (char*)_libjvm->findSymbol("MethodFlushing");
|
||||
_sweep_started = (int*)_libjvm->findSymbol("_ZN14NMethodSweeper14_sweep_startedE");
|
||||
}
|
||||
}
|
||||
|
||||
void VMStructs::initThreadBridge(JNIEnv* env) {
|
||||
@@ -277,27 +238,3 @@ void VMStructs::initLogging(JNIEnv* env) {
|
||||
VMThread* VMThread::current() {
|
||||
return (VMThread*)pthread_getspecific((pthread_key_t)_tls_index);
|
||||
}
|
||||
|
||||
DisableSweeper::DisableSweeper() {
|
||||
// Workaround for JDK-8212160: Temporarily disable MethodFlushing
|
||||
// while generating initial set of CompiledMethodLoad events
|
||||
_enabled = _method_flushing != NULL && *_method_flushing;
|
||||
if (!_enabled) return;
|
||||
|
||||
*_method_flushing = 0;
|
||||
__sync_synchronize();
|
||||
|
||||
// Wait a bit in case sweeping has already started
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (_sweep_started == NULL || *_sweep_started) {
|
||||
usleep(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DisableSweeper::~DisableSweeper() {
|
||||
if (!_enabled) return;
|
||||
|
||||
*_method_flushing = 1;
|
||||
__sync_synchronize();
|
||||
}
|
||||
|
||||
@@ -46,8 +46,6 @@ class VMStructs {
|
||||
static int _frame_size_offset;
|
||||
static int _is_gc_active_offset;
|
||||
static char* _collected_heap_addr;
|
||||
static const void* _code_heap_low;
|
||||
static const void* _code_heap_high;
|
||||
|
||||
static jfieldID _eetop;
|
||||
static jfieldID _tid;
|
||||
@@ -62,9 +60,6 @@ class VMStructs {
|
||||
static LockFunc _lock_func;
|
||||
static LockFunc _unlock_func;
|
||||
|
||||
static char* _method_flushing;
|
||||
static int* _sweep_started;
|
||||
|
||||
static uintptr_t readSymbol(const char* symbol_name);
|
||||
static void initOffsets();
|
||||
static void initJvmFunctions();
|
||||
@@ -243,18 +238,6 @@ class RuntimeStub : VMStructs {
|
||||
}
|
||||
};
|
||||
|
||||
class CodeHeap : VMStructs {
|
||||
public:
|
||||
static bool contains(const void* pc) {
|
||||
return _code_heap_low <= pc && pc < _code_heap_high;
|
||||
}
|
||||
|
||||
static void updateBounds(const void* start, const void* end) {
|
||||
if (start < _code_heap_low) _code_heap_low = start;
|
||||
if (end > _code_heap_high) _code_heap_high = end;
|
||||
}
|
||||
};
|
||||
|
||||
class CollectedHeap : VMStructs {
|
||||
public:
|
||||
static bool isGCActive() {
|
||||
@@ -263,13 +246,4 @@ class CollectedHeap : VMStructs {
|
||||
}
|
||||
};
|
||||
|
||||
class DisableSweeper : VMStructs {
|
||||
private:
|
||||
bool _enabled;
|
||||
|
||||
public:
|
||||
DisableSweeper();
|
||||
~DisableSweeper();
|
||||
};
|
||||
|
||||
#endif // _VMSTRUCTS_H
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include <sys/types.h>
|
||||
#include "wallClock.h"
|
||||
#include "profiler.h"
|
||||
#include "stackFrame.h"
|
||||
|
||||
|
||||
// Maximum number of threads sampled in one iteration. This limit serves as a throttle
|
||||
@@ -41,30 +40,10 @@ const int WAKEUP_SIGNAL = SIGIO;
|
||||
long WallClock::_interval;
|
||||
bool WallClock::_sample_idle_threads;
|
||||
|
||||
ThreadState WallClock::getThreadState(void* ucontext) {
|
||||
StackFrame frame(ucontext);
|
||||
uintptr_t pc = frame.pc();
|
||||
|
||||
// Consider a thread sleeping, if it has been interrupted in the middle of syscall execution,
|
||||
// either when PC points to the syscall instruction, or if syscall has just returned with EINTR
|
||||
if (StackFrame::isSyscall((instruction_t*)pc)) {
|
||||
return THREAD_SLEEPING;
|
||||
}
|
||||
|
||||
// Make sure the previous instruction address is readable
|
||||
uintptr_t prev_pc = pc - SYSCALL_SIZE;
|
||||
if ((pc & 0xfff) >= SYSCALL_SIZE || Profiler::_instance.findNativeLibrary((instruction_t*)prev_pc) != NULL) {
|
||||
if (StackFrame::isSyscall((instruction_t*)prev_pc) && frame.checkInterruptedSyscall()) {
|
||||
return THREAD_SLEEPING;
|
||||
}
|
||||
}
|
||||
|
||||
return THREAD_RUNNING;
|
||||
}
|
||||
|
||||
void WallClock::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
ThreadState thread_state = _sample_idle_threads ? getThreadState(ucontext) : THREAD_RUNNING;
|
||||
Profiler::_instance.recordSample(ucontext, _interval, 0, NULL, thread_state);
|
||||
ExecutionEvent event;
|
||||
event._thread_state = _sample_idle_threads ? Profiler::_instance.getThreadState(ucontext) : THREAD_RUNNING;
|
||||
Profiler::_instance.recordSample(ucontext, _interval, 0, &event);
|
||||
}
|
||||
|
||||
void WallClock::wakeupHandler(int signo) {
|
||||
@@ -91,7 +70,7 @@ Error WallClock::start(Arguments& args) {
|
||||
return Error("interval must be positive");
|
||||
}
|
||||
|
||||
_sample_idle_threads = strcmp(args._event, EVENT_WALL) == 0;
|
||||
_sample_idle_threads = strcmp(args._event_desc, EVENT_WALL) == 0;
|
||||
|
||||
// Increase default interval for wall clock mode due to larger number of sampled threads
|
||||
_interval = args._interval ? args._interval : (_sample_idle_threads ? DEFAULT_INTERVAL * 5 : DEFAULT_INTERVAL);
|
||||
@@ -124,6 +103,11 @@ void WallClock::timerLoop() {
|
||||
long long next_cycle_time = OS::nanotime();
|
||||
|
||||
while (_running) {
|
||||
if (!_enabled) {
|
||||
sleep(_interval);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sample_idle_threads) {
|
||||
// Try to keep the wall clock interval stable, regardless of the number of profiled threads
|
||||
int estimated_thread_count = thread_filter_enabled ? thread_filter->size() : thread_list->size();
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
#include "engine.h"
|
||||
#include "os.h"
|
||||
|
||||
|
||||
class WallClock : public Engine {
|
||||
@@ -39,7 +38,6 @@ class WallClock : public Engine {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ThreadState getThreadState(void* ucontext);
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
static void wakeupHandler(int signo);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ fi
|
||||
|
||||
FILENAME=/tmp/java.trace
|
||||
|
||||
${JAVA_HOME}/bin/java -agentpath:../build/libasyncProfiler.so=start,collapsed,threads,file=$FILENAME ThreadsTarget
|
||||
${JAVA_HOME}/bin/java -agentpath:../build/libasyncProfiler.so=start,event=cpu,collapsed,threads,file=$FILENAME ThreadsTarget
|
||||
|
||||
# wait for normal termination
|
||||
|
||||
|
||||
Reference in New Issue
Block a user