Compare commits

...

16 Commits
jstack ... v1.x

Author SHA1 Message Date
Andrei Pangin
50fccae0fc Release 1.8.8 2022-09-10 02:25:05 +03:00
Andrei Pangin
f9afcaf5e7 Workaround for JDK-8288547 2022-09-10 02:24:22 +03:00
Andrey Pangin
a0c301859f Release 1.8.7 2021-10-01 01:20:32 +03:00
Andrey Pangin
56d736b65c The workaround for #295 didn't work sometimes
(cherry picked from commit 3256d824de)
2021-09-29 21:58:02 +03:00
Andrei Pangin
e76219683d Introduced system property to workaround JDK-8173361 (#468) 2021-09-29 21:53:38 +03:00
Andrey Pangin
9341b201b8 Release 1.8.6 2021-07-08 22:53:04 +03:00
Andrey Pangin
c43912b903 Sign macOS binaries 2021-07-08 21:45:57 +03:00
Andrey Pangin
7ed21187e9 Extend CompiledMethodLoad bug workaround to JDK < 11.0.10 2021-07-07 02:10:29 +03:00
Andrey Pangin
09b5ec0b63 'log=none' option to suppress warnings in logs 2021-07-05 03:42:30 +03:00
Andrey Pangin
a12c291423 Workaround JDK bug related to posting CompiledMethodLoad event 2021-07-05 00:57:47 +03:00
Andrey Pangin
32997bb645 Release 1.8.5 2021-03-22 02:31:30 +03:00
Andrey Pangin
9b11014b25 Simplified safe mode check 2021-03-13 17:03:03 +03:00
Andrey Pangin
516d717d49 Backported JFR-FlameGraph converter 2021-03-04 20:09:56 +03:00
Andrey Pangin
5dd9e86a1d Release 1.8.4 2021-02-24 02:50:58 +03:00
Andrey Pangin
cb0f1eb72d Fixed JDK 7 crash during wall-clock profiling 2021-02-20 03:41:37 +03:00
Andrey Pangin
34daf4f540 #386: Added a note about IntelliJ IDEA 2021-02-01 19:11:59 +03:00
24 changed files with 424 additions and 138 deletions

View File

@@ -1,5 +1,41 @@
# Changelog
## [1.8.8] - 2022-09-10
### 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
### Improvements
- `log=none` option to suppress warnings about missing JVM symbols
- Sign macOS binaries
### 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
## [1.8.3] - 2021-01-06
### Improvements

View File

@@ -1,4 +1,4 @@
PROFILER_VERSION=1.8.3
PROFILER_VERSION=1.8.8
JATTACH_VERSION=1.5
JAVAC_RELEASE_VERSION=6
@@ -33,11 +33,13 @@ 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
@@ -70,6 +72,8 @@ 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))
mkdir -p $(PACKAGE_DIR)
cp -RP build profiler.sh LICENSE NOTICE *.md $(PACKAGE_DIR)
chmod -R 755 $(PACKAGE_DIR)

View File

