Commits vergleichen
Keine gemeinsamen Commits. "3c205788b4711d808838c0bd15e02218f457f58e" und "92a39fc8502659a5c0d43f4d96f79da57cd9fbae" haben vollständig unterschiedliche Historien.
3c205788b4
...
92a39fc850
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,2 @@
|
|||||||
/.idea
|
/.idea
|
||||||
/target
|
/target
|
||||||
/dependency-reduced-pom.xml
|
|
48
pom.xml
48
pom.xml
@ -10,58 +10,30 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<file.encoding>UTF-8</file.encoding>
|
<file.encoding>UTF-8</file.encoding>
|
||||||
<maven.compiler.source>8</maven.compiler.source>
|
<maven.compiler.source>11</maven.compiler.source>
|
||||||
<maven.compiler.target>8</maven.compiler.target>
|
<maven.compiler.target>11</maven.compiler.target>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
<version>3.2.0</version>
|
||||||
<version>3.2.4</version>
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>shade</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
<configuration>
|
||||||
<minimizeJar>true</minimizeJar>
|
<archive>
|
||||||
<transformers>
|
<manifest>
|
||||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
<addClasspath>true</addClasspath>
|
||||||
|
<mainClass>de.steamwar.Main</mainClass>
|
||||||
|
</manifest>
|
||||||
<manifestEntries>
|
<manifestEntries>
|
||||||
<Main-Class>de.steamwar.Main</Main-Class>
|
|
||||||
<Agent-Class>de.steamwar.Agent</Agent-Class>
|
<Agent-Class>de.steamwar.Agent</Agent-Class>
|
||||||
<Premain-Class>de.steamwar.Agent</Premain-Class>
|
<Premain-Class>de.steamwar.Agent</Premain-Class>
|
||||||
</manifestEntries>
|
</manifestEntries>
|
||||||
</transformer>
|
</archive>
|
||||||
</transformers>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
<finalName>LixfelsProfiler</finalName>
|
<finalName>LixfelsProfiler</finalName>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
<repositories>
|
|
||||||
<repository>
|
|
||||||
<id>fabric</id>
|
|
||||||
<url>https://maven.fabricmc.net/</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>tools.profiler</groupId>
|
|
||||||
<artifactId>async-profiler</artifactId>
|
|
||||||
<version>2.9</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>net.fabricmc</groupId>
|
|
||||||
<artifactId>mapping-io</artifactId>
|
|
||||||
<version>0.3.0</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</project>
|
</project>
|
@ -1,68 +1,32 @@
|
|||||||
package de.steamwar;
|
package de.steamwar;
|
||||||
|
|
||||||
import one.profiler.AsyncProfiler;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.instrument.Instrumentation;
|
import java.lang.instrument.Instrumentation;
|
||||||
|
|
||||||
public class Agent {
|
public class Agent {
|
||||||
private Agent() {}
|
private Agent() {}
|
||||||
|
|
||||||
private static Thread shutdownHook;
|
private static Sampler sampler;
|
||||||
private static Instrumentation instrumentation;
|
|
||||||
public static Instrumentation getInstrumentation() {
|
|
||||||
return instrumentation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void premain(String args, Instrumentation inst) {
|
public static void premain(String args, Instrumentation inst) {
|
||||||
agentmain(args, inst);
|
if ("start".equals(args)) {
|
||||||
|
sampler = new Sampler();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void agentmain(String args, Instrumentation inst) {
|
public static void agentmain(String args, Instrumentation inst) {
|
||||||
instrumentation = inst;
|
|
||||||
//AsyncProfiler.getInstance("/home/lixfel/libasyncProfiler.so");
|
|
||||||
switch (args) {
|
switch (args) {
|
||||||
case "start":
|
case "start":
|
||||||
start();
|
if(sampler == null)
|
||||||
|
sampler = new Sampler();
|
||||||
break;
|
break;
|
||||||
case "stop":
|
case "stop":
|
||||||
stop();
|
if(sampler != null)
|
||||||
|
sampler.stop();
|
||||||
|
sampler = null;
|
||||||
break;
|
break;
|
||||||
case "heap":
|
case "heap":
|
||||||
OpenJ9.heapdump();
|
OpenJ9.heapdump();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void start() {
|
|
||||||
if(shutdownHook != null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
callProfiler("start,event=wall,threads,interval=1000000,cstack=fp"); // 1mio ns = 1ms
|
|
||||||
shutdownHook = new Thread(Agent::stop, "SamplerShutdownHook");
|
|
||||||
Runtime.getRuntime().addShutdownHook(shutdownHook);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void stop() {
|
|
||||||
if(shutdownHook == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
AsyncProfiler.getInstance().stop();
|
|
||||||
new Profile(ObfHelper.loadMappingsIfPresent(), callProfiler("collapsed=samples,dot,sig")).save();
|
|
||||||
|
|
||||||
try {
|
|
||||||
Runtime.getRuntime().removeShutdownHook(shutdownHook);
|
|
||||||
shutdownHook = null;
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
//ignored
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String callProfiler(String command) {
|
|
||||||
try {
|
|
||||||
return AsyncProfiler.getInstance().execute(command);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,189 +0,0 @@
|
|||||||
package de.steamwar;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class Method {
|
|
||||||
private static final DecimalFormat df = new DecimalFormat("0.00");
|
|
||||||
|
|
||||||
private static int idCounter;
|
|
||||||
|
|
||||||
private final Profile profile;
|
|
||||||
private final int id;
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
private final int[] samples = new int[State.values().length];
|
|
||||||
private final HashMap<Method, Integer> calls = new HashMap<>();
|
|
||||||
|
|
||||||
public Method(Profile profile, Map<String, ObfHelper.ClassMapping> mappings, String name) {
|
|
||||||
this.profile = profile;
|
|
||||||
this.id = idCounter++;
|
|
||||||
|
|
||||||
try {
|
|
||||||
name = javaPrettify(mappings, name);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
if(name.startsWith("/")) { // native lib
|
|
||||||
name = name.substring(name.lastIndexOf('/') + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int ownSampleRuns() {
|
|
||||||
return Arrays.stream(samples).sum();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean filtered() {
|
|
||||||
return Arrays.stream(samples).sum() < Profile.FILTER;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(State state, int samples, Method calling, boolean outsideCycle) {
|
|
||||||
if(outsideCycle)
|
|
||||||
this.samples[state.ordinal()] += samples;
|
|
||||||
|
|
||||||
if (calling != null)
|
|
||||||
calls.compute(calling, (pre, sample) -> sample == null ? samples : sample + samples);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String percentage(int value) {
|
|
||||||
return df.format(value * 100.0 / ownSampleRuns());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String totalPercentage(int value) {
|
|
||||||
return df.format(value * 100.0 / profile.getSampleRuns());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String time(int value) {
|
|
||||||
return df.format(value * Profile.SAMPLING_SPEED / 1e9);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void prune() {
|
|
||||||
calls.keySet().removeIf(Method::filtered);
|
|
||||||
calls.values().removeIf(s -> s < Profile.FILTER);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void toDot(OutputStreamWriter writer) throws IOException {
|
|
||||||
int waiting = samples[State.WAITING.ordinal()];
|
|
||||||
int runnable = samples[State.RUNNING.ordinal()];
|
|
||||||
int io = samples[State.IO.ordinal()];
|
|
||||||
int time = samples[State.TIME.ordinal()];
|
|
||||||
int total = waiting + runnable + io + time;
|
|
||||||
|
|
||||||
int r = ( 255 * waiting + 192 * time) / total;
|
|
||||||
int g = (255 * runnable + 64 * waiting + 192 * io + 192 * time) / total;
|
|
||||||
int b = ( 64 * waiting + 255 * io + 192 * time) / total;
|
|
||||||
int a = (int) (255 * Math.sqrt(total / (double) profile.getSampleRuns()));
|
|
||||||
if (a > 255)
|
|
||||||
a = 255;
|
|
||||||
|
|
||||||
writer.append(String.valueOf(id)).append(" [fillcolor=\"#").append(String.format("%02X", r)).append(String.format("%02X", g)).append(String.format("%02X", b)).append(String.format("%02X", a)).append("\",label=\"").append(name).append("\\n").append(time(ownSampleRuns())).append("s ").append(totalPercentage(ownSampleRuns())).append("% R").append(percentage(runnable)).append("% T").append(percentage(time)).append("% I").append(percentage(io)).append("% W").append(percentage(waiting)).append("%\"];\n");
|
|
||||||
|
|
||||||
for (Map.Entry<Method, Integer> entry : calls.entrySet()) {
|
|
||||||
writer.append(String.valueOf(id)).append(" -> ").append(String.valueOf(entry.getKey().id)).append(" [label=\"").append(time(entry.getValue())).append("s\\n").append(percentage(entry.getValue())).append("%\",weight=").append(String.valueOf(entry.getValue())).append("];\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String javaPrettify(Map<String, ObfHelper.ClassMapping> mappings, String name) {
|
|
||||||
try {
|
|
||||||
int methodDivisor = name.lastIndexOf(".");
|
|
||||||
String className = name.substring(0, methodDivisor);
|
|
||||||
String methodDefinition = name.substring(methodDivisor + 1).replace('|', ';');
|
|
||||||
String methodName = methodDefinition.split("\\(", 2)[0];
|
|
||||||
String methodArgs = methodDefinition.split("\\(", 2)[1].split("\\)", 2)[0];
|
|
||||||
String methodReturn = methodDefinition.split("\\)", 2)[1];
|
|
||||||
|
|
||||||
ObfHelper.ClassMapping mapping = mappings.get(className);
|
|
||||||
if (mapping != null) {
|
|
||||||
className = mapping.mojangName;
|
|
||||||
methodName = mapping.methodsByObf.getOrDefault(methodDefinition, methodName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (className.contains("$$Lambda$")) {
|
|
||||||
className = className.split("\\$\\$Lambda\\$", 2)[0] + ".λ";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (methodName.startsWith("lambda$")) {
|
|
||||||
methodName = methodName.split("\\$", 2)[1].replace('$', 'λ');
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
formatJvmTypes(mappings, builder, methodReturn);
|
|
||||||
builder.append("\\n").append(className.replace('$', '.')).append(".").append(methodName).append("\\n(");
|
|
||||||
formatJvmTypes(mappings, builder, methodArgs);
|
|
||||||
builder.append(")");
|
|
||||||
|
|
||||||
return builder.toString();
|
|
||||||
} catch (Throwable t) {
|
|
||||||
throw new IllegalArgumentException(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void formatJvmTypes(Map<String, ObfHelper.ClassMapping> mappings, StringBuilder builder, String jvmType) {
|
|
||||||
int i = 0;
|
|
||||||
while(i < jvmType.length()) {
|
|
||||||
int arrayDimensions = 0;
|
|
||||||
while(jvmType.charAt(i) == '[') {
|
|
||||||
arrayDimensions++;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (jvmType.charAt(i)) {
|
|
||||||
case 'B':
|
|
||||||
builder.append("byte");
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'C':
|
|
||||||
builder.append("char");
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'D':
|
|
||||||
builder.append("double");
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'F':
|
|
||||||
builder.append("float");
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'I':
|
|
||||||
builder.append("int");
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'J':
|
|
||||||
builder.append("long");
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'S':
|
|
||||||
builder.append("short");
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'Z':
|
|
||||||
builder.append("boolean");
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'V':
|
|
||||||
builder.append("void");
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'L':
|
|
||||||
int end = jvmType.indexOf(';', i++);
|
|
||||||
String className = jvmType.substring(i, end).replace('/', '.');
|
|
||||||
ObfHelper.ClassMapping mapping = mappings.get(className);
|
|
||||||
builder.append((mapping == null ? className : mapping.mojangName).replace('$', '.'));
|
|
||||||
i = end+1;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException(jvmType + " at " + i);
|
|
||||||
}
|
|
||||||
|
|
||||||
while(arrayDimensions-- > 0) {
|
|
||||||
builder.append("[]");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(i < jvmType.length())
|
|
||||||
builder.append(", ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
package de.steamwar;
|
|
||||||
|
|
||||||
import net.fabricmc.mappingio.MappingReader;
|
|
||||||
import net.fabricmc.mappingio.format.MappingFormat;
|
|
||||||
import net.fabricmc.mappingio.tree.MappingTree;
|
|
||||||
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class ObfHelper {
|
|
||||||
private ObfHelper() {}
|
|
||||||
|
|
||||||
public static final String MOJANG_PLUS_YARN_NAMESPACE = "mojang+yarn";
|
|
||||||
public static final String SPIGOT_NAMESPACE = "spigot";
|
|
||||||
|
|
||||||
public static Map<String, ClassMapping> loadMappingsIfPresent() {
|
|
||||||
for(Class<?> clazz : Agent.getInstrumentation().getAllLoadedClasses()) {
|
|
||||||
if(clazz.getName().equals("io.papermc.paper.util.ObfHelper")) {
|
|
||||||
try (final InputStream mappingsInputStream = clazz.getClassLoader().getResourceAsStream("META-INF/mappings/reobf.tiny")) {
|
|
||||||
if (mappingsInputStream == null)
|
|
||||||
return Collections.emptyMap();
|
|
||||||
|
|
||||||
final MemoryMappingTree tree = new MemoryMappingTree();
|
|
||||||
MappingReader.read(new InputStreamReader(mappingsInputStream, StandardCharsets.UTF_8), MappingFormat.TINY_2, tree);
|
|
||||||
Map<String, ClassMapping> classes = new HashMap<>();
|
|
||||||
|
|
||||||
for (final MappingTree.ClassMapping cls : tree.getClasses()) {
|
|
||||||
final Map<String, String> methods = new HashMap<>();
|
|
||||||
|
|
||||||
for (final MappingTree.MethodMapping methodMapping : cls.getMethods()) {
|
|
||||||
methods.put(
|
|
||||||
methodKey(
|
|
||||||
methodMapping.getName(SPIGOT_NAMESPACE),
|
|
||||||
methodMapping.getDesc(SPIGOT_NAMESPACE)
|
|
||||||
),
|
|
||||||
methodMapping.getName(MOJANG_PLUS_YARN_NAMESPACE)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final ClassMapping map = new ClassMapping(
|
|
||||||
cls.getName(SPIGOT_NAMESPACE).replace('/', '.'),
|
|
||||||
cls.getName(MOJANG_PLUS_YARN_NAMESPACE).replace('/', '.'),
|
|
||||||
methods
|
|
||||||
);
|
|
||||||
classes.put(map.obfName, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
return classes;
|
|
||||||
} catch (final IOException ex) {
|
|
||||||
System.err.println("Failed to load mappings for stacktrace deobfuscation.");
|
|
||||||
ex.printStackTrace();
|
|
||||||
return Collections.emptyMap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Collections.emptyMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String methodKey(final String obfName, final String obfDescriptor) {
|
|
||||||
return obfName + obfDescriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ClassMapping {
|
|
||||||
public final String obfName;
|
|
||||||
public final String mojangName;
|
|
||||||
public final Map<String, String> methodsByObf;
|
|
||||||
|
|
||||||
public ClassMapping(String obfName, String mojangName, Map<String, String> methodsByObf) {
|
|
||||||
this.obfName = obfName;
|
|
||||||
this.mojangName = mojangName;
|
|
||||||
this.methodsByObf = methodsByObf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
package de.steamwar;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class Profile {
|
|
||||||
|
|
||||||
public static final long SAMPLING_SPEED = 1000000;
|
|
||||||
public static final int FILTER = 100;
|
|
||||||
|
|
||||||
private static final HashMap<String, State> stateMethods = new HashMap<>();
|
|
||||||
static {
|
|
||||||
stateMethods.put("jdk.internal.misc.Unsafe.park(ZJ)V", State.TIME);
|
|
||||||
stateMethods.put("java.lang.Thread.sleep(J)V", State.TIME);
|
|
||||||
|
|
||||||
stateMethods.put("java.lang.Object.wait(J)V", State.WAITING);
|
|
||||||
stateMethods.put("java.lang.Object.wait(JI)V", State.WAITING);
|
|
||||||
stateMethods.put("java.lang.ref.Reference.waitForReferencePendingList()V", State.WAITING);
|
|
||||||
stateMethods.put("com.ibm.lang.management.internal.MemoryNotificationThread.processNotificationLoop()V", State.WAITING);
|
|
||||||
|
|
||||||
stateMethods.put("sun.nio.ch.SocketDispatcher.read0(Ljava/io/FileDescriptor|JI)I", State.IO);
|
|
||||||
stateMethods.put("io.netty.channel.epoll.Native.epollWait(IJII)I", State.IO);
|
|
||||||
stateMethods.put("openj9.internal.tools.attach.target.IPC.waitSemaphore()I", State.IO);
|
|
||||||
stateMethods.put("java.io.FileInputStream.readBytes([BII)I", State.IO);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final HashSet<String> omittedMethods = new HashSet<>();
|
|
||||||
static {
|
|
||||||
omittedMethods.add("java.lang.Thread.run()V");
|
|
||||||
omittedMethods.add("java.util.concurrent.FutureTask.run()V");
|
|
||||||
omittedMethods.add("java.util.concurrent.ForkJoinWorkerThread.run()V");
|
|
||||||
omittedMethods.add("java.util.concurrent.ForkJoinWorkerThread.runWorker(Ljava/util/concurrent/ForkJoinPool$WorkQueue|)V");
|
|
||||||
omittedMethods.add("java.util.concurrent.ThreadPoolExecutor$Worker.run()V");
|
|
||||||
omittedMethods.add("java.util.concurrent.ThreadPoolExecutor.runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker|)V");
|
|
||||||
}
|
|
||||||
|
|
||||||
private final HashMap<String, Method> methods = new HashMap<>();
|
|
||||||
private final HashMap<Method, Integer> threads = new HashMap<>();
|
|
||||||
|
|
||||||
public Profile(Map<String, ObfHelper.ClassMapping> mappings, String profile) {
|
|
||||||
HashSet<String> previousIds = new HashSet<>();
|
|
||||||
for(String line : profile.split("\n")) {
|
|
||||||
previousIds.clear();
|
|
||||||
|
|
||||||
int sampleDivisor = line.lastIndexOf(" ");
|
|
||||||
int samples = Integer.parseInt(line.substring(sampleDivisor + 1));
|
|
||||||
|
|
||||||
String[] names = line.substring(0, sampleDivisor).split(";");
|
|
||||||
if(names.length <= 1)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
State state = State.RUNNING;
|
|
||||||
|
|
||||||
for(String name : names) {
|
|
||||||
state = stateMethods.getOrDefault(name, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
Method calling = null;
|
|
||||||
for(int i = names.length-1; i >= 0; i--) {
|
|
||||||
String name = names[i];
|
|
||||||
|
|
||||||
if(omittedMethods.contains(name))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Method method = methods.computeIfAbsent(name, id -> new Method(this, mappings, id));
|
|
||||||
method.add(state, samples, calling, previousIds.add(name));
|
|
||||||
calling = method;
|
|
||||||
}
|
|
||||||
|
|
||||||
threads.compute(calling, (thread, previous) -> previous == null ? samples : previous + samples);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSampleRuns() {
|
|
||||||
return threads.values().stream().max(Integer::compareTo).orElse(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void prune() {
|
|
||||||
methods.values().removeIf(Method::filtered);
|
|
||||||
methods.values().forEach(Method::prune);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save() {
|
|
||||||
prune();
|
|
||||||
|
|
||||||
File output = new File(System.getProperty("user.home"), "samples.dot");
|
|
||||||
try(OutputStreamWriter writer = new FileWriter(output)) {
|
|
||||||
//\ngraph [splines=true overlap=false];
|
|
||||||
writer.append("digraph {\ngraph [splines=true,overlap=false,searchsize=2];\nnode [shape=plaintext,style=\"filled\",margin=0.1];\n");
|
|
||||||
|
|
||||||
for(Method method : methods.values()) {
|
|
||||||
method.toDot(writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.append("}");
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
171
src/main/java/de/steamwar/Sampler.java
Normale Datei
171
src/main/java/de/steamwar/Sampler.java
Normale Datei
@ -0,0 +1,171 @@
|
|||||||
|
package de.steamwar;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.locks.LockSupport;
|
||||||
|
|
||||||
|
public class Sampler {
|
||||||
|
|
||||||
|
public static final long SAMPLING_SPEED = 1000000;
|
||||||
|
public static final int FILTER = 100;
|
||||||
|
|
||||||
|
private static final Set<String> waitingMethods = new HashSet<>();
|
||||||
|
static {
|
||||||
|
waitingMethods.add("io.netty.channel.epoll.Native.epollWait");
|
||||||
|
waitingMethods.add("io.netty.channel.epoll.Native.epollWait0");
|
||||||
|
waitingMethods.add("org.bukkit.craftbukkit.libs.jline.internal.NonBlockingInputStream.read");
|
||||||
|
waitingMethods.add("net.minecrell.terminalconsole.SimpleTerminalConsole.readCommands");
|
||||||
|
waitingMethods.add("com.ibm.lang.management.internal.MemoryNotificationThread.processNotificationLoop");
|
||||||
|
waitingMethods.add("openj9.internal.tools.attach.target.IPC.waitSemaphore");
|
||||||
|
waitingMethods.add("sun.nio.ch.Net.poll");
|
||||||
|
waitingMethods.add("sun.nio.ch.SocketDispatcher.read0");
|
||||||
|
waitingMethods.add("sun.awt.X11.XToolkit.waitForEvents");
|
||||||
|
waitingMethods.add("java.lang.ProcessHandleImpl.waitForProcessExit0");
|
||||||
|
waitingMethods.add("java.lang.ref.Reference.waitForReferencePendingList");
|
||||||
|
waitingMethods.add("java.util.concurrent.locks.LockSupport.parkNanos");
|
||||||
|
waitingMethods.add("java.io.FileInputStream.readBytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Set<String> omittedMethods = new HashSet<>();
|
||||||
|
static {
|
||||||
|
omittedMethods.add("java.lang.reflect.Method.invoke");
|
||||||
|
omittedMethods.add("java.lang.Thread.run");
|
||||||
|
|
||||||
|
omittedMethods.add("java.util.Iterator.forEachRemaining");
|
||||||
|
omittedMethods.add("java.lang.Iterable.forEach");
|
||||||
|
omittedMethods.add("java.util.ArrayList.forEach");
|
||||||
|
omittedMethods.add("java.util.ArrayList$ArrayListSpliterator.forEachRemaining");
|
||||||
|
omittedMethods.add("java.util.Map.forEach");
|
||||||
|
omittedMethods.add("java.util.HashMap$EntrySpliterator.forEachRemaining");
|
||||||
|
omittedMethods.add("java.util.Spliterators$IteratorSpliterator.forEachRemaining");
|
||||||
|
omittedMethods.add("java.util.Spliterators$ArraySpliterator.forEachRemaining");
|
||||||
|
omittedMethods.add("java.util.concurrent.ThreadPoolExecutor$Worker.run");
|
||||||
|
omittedMethods.add("java.util.concurrent.ThreadPoolExecutor.runWorker");
|
||||||
|
omittedMethods.add("java.util.concurrent.CompletableFuture$Completion.run");
|
||||||
|
omittedMethods.add("java.util.concurrent.CompletableFuture$AsyncSupply.run");
|
||||||
|
omittedMethods.add("java.util.stream.ReferencePipeline.forEach");
|
||||||
|
omittedMethods.add("java.util.stream.ReferencePipeline.collect");
|
||||||
|
omittedMethods.add("java.util.stream.ReferencePipeline$7$1.forEach");
|
||||||
|
omittedMethods.add("java.util.stream.ReferencePipeline$3$1.accept");
|
||||||
|
omittedMethods.add("java.util.stream.AbstractPipeline.evaluate");
|
||||||
|
omittedMethods.add("java.util.stream.AbstractPipeline.wrapAndCopyInto");
|
||||||
|
omittedMethods.add("java.util.stream.AbstractPipeline.copyInto");
|
||||||
|
omittedMethods.add("java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential");
|
||||||
|
omittedMethods.add("java.util.stream.ForEachOps$ForEachOp.evaluateSequential");
|
||||||
|
omittedMethods.add("java.util.stream.ReduceOps$ReduceOp.evaluateSequential");
|
||||||
|
omittedMethods.add("java.util.stream.ReduceOps$1ReducingSink.accept");
|
||||||
|
omittedMethods.add("java.util.stream.Streams$StreamBuilderImpl.forEachRemaining");
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<String, Trace> traces = new HashMap<>();
|
||||||
|
private final Thread samplerThread;
|
||||||
|
private final Thread shutdownHook;
|
||||||
|
private boolean shutdown = false;
|
||||||
|
|
||||||
|
private int sampleRuns;
|
||||||
|
|
||||||
|
public Sampler() {
|
||||||
|
samplerThread = new Thread(this::run, "Sampler");
|
||||||
|
samplerThread.setDaemon(true);
|
||||||
|
samplerThread.start();
|
||||||
|
shutdownHook = new Thread(this::stop, "SamplerShutdownHook");
|
||||||
|
Runtime.getRuntime().addShutdownHook(shutdownHook);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSampleRuns() {
|
||||||
|
return sampleRuns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
shutdown = true;
|
||||||
|
try {
|
||||||
|
samplerThread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Runtime.getRuntime().removeShutdownHook(shutdownHook);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
//ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
File output = new File(System.getProperty("user.home"), "samples.dot");
|
||||||
|
try {
|
||||||
|
OutputStreamWriter writer = new FileWriter(output);
|
||||||
|
toDot(writer);
|
||||||
|
writer.flush();
|
||||||
|
writer.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void run() {
|
||||||
|
long lastTime = System.nanoTime();
|
||||||
|
while(!shutdown) {
|
||||||
|
sample();
|
||||||
|
|
||||||
|
long currentTime = System.nanoTime();
|
||||||
|
LockSupport.parkNanos(SAMPLING_SPEED - currentTime + lastTime);
|
||||||
|
lastTime = currentTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sample() {
|
||||||
|
sampleRuns++;
|
||||||
|
|
||||||
|
Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces();
|
||||||
|
Set<String> previousIds = new HashSet<>();
|
||||||
|
|
||||||
|
for(Map.Entry<Thread, StackTraceElement[]> entry : stackTraces.entrySet()) {
|
||||||
|
Thread thread = entry.getKey();
|
||||||
|
StackTraceElement[] stack = entry.getValue();
|
||||||
|
String threadName = thread.getName();
|
||||||
|
if(stack.length == 0 || threadName.equals("Sampler"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
previousIds.clear();
|
||||||
|
|
||||||
|
Thread.State state = thread.getState();
|
||||||
|
|
||||||
|
String[] ids = new String[stack.length];
|
||||||
|
for(int i = 0; i < stack.length; i++) {
|
||||||
|
StackTraceElement ste = stack[i];
|
||||||
|
String id = ste.getClassName() + "." + ste.getMethodName();
|
||||||
|
ids[i] = id;
|
||||||
|
|
||||||
|
if (waitingMethods.contains(id))
|
||||||
|
state = Thread.State.WAITING;
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace predecessor = traces.computeIfAbsent(threadName, name -> new Trace(this, threadName));
|
||||||
|
predecessor.add(state, null);
|
||||||
|
|
||||||
|
for(int i = stack.length - 1; i >= 0; i--) {
|
||||||
|
String id = ids[i];
|
||||||
|
|
||||||
|
if(!previousIds.add(id) || omittedMethods.contains(id))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Trace trace = traces.computeIfAbsent(id, id1 -> new Trace(this, id1));
|
||||||
|
trace.add(state, predecessor);
|
||||||
|
predecessor = trace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toDot(OutputStreamWriter writer) throws IOException {
|
||||||
|
writer.append("digraph {\nnode [shape=plaintext,style=\"filled\", margin=0.1];\n");
|
||||||
|
|
||||||
|
for(Trace trace : traces.values()) {
|
||||||
|
trace.toDot(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.append("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,8 +0,0 @@
|
|||||||
package de.steamwar;
|
|
||||||
|
|
||||||
public enum State {
|
|
||||||
RUNNING,
|
|
||||||
WAITING,
|
|
||||||
TIME,
|
|
||||||
IO
|
|
||||||
}
|
|
79
src/main/java/de/steamwar/Trace.java
Normale Datei
79
src/main/java/de/steamwar/Trace.java
Normale Datei
@ -0,0 +1,79 @@
|
|||||||
|
package de.steamwar;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class Trace {
|
||||||
|
private static final DecimalFormat df = new DecimalFormat("0.00");
|
||||||
|
|
||||||
|
private static int idCounter;
|
||||||
|
|
||||||
|
private final Sampler sampler;
|
||||||
|
private final int id;
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
private final int[] samples = new int[6];
|
||||||
|
private final Map<Trace, Integer> predecessors = new HashMap<>();
|
||||||
|
|
||||||
|
public Trace(Sampler sampler, String name) {
|
||||||
|
this.sampler = sampler;
|
||||||
|
id = idCounter++;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ownSampleRuns() {
|
||||||
|
return Arrays.stream(samples).sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean filtered() {
|
||||||
|
return Arrays.stream(samples).sum() < Sampler.FILTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(Thread.State state, Trace predecessor) {
|
||||||
|
samples[state.ordinal()] += 1;
|
||||||
|
if (predecessor != null)
|
||||||
|
predecessors.compute(predecessor, (pre, sample) -> sample == null ? 1 : sample + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String percentage(int value) {
|
||||||
|
return df.format(value * 100.0 / ownSampleRuns());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String totalPercentage(int value) {
|
||||||
|
return df.format(value * 100.0 / sampler.getSampleRuns());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String time(int value) {
|
||||||
|
return df.format(value * Sampler.SAMPLING_SPEED / 1e9);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toDot(OutputStreamWriter writer) throws IOException {
|
||||||
|
if (filtered())
|
||||||
|
return;
|
||||||
|
|
||||||
|
int waiting = samples[3] + samples[4] + samples[5];
|
||||||
|
int runnable = samples[1] + samples[0];
|
||||||
|
int blocked = samples[2];
|
||||||
|
int total = waiting + runnable + blocked;
|
||||||
|
|
||||||
|
int r = (255 * blocked + 192 * waiting) / total;
|
||||||
|
int g = (255 * runnable + 192 * waiting) / total;
|
||||||
|
int b = (192 * waiting) / total;
|
||||||
|
int a = (int) (255 * Math.sqrt(total / (double) sampler.getSampleRuns()));
|
||||||
|
if (a > 255)
|
||||||
|
a = 255;
|
||||||
|
|
||||||
|
writer.append(String.valueOf(id)).append(" [fillcolor=\"#").append(String.format("%02X", r)).append(String.format("%02X", g)).append(String.format("%02X", b)).append(String.format("%02X", a)).append("\",label=\"").append(name).append("\\n").append(time(ownSampleRuns())).append("s ").append(totalPercentage(ownSampleRuns())).append("%\\nR").append(percentage(runnable)).append("% B").append(percentage(blocked)).append("% W").append(percentage(waiting)).append("%\"];\n");
|
||||||
|
|
||||||
|
for (Map.Entry<Trace, Integer> entry : predecessors.entrySet()) {
|
||||||
|
if (entry.getKey().filtered() || entry.getValue() < Sampler.FILTER)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
writer.append(String.valueOf(entry.getKey().id)).append(" -> ").append(String.valueOf(id)).append(" [label=\"").append(time(entry.getValue())).append("s\\n").append(percentage(entry.getValue())).append("%\",weight=").append(String.valueOf(entry.getValue())).append("];\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren