mirror of
https://github.com/async-profiler/async-profiler.git
synced 2026-04-28 10:53:49 +00:00
Compare commits
3 Commits
499904dce7
...
v1.8.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32997bb645 | ||
|
|
9b11014b25 | ||
|
|
516d717d49 |
@@ -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
|
||||
|
||||
2
Makefile
2
Makefile
@@ -1,4 +1,4 @@
|
||||
PROFILER_VERSION=1.8.4
|
||||
PROFILER_VERSION=1.8.5
|
||||
JATTACH_VERSION=1.5
|
||||
JAVAC_RELEASE_VERSION=6
|
||||
|
||||
|
||||
16
README.md
16
README.md
@@ -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`
|
||||
|
||||
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.4</version>
|
||||
<version>1.8.5</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>async-profiler</name>
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
@@ -69,32 +71,36 @@ public class FlameGraph {
|
||||
|
||||
String[] trace = line.substring(0, space).split(";");
|
||||
long ticks = Long.parseLong(line.substring(space + 1));
|
||||
|
||||
depth = Math.max(depth, trace.length);
|
||||
|
||||
Frame frame = root;
|
||||
if (reverse) {
|
||||
for (int i = trace.length; --i >= skip; ) {
|
||||
frame.total += ticks;
|
||||
frame = frame.child(trace[i]);
|
||||
}
|
||||
} else {
|
||||
for (int i = skip; i < trace.length; i++) {
|
||||
frame.total += ticks;
|
||||
frame = frame.child(trace[i]);
|
||||
}
|
||||
}
|
||||
frame.total += ticks;
|
||||
frame.self += ticks;
|
||||
addSample(trace, ticks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addSample(String[] trace, long ticks) {
|
||||
Frame frame = root;
|
||||
if (reverse) {
|
||||
for (int i = trace.length; --i >= skip; ) {
|
||||
frame.total += ticks;
|
||||
frame = frame.child(trace[i]);
|
||||
}
|
||||
} else {
|
||||
for (int i = skip; i < trace.length; i++) {
|
||||
frame.total += ticks;
|
||||
frame = frame.child(trace[i]);
|
||||
}
|
||||
}
|
||||
frame.total += ticks;
|
||||
frame.self += ticks;
|
||||
|
||||
depth = Math.max(depth, trace.length);
|
||||
}
|
||||
|
||||
public void dump() throws IOException {
|
||||
if (output == null) {
|
||||
dump(System.out);
|
||||
} else {
|
||||
try (PrintStream out = new PrintStream(output, "UTF-8")) {
|
||||
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(output), 32768);
|
||||
PrintStream out = new PrintStream(bos, false, "UTF-8")) {
|
||||
dump(out);
|
||||
}
|
||||
}
|
||||
@@ -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("'", "\\'");
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
89
src/converter/jfr2flame.java
Normal file
89
src/converter/jfr2flame.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
26
src/converter/one/jfr/StackTrace.java
Normal file
26
src/converter/one/jfr/StackTrace.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user