@@ -14,21 +14,19 @@ async-profiler can trace the following kinds of events:
## Download
Latest release (1.8.3):
Latest v1 release (1.8.8):
- 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)
- 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)
[Early access](https://github.com/jvm-profiling-tools/async-profiler/releases/tag/v2.0-b1) (2.0-b1):
[Other releases](https://github.com/jvm-profiling-tools/async-profiler/releases)
- Linux x64 (glibc): [async-profiler-2.0-b1-linux-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.0-b1/async-profiler-2.0-b1-linux-x64.tar.gz)
- macOS x64: [async-profiler-2.0-b1-macos-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.0-b1/async-profiler-2.0-b1-macos-x64.tar.gz)
[Previous releases](https://github.com/jvm-profiling-tools/async-profiler/releases)
Note: async-profiler also comes bundled with IntelliJ IDEA Ultimate 2018.3 and later.
For more information refer to [IntelliJ IDEA documentation](https://www.jetbrains.com/help/idea/cpu-profiler.html).
## Supported platforms
@@ -238,7 +236,7 @@ $ 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.3/src/arguments.cpp#L49).
[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`

View File

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

View File

@@ -201,6 +201,9 @@ 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;

View File

@@ -123,6 +123,7 @@ class Arguments {
int _include;
int _exclude;
bool _threads;
bool _log;
int _style;
CStack _cstack;
Output _output;
@@ -150,6 +151,7 @@ class Arguments {
_include(0),
_exclude(0),
_threads(false),
_log(true),
_style(0),
_cstack(CSTACK_DEFAULT),
_output(OUTPUT_NONE),

View File

@@ -14,8 +14,10 @@
* limitations under the License.
*/
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
@@ -69,32 +71,36 @@ public class FlameGraph {
String[] trace = line.substring(0, space).split(";");
long ticks = Long.parseLong(line.substring(space + 1));
depth = Math.max(depth, trace.length);
Frame frame = root;
if (reverse) {
for (int i = trace.length; --i >= skip; ) {
frame.total += ticks;
frame = frame.child(trace[i]);
}
} else {
for (int i = skip; i < trace.length; i++) {
frame.total += ticks;
frame = frame.child(trace[i]);
}
}
frame.total += ticks;
frame.self += ticks;
addSample(trace, ticks);
}
}
}
public void addSample(String[] trace, long ticks) {
Frame frame = root;
if (reverse) {
for (int i = trace.length; --i >= skip; ) {
frame.total += ticks;
frame = frame.child(trace[i]);
}
} else {
for (int i = skip; i < trace.length; i++) {
frame.total += ticks;
frame = frame.child(trace[i]);
}
}
frame.total += ticks;
frame.self += ticks;
depth = Math.max(depth, trace.length);
}
public void dump() throws IOException {
if (output == null) {
dump(System.out);
} else {
try (PrintStream out = new PrintStream(output, "UTF-8")) {
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(output), 32768);
PrintStream out = new PrintStream(bos, false, "UTF-8")) {
dump(out);
}
}
@@ -137,7 +143,7 @@ public class FlameGraph {
private void printFrame(PrintStream out, String title, Frame frame, int level, long x) {
int type = frameType(title);
title = stripSuffix(title);
if (title.indexOf('"') >= 0) {
if (title.indexOf('\'') >= 0) {
title = title.replace("'", "\\'");
}

View File

@@ -25,6 +25,7 @@ public class Main {
System.out.println();
System.out.println("Available converters:");
System.out.println(" FlameGraph input.collapsed output.html");
System.out.println(" jfr2flame input.jfr output.html");
System.out.println(" jfr2nflx input.jfr output.nflx");
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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.
*/
import one.jfr.ClassRef;
import one.jfr.Frame;
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 final JfrReader jfr;
private final Map<Long, String> methodNames = new HashMap<>();
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);
}
fg.addSample(trace, stackTrace.samples);
}
}
private String getMethodName(long methodId, int type) {
String result = methodNames.get(methodId);
if (result != null) {
return result;
}
MethodRef method = jfr.methods.get(methodId);
ClassRef cls = jfr.classes.get(method.cls);
byte[] className = jfr.symbols.get(cls.name);
byte[] methodName = jfr.symbols.get(method.name);
if (className == null || className.length == 0) {
String methodStr = new String(methodName, StandardCharsets.UTF_8);
result = type == FRAME_KERNEL ? methodStr + "_[k]" : methodStr;
} else {
String classStr = new String(className, StandardCharsets.UTF_8);
String methodStr = new String(methodName, StandardCharsets.UTF_8);
result = classStr + '.' + methodStr + "_[j]";
}
methodNames.put(methodId, result);
return result;
}
public static void main(String[] args) throws Exception {
FlameGraph fg = new FlameGraph(args);
if (fg.input == null) {
System.out.println("Usage: java " + jfr2flame.class.getName() + " [options] input.jfr [output.html]");
System.exit(1);
}
try (JfrReader jfr = new JfrReader(fg.input)) {
new jfr2flame(jfr).convert(fg);
}
fg.dump();
}
}

View File

@@ -19,6 +19,7 @@ import one.jfr.Frame;
import one.jfr.JfrReader;
import one.jfr.MethodRef;
import one.jfr.Sample;
import one.jfr.StackTrace;
import one.proto.Proto;
import java.io.File;
@@ -60,10 +61,10 @@ public class jfr2nflx {
Proto nodes = new Proto(10000);
Proto node = new Proto(10000);
for (Map.Entry<Integer, Frame[]> entry : jfr.stackTraces.entrySet()) {
for (Map.Entry<Integer, StackTrace> entry : jfr.stackTraces.entrySet()) {
profile.field(5, nodes
.field(1, entry.getKey())
.field(2, packNode(node, entry.getValue())));
.field(2, packNode(node, entry.getValue().frames)));
nodes.reset();
node.reset();
}

View File

@@ -51,7 +51,7 @@ public class JfrReader implements Closeable {
public final long startNanos;
public final long stopNanos;
public final Map<Integer, Frame[]> stackTraces = new HashMap<>();
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<>();
@@ -82,11 +82,7 @@ public class JfrReader implements Closeable {
int size = buf.getInt();
int type = buf.getInt();
if (type == EVENT_EXECUTION_SAMPLE) {
long time = buf.getLong();
int tid = buf.getInt();
int stackTraceId = (int) buf.getLong();
short threadState = buf.getShort();
samples.add(new Sample(time, tid, stackTraceId, threadState));
readExecutionSample();
} else {
buf.position(buf.position() + size - 8);
}
@@ -95,6 +91,19 @@ public class JfrReader implements Closeable {
Collections.sort(samples);
}
private void readExecutionSample() {
long time = buf.getLong();
int tid = buf.getInt();
int stackTraceId = (int) buf.getLong();
short threadState = buf.getShort();
samples.add(new Sample(time, tid, stackTraceId, threadState));
StackTrace stackTrace = stackTraces.get(stackTraceId);
if (stackTrace != null) {
stackTrace.samples++;
}
}
private void readCheckpoint(int checkpointOffset) {
buf.position(checkpointOffset + 24);
@@ -135,7 +144,7 @@ public class JfrReader implements Closeable {
byte type = buf.get();
frames[j] = new Frame(method, type);
}
stackTraces.put(id, frames);
stackTraces.put(id, new StackTrace(frames));
}
}

View File

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

View File

@@ -16,6 +16,7 @@
#include "engine.h"
#include "stackFrame.h"
#include "vmStructs.h"
Error Engine::check(Arguments& args) {
@@ -26,8 +27,7 @@ CStack Engine::cstack() {
return CSTACK_FP;
}
int Engine::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
CodeCache* java_methods, CodeCache* runtime_stubs) {
int Engine::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth) {
const void* pc;
uintptr_t fp;
uintptr_t prev_fp = (uintptr_t)&fp;
@@ -47,7 +47,7 @@ int Engine::getNativeTrace(void* ucontext, int tid, const void** callchain, int
// Walk until the bottom of the stack or until the first Java frame
while (depth < max_depth && pc >= valid_pc) {
if (java_methods->contains(pc) || runtime_stubs->contains(pc)) {
if (CodeHeap::contains(pc)) {
break;
}

View File

@@ -18,7 +18,6 @@
#define _ENGINE_H
#include "arguments.h"
#include "codeCache.h"
class Engine {
@@ -34,8 +33,7 @@ class Engine {
virtual void onThreadEnd(int tid) {}
virtual CStack cstack();
virtual int getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
CodeCache* java_methods, CodeCache* runtime_stubs);
virtual int getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth);
};
#endif // _ENGINE_H

View File

@@ -57,8 +57,7 @@ class PerfEvents : public Engine {
destroyForThread(tid);
}
int getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
CodeCache* java_methods, CodeCache* runtime_stubs);
int getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth);
static bool supported();
static const char* getEventName(int event_id);

View File

@@ -37,6 +37,7 @@
#include "spinLock.h"
#include "stackFrame.h"
#include "symbols.h"
#include "vmStructs.h"
// Ancient fcntl.h does not define F_SETOWN_EX constants and structures
@@ -535,9 +536,11 @@ Error PerfEvents::start(Arguments& args) {
_ring = args._ring;
if (_ring != RING_USER && !Symbols::haveKernelSymbols()) {
fprintf(stderr, "WARNING: Kernel symbols are unavailable due to restrictions. Try\n"
" echo 0 > /proc/sys/kernel/kptr_restrict\n"
" echo 1 > /proc/sys/kernel/perf_event_paranoid\n");
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");
}
_ring = RING_USER;
}
_cstack = args._cstack;
@@ -576,8 +579,7 @@ void PerfEvents::stop() {
}
}
int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
CodeCache* java_methods, CodeCache* runtime_stubs) {
int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth) {
PerfEvent* event = &_events[tid];
if (!event->tryLock()) {
return 0; // the event is being destroyed
@@ -601,7 +603,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 (java_methods->contains(iptr) || runtime_stubs->contains(iptr) || depth >= max_depth) {
if (CodeHeap::contains(iptr) || depth >= max_depth) {
// Stop at the first Java frame
goto stack_complete;
}
@@ -614,7 +616,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 (java_methods->contains(pc) || runtime_stubs->contains(pc) || depth >= max_depth) {
if (CodeHeap::contains(pc) || depth >= max_depth) {
goto stack_complete;
}
callchain[depth++] = pc;
@@ -624,12 +626,12 @@ int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain,
const void* to = (const void*)ring.next();
ring.next();
if (java_methods->contains(to) || runtime_stubs->contains(to) || depth >= max_depth) {
if (CodeHeap::contains(to) || depth >= max_depth) {
goto stack_complete;
}
callchain[depth++] = to;
if (java_methods->contains(from) || runtime_stubs->contains(from) || depth >= max_depth) {
if (CodeHeap::contains(from) || depth >= max_depth) {
goto stack_complete;
}
callchain[depth++] = from;

View File

@@ -46,8 +46,7 @@ Error PerfEvents::start(Arguments& args) {
void PerfEvents::stop() {
}
int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
CodeCache* java_methods, CodeCache* runtime_stubs) {
int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth) {
return 0;
}

View File

@@ -57,8 +57,6 @@ enum StackRecovery {
LAST_JAVA_PC = 8,
GC_TRACES = 16,
CHECK_STATE = 32,
HOTSPOT_ONLY = LAST_JAVA_PC | GC_TRACES,
MAX_RECOVERY = 63
};
@@ -168,6 +166,8 @@ void Profiler::addJavaMethod(const void* address, int length, jmethodID method)
_jit_lock.lock();
_java_methods.add(address, length, method, true);
_jit_lock.unlock();
CodeHeap::updateBounds(address, (const char*)address + length);
}
void Profiler::removeJavaMethod(const void* address, jmethodID method) {
@@ -180,6 +180,8 @@ void Profiler::addRuntimeStub(const void* address, int length, const char* name)
_stubs_lock.lock();
_runtime_stubs.add(address, length, name, true);
_stubs_lock.unlock();
CodeHeap::updateBounds(address, (const char*)address + length);
}
void Profiler::onThreadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
@@ -264,53 +266,26 @@ const char* Profiler::findNativeMethod(const void* address) {
return lib == NULL ? NULL : lib->binarySearch(address);
}
// When thread is in Java state, it should have Java frame somewhere on the top of the stack
bool Profiler::checkWalkable(void* ucontext) {
// When a thread in Java state has a Java frame on the top of the stack,
// it is known to be safe for stack walking
bool Profiler::inJavaCode(void* ucontext) {
if (ucontext == NULL) {
return false;
}
StackFrame frame(ucontext);
const void* pc = (const void*)frame.pc();
uintptr_t fp = frame.fp();
uintptr_t prev_fp = (uintptr_t)&fp;
uintptr_t bottom = prev_fp + 0x40000;
const void* const valid_pc = (const void*)0x1000;
// Walk until the bottom of the stack or until the first Java frame
for (int depth = 0; depth < 5 && pc >= valid_pc; depth++) {
if (_runtime_stubs.contains(pc)) {
_stubs_lock.lockShared();
jmethodID method = _runtime_stubs.find(pc);
_stubs_lock.unlockShared();
return method == NULL || strcmp((const char*)method, "call_stub") != 0;
} else if (_java_methods.contains(pc)) {
return true;
}
// Check if the next frame is below on the current stack
if (fp <= prev_fp || fp >= bottom) {
break;
}
// Frame pointer must be word aligned
if ((fp & (sizeof(uintptr_t) - 1)) != 0) {
break;
}
prev_fp = fp;
pc = ((const void**)fp)[1];
fp = ((uintptr_t*)fp)[0];
const void* pc = (const void*)StackFrame(ucontext).pc();
if (_runtime_stubs.contains(pc)) {
_stubs_lock.lockShared();
jmethodID method = _runtime_stubs.find(pc);
_stubs_lock.unlockShared();
return method == NULL || strcmp((const char*)method, "call_stub") != 0;
}
return false;
return CodeHeap::contains(pc);
}
int Profiler::getNativeTrace(void* ucontext, ASGCT_CallFrame* frames, int tid) {
const void* native_callchain[MAX_NATIVE_FRAMES];
int native_frames = _engine->getNativeTrace(ucontext, tid, native_callchain, MAX_NATIVE_FRAMES,
&_java_methods, &_runtime_stubs);
int native_frames = _engine->getNativeTrace(ucontext, tid, native_callchain, MAX_NATIVE_FRAMES);
int depth = 0;
jmethodID prev_method = NULL;
@@ -338,7 +313,7 @@ int Profiler::getJavaTraceAsync(void* ucontext, ASGCT_CallFrame* frames, int max
if (_safe_mode & CHECK_STATE) {
int state = vm_thread->state();
if ((state == 8 || state == 9) && !checkWalkable(ucontext)) {
if ((state == 8 || state == 9) && !inJavaCode(ucontext)) {
// Thread is in Java state, but does not have a valid Java frame on top of the stack
return 0;
}
@@ -577,7 +552,7 @@ void Profiler::recordSample(void* ucontext, u64 counter, jint event_type, jmetho
if (event_type == 0) {
// Need to reset PerfEvents ring buffer, even though we discard the collected trace
_engine->getNativeTrace(ucontext, tid, NULL, 0, &_java_methods, &_runtime_stubs);
_engine->getNativeTrace(ucontext, tid, NULL, 0);
}
return;
}
@@ -621,16 +596,16 @@ void Profiler::recordSample(void* ucontext, u64 counter, jint event_type, jmetho
_locks[lock_index].unlock();
}
jboolean JNICALL Profiler::NativeLibraryLoadTrap(JNIEnv* env, jobject self, jstring name, jboolean builtin) {
jboolean result = ((jboolean JNICALL (*)(JNIEnv*, jobject, jstring, jboolean))
_instance._original_NativeLibrary_load)(env, self, name, builtin);
jboolean JNICALL Profiler::NativeLibraryLoadTrap(JNIEnv* env, jobject self, jstring name, jboolean builtin, jboolean throws) {
jboolean result = ((jboolean JNICALL (*)(JNIEnv*, jobject, jstring, jboolean, jboolean))
_instance._original_NativeLibrary_load)(env, self, name, builtin, throws);
_instance.updateSymbols(false);
return result;
}
jboolean JNICALL Profiler::NativeLibrariesLoadTrap(JNIEnv* env, jobject self, jobject lib, jstring name, jboolean builtin, jboolean jni) {
jboolean result = ((jboolean JNICALL (*)(JNIEnv*, jobject, jobject, jstring, jboolean, jboolean))
_instance._original_NativeLibrary_load)(env, self, lib, name, builtin, jni);
jboolean JNICALL Profiler::NativeLibrariesLoadTrap(JNIEnv* env, jobject self, jobject lib, jstring name, jboolean builtin, jboolean jni, jboolean throws) {
jboolean result = ((jboolean JNICALL (*)(JNIEnv*, jobject, jobject, jstring, jboolean, jboolean, jboolean))
_instance._original_NativeLibrary_load)(env, self, lib, name, builtin, jni, throws);
_instance.updateSymbols(false);
return result;
}
@@ -641,6 +616,8 @@ void JNICALL Profiler::ThreadSetNativeNameTrap(JNIEnv* env, jobject self, jstrin
}
void Profiler::bindNativeLibraryLoad(JNIEnv* env, bool enable) {
static unsigned int warning_reported = 0;
jclass NativeLibrary;
if (_original_NativeLibrary_load == NULL) {
@@ -650,15 +627,25 @@ void Profiler::bindNativeLibraryLoad(JNIEnv* env, bool enable) {
strcpy(original_jni_name, "Java_jdk_internal_loader_NativeLibraries_");
_trapped_NativeLibrary_load = (void*)NativeLibrariesLoadTrap;
// JDK 15+
_load_method.name = (char*)"load";
_load_method.signature = (char*)"(Ljdk/internal/loader/NativeLibraries$NativeLibraryImpl;Ljava/lang/String;ZZ)Z";
if (env->GetStaticMethodID(NativeLibrary, "load", "(Ljdk/internal/loader/NativeLibraries$NativeLibraryImpl;Ljava/lang/String;ZZZ)Z") != NULL) {
// JDK 17+
_load_method.name = (char*)"load";
_load_method.signature = (char*)"(Ljdk/internal/loader/NativeLibraries$NativeLibraryImpl;Ljava/lang/String;ZZZ)Z";
} else {
// JDK 15-16
_load_method.name = (char*)"load";
_load_method.signature = (char*)"(Ljdk/internal/loader/NativeLibraries$NativeLibraryImpl;Ljava/lang/String;ZZ)Z";
}
} else if ((NativeLibrary = env->FindClass("java/lang/ClassLoader$NativeLibrary")) != NULL) {
strcpy(original_jni_name, "Java_java_lang_ClassLoader_00024NativeLibrary_");
_trapped_NativeLibrary_load = (void*)NativeLibraryLoadTrap;
if (env->GetMethodID(NativeLibrary, "load0", "(Ljava/lang/String;Z)Z") != NULL) {
if (env->GetMethodID(NativeLibrary, "load0", "(Ljava/lang/String;ZZ)Z") != NULL) {
// JDK 11.0.15+ (workaround for JDK-8288547)
_load_method.name = (char*)"load0";
_load_method.signature = (char*)"(Ljava/lang/String;ZZ)Z";
} else if (env->GetMethodID(NativeLibrary, "load0", "(Ljava/lang/String;Z)Z") != NULL) {
// JDK 9-14
_load_method.name = (char*)"load0";
_load_method.signature = (char*)"(Ljava/lang/String;Z)Z";
@@ -673,13 +660,13 @@ void Profiler::bindNativeLibraryLoad(JNIEnv* env, bool enable) {
}
} else {
fprintf(stderr, "WARNING: Failed to intercept NativeLibraries.load()\n");
if (!warning_reported++) fprintf(stderr, "WARNING: async-profiler failed to intercept NativeLibraries.load()\n");
return;
}
strcat(original_jni_name, _load_method.name);
if ((_original_NativeLibrary_load = dlsym(VM::_libjava, original_jni_name)) == NULL) {
fprintf(stderr, "WARNING: Could not find %s\n", original_jni_name);
if (!warning_reported++) fprintf(stderr, "WARNING: async-profiler could not find %s\n", original_jni_name);
return;
}
@@ -688,7 +675,7 @@ void Profiler::bindNativeLibraryLoad(JNIEnv* env, bool enable) {
? "jdk/internal/loader/NativeLibraries"
: "java/lang/ClassLoader$NativeLibrary";
if ((NativeLibrary = env->FindClass(class_name)) == NULL) {
fprintf(stderr, "WARNING: Could not find %s\n", class_name);
if (!warning_reported++) fprintf(stderr, "WARNING: async-profiler could not find %s\n", class_name);
return;
}
}
@@ -817,7 +804,7 @@ Engine* Profiler::selectEngine(const char* event_name) {
}
}
Error Profiler::checkJvmCapabilities() {
Error Profiler::checkJvmCapabilities(bool print_warnings) {
if (VMStructs::libjvm() == NULL) {
return Error("Could not find libjvm among loaded libraries. Unsupported JVM?");
}
@@ -826,7 +813,7 @@ Error Profiler::checkJvmCapabilities() {
return Error("Could not find VMThread bridge. Unsupported JVM?");
}
if (VMStructs::_get_stack_trace == NULL) {
if (VMStructs::_get_stack_trace == NULL && print_warnings) {
fprintf(stderr, "WARNING: Install JVM debug symbols to improve profile accuracy\n");
}
@@ -839,7 +826,7 @@ Error Profiler::start(Arguments& args, bool reset) {
return Error("Profiler already started");
}
Error error = checkJvmCapabilities();
Error error = checkJvmCapabilities(args._log);
if (error) {
return error;
}
@@ -896,7 +883,11 @@ Error Profiler::start(Arguments& args, bool reset) {
updateSymbols(args._ring != RING_USER);
_safe_mode = args._safe_mode | (VM::hotspot_version() ? 0 : HOTSPOT_ONLY);
_safe_mode = VM::_safe_mode | args._safe_mode;
if (VM::hotspot_version() < 8) {
// Cannot use JVM TI stack walker during GC on non-HotSpot JVMs or with PermGen
_safe_mode |= GC_TRACES | LAST_JAVA_PC;
}
_add_thread_frame = args._threads && args._output != OUTPUT_JFR;
_update_thread_names = (args._threads || args._output == OUTPUT_JFR) && VMThread::hasNativeId();
@@ -958,7 +949,7 @@ Error Profiler::check(Arguments& args) {
return Error("Profiler already started");
}
Error error = checkJvmCapabilities();
Error error = checkJvmCapabilities(args._log);
if (error) {
return error;
}

View File

@@ -143,8 +143,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);
static jboolean JNICALL NativeLibrariesLoadTrap(JNIEnv* env, jobject self, jobject lib, jstring name, jboolean builtin, jboolean jni);
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);
void bindNativeLibraryLoad(JNIEnv* env, bool enable);
// Support for intercepting Thread.setNativeName()
@@ -162,7 +162,7 @@ class Profiler {
void onThreadEnd(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread);
const char* asgctError(int code);
bool checkWalkable(void* ucontext);
bool inJavaCode(void* ucontext);
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);
@@ -180,7 +180,7 @@ class Profiler {
void updateNativeThreadNames();
bool excludeTrace(FrameName* fn, CallTraceSample* trace);
Engine* selectEngine(const char* event_name);
Error checkJvmCapabilities();
Error checkJvmCapabilities(bool print_warnings);
public:
static Profiler _instance;

View File

@@ -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 (arg2() == (uintptr_t)-1) {
if ((int)arg2() == -1) {
uintptr_t pc = this->pc();
if ((pc & 0xfff) >= 7 && *(unsigned char*)(pc - 7) == 0xb8 && *(int*)(pc - 6) == SYS_poll) {
this->pc() = pc - 7;

View File

@@ -33,6 +33,8 @@ 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;
@@ -64,17 +66,14 @@ 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");
@@ -117,12 +116,16 @@ 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);
_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_COMPILED_METHOD_LOAD, NULL);
_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_COMPILED_METHOD_UNLOAD, 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_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);
}
@@ -137,6 +140,13 @@ 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) {
@@ -264,7 +274,24 @@ 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(), VM::jni());
JavaAPI::registerNatives(VM::jvmti(), jni);
return JNI_VERSION_1_6;
}

View File

@@ -90,6 +90,7 @@ 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);
@@ -97,6 +98,7 @@ class VM {
static void loadAllMethodIDs(jvmtiEnv* jvmti, JNIEnv* jni);
public:
static int _safe_mode;
static void* _libjvm;
static void* _libjava;
static AsyncGetCallTrace _asyncGetCallTrace;
@@ -120,6 +122,10 @@ class VM {
return _hotspot_version;
}
static int hotspot_minor() {
return _hotspot_minor;
}
static bool inRedefineClasses() {
return _in_redefine_classes > 0;
}

View File

@@ -17,6 +17,7 @@
#include <pthread.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include "vmStructs.h"
#include "vmEntry.h"
@@ -43,6 +44,8 @@ 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;
@@ -55,6 +58,8 @@ 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) {
@@ -90,6 +95,11 @@ 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);
@@ -142,6 +152,24 @@ 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);
@@ -161,6 +189,11 @@ 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() {
@@ -187,6 +220,12 @@ 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) {
@@ -238,3 +277,27 @@ 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();
}

View File

@@ -46,6 +46,8 @@ 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;
@@ -60,6 +62,9 @@ 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();
@@ -238,6 +243,18 @@ 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() {
@@ -246,4 +263,13 @@ class CollectedHeap : VMStructs {
}
};
class DisableSweeper : VMStructs {
private:
bool _enabled;
public:
DisableSweeper();
~DisableSweeper();
};
#endif // _VMSTRUCTS_H