Compare commits

...

3 Commits

Author SHA1 Message Date
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
12 changed files with 190 additions and 76 deletions

View File

@@ -1,5 +1,13 @@
# Changelog
## [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

View File

@@ -1,4 +1,4 @@
PROFILER_VERSION=1.8.4
PROFILER_VERSION=1.8.5
JATTACH_VERSION=1.5
JAVAC_RELEASE_VERSION=6

View File

@@ -14,14 +14,14 @@ async-profiler can trace the following kinds of events:
## Download
Latest release (1.8.4):
Latest release (1.8.5):
- Linux x64 (glibc): [async-profiler-1.8.4-linux-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.4/async-profiler-1.8.4-linux-x64.tar.gz)
- Linux x86 (glibc): [async-profiler-1.8.4-linux-x86.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.4/async-profiler-1.8.4-linux-x86.tar.gz)
- Linux x64 (musl): [async-profiler-1.8.4-linux-musl-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.4/async-profiler-1.8.4-linux-musl-x64.tar.gz)
- Linux ARM: [async-profiler-1.8.4-linux-arm.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.4/async-profiler-1.8.4-linux-arm.tar.gz)
- Linux AArch64: [async-profiler-1.8.4-linux-aarch64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.4/async-profiler-1.8.4-linux-aarch64.tar.gz)
- macOS x64: [async-profiler-1.8.4-macos-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.4/async-profiler-1.8.4-macos-x64.tar.gz)
- Linux x64 (glibc): [async-profiler-1.8.5-linux-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.5/async-profiler-1.8.5-linux-x64.tar.gz)
- Linux x86 (glibc): [async-profiler-1.8.5-linux-x86.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.5/async-profiler-1.8.5-linux-x86.tar.gz)
- Linux x64 (musl): [async-profiler-1.8.5-linux-musl-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.5/async-profiler-1.8.5-linux-musl-x64.tar.gz)
- Linux ARM: [async-profiler-1.8.5-linux-arm.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.5/async-profiler-1.8.5-linux-arm.tar.gz)
- Linux AArch64: [async-profiler-1.8.5-linux-aarch64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.5/async-profiler-1.8.5-linux-aarch64.tar.gz)
- macOS x64: [async-profiler-1.8.5-macos-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.5/async-profiler-1.8.5-macos-x64.tar.gz)
[Early access](https://github.com/jvm-profiling-tools/async-profiler/releases/tag/v2.0-b1) (2.0-b1):
@@ -241,7 +241,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.4/src/arguments.cpp#L49).
[in the source code](https://github.com/jvm-profiling-tools/async-profiler/blob/v1.8.5/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.4</version>
<version>1.8.5</version>
<packaging>jar</packaging>
<name>async-profiler</name>

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

@@ -262,47 +262,21 @@ 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 _java_methods.contains(pc);
}
int Profiler::getNativeTrace(void* ucontext, ASGCT_CallFrame* frames, int tid) {
@@ -336,7 +310,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;
}

View File

@@ -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);