Profiler 1.0
Dieser Commit ist enthalten in:
Ursprung
322a89dc51
Commit
230209436d
13
pom.xml
13
pom.xml
@ -5,13 +5,13 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<groupId>de.steamwar</groupId>
|
<groupId>de.steamwar</groupId>
|
||||||
<artifactId>AttachTool</artifactId>
|
<artifactId>LixfelsProfiler</artifactId>
|
||||||
<version>1.0</version>
|
<version>1.0</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<file.encoding>UTF-8</file.encoding>
|
<file.encoding>UTF-8</file.encoding>
|
||||||
<maven.compiler.source>16</maven.compiler.source>
|
<maven.compiler.source>11</maven.compiler.source>
|
||||||
<maven.compiler.target>16</maven.compiler.target>
|
<maven.compiler.target>11</maven.compiler.target>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@ -24,11 +24,16 @@
|
|||||||
<archive>
|
<archive>
|
||||||
<manifest>
|
<manifest>
|
||||||
<addClasspath>true</addClasspath>
|
<addClasspath>true</addClasspath>
|
||||||
<mainClass>de.steamwar.AttachTool</mainClass>
|
<mainClass>de.steamwar.Main</mainClass>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
<manifestEntries>
|
||||||
|
<Agent-Class>de.steamwar.Agent</Agent-Class>
|
||||||
|
<Premain-Class>de.steamwar.Agent</Premain-Class>
|
||||||
|
</manifestEntries>
|
||||||
</archive>
|
</archive>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
|
<finalName>LixfelsProfiler</finalName>
|
||||||
</build>
|
</build>
|
||||||
</project>
|
</project>
|
29
src/main/java/de/steamwar/Agent.java
Normale Datei
29
src/main/java/de/steamwar/Agent.java
Normale Datei
@ -0,0 +1,29 @@
|
|||||||
|
package de.steamwar;
|
||||||
|
|
||||||
|
import java.lang.instrument.Instrumentation;
|
||||||
|
|
||||||
|
public class Agent {
|
||||||
|
private Agent() {}
|
||||||
|
|
||||||
|
private static Sampler sampler;
|
||||||
|
|
||||||
|
public static void premain(String args, Instrumentation inst) {
|
||||||
|
if ("start".equals(args)) {
|
||||||
|
sampler = new Sampler(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void agentmain(String args, Instrumentation inst) {
|
||||||
|
switch (args) {
|
||||||
|
case "start":
|
||||||
|
if(sampler == null)
|
||||||
|
sampler = new Sampler(false);
|
||||||
|
break;
|
||||||
|
case "stop":
|
||||||
|
if(sampler != null)
|
||||||
|
sampler.stop();
|
||||||
|
sampler = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,39 +0,0 @@
|
|||||||
package de.steamwar;
|
|
||||||
|
|
||||||
import com.sun.tools.attach.AttachNotSupportedException;
|
|
||||||
import com.sun.tools.attach.VirtualMachine;
|
|
||||||
import com.sun.tools.attach.VirtualMachineDescriptor;
|
|
||||||
import com.sun.tools.attach.spi.AttachProvider;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
public class AttachTool {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
if(args.length == 0) {
|
|
||||||
for(AttachProvider provider : AttachProvider.providers()) {
|
|
||||||
for(VirtualMachineDescriptor vmd : provider.listVirtualMachines()) {
|
|
||||||
System.out.println(vmd.id() + " " + vmd.displayName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else if(args.length == 1) {
|
|
||||||
try {
|
|
||||||
VirtualMachine vm = VirtualMachine.attach(args[0]);
|
|
||||||
|
|
||||||
// start management agent
|
|
||||||
Properties props = new Properties();
|
|
||||||
props.put("com.sun.management.jmxremote.port", "1999");
|
|
||||||
props.put("com.sun.management.jmxremote.authenticate", "false");
|
|
||||||
props.put("com.sun.management.jmxremote.ssl", "false");
|
|
||||||
vm.startManagementAgent(props);
|
|
||||||
|
|
||||||
vm.detach();
|
|
||||||
} catch (AttachNotSupportedException | IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
51
src/main/java/de/steamwar/Main.java
Normale Datei
51
src/main/java/de/steamwar/Main.java
Normale Datei
@ -0,0 +1,51 @@
|
|||||||
|
package de.steamwar;
|
||||||
|
|
||||||
|
import com.sun.tools.attach.*;
|
||||||
|
import com.sun.tools.attach.spi.AttachProvider;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
|
||||||
|
public static final File jarPath;
|
||||||
|
|
||||||
|
static {
|
||||||
|
File jarPath1;
|
||||||
|
try {
|
||||||
|
jarPath1 = new File(Main.class.getProtectionDomain().getCodeSource().getLocation().toURI());
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
jarPath1 = null;
|
||||||
|
}
|
||||||
|
jarPath = jarPath1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
if(args.length == 0) {
|
||||||
|
for(AttachProvider provider : AttachProvider.providers()) {
|
||||||
|
for(VirtualMachineDescriptor vmd : provider.listVirtualMachines()) {
|
||||||
|
System.out.println(vmd.id() + " " + vmd.displayName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else if(args.length == 1) {
|
||||||
|
try {
|
||||||
|
VirtualMachine vm = VirtualMachine.attach(args[0]);
|
||||||
|
|
||||||
|
vm.loadAgent(jarPath.getAbsolutePath(), "start");
|
||||||
|
|
||||||
|
System.out.println("Press keyboard to stop sampling...");
|
||||||
|
System.in.read();
|
||||||
|
|
||||||
|
vm.loadAgent(jarPath.getAbsolutePath(), "stop");
|
||||||
|
|
||||||
|
vm.detach();
|
||||||
|
|
||||||
|
new ProcessBuilder("xdot", "samples.dot").directory(jarPath.getParentFile()).start();
|
||||||
|
} catch (AttachNotSupportedException | IOException | AgentLoadException | AgentInitializationException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
134
src/main/java/de/steamwar/Sampler.java
Normale Datei
134
src/main/java/de/steamwar/Sampler.java
Normale Datei
@ -0,0 +1,134 @@
|
|||||||
|
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 double FILTER = 0.005;
|
||||||
|
|
||||||
|
private static final Set<String> waitingMethods = new HashSet<>();
|
||||||
|
static {
|
||||||
|
waitingMethods.add("io.netty.channel.epoll.Native.epollWait");
|
||||||
|
waitingMethods.add("org.jline.utils.NonBlockingInputStream.read");
|
||||||
|
waitingMethods.add("org.bukkit.craftbukkit.libs.jline.internal.NonBlockingInputStream.read");
|
||||||
|
waitingMethods.add("org.bukkit.craftbukkit.v1_15_R1.util.TerminalConsoleWriterThread.run");
|
||||||
|
waitingMethods.add("sun.nio.ch.SocketDispatcher.read0");
|
||||||
|
waitingMethods.add("openj9.internal.tools.attach.target.IPC.waitSemaphore");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final List<String> omittedMethods = new ArrayList<>();
|
||||||
|
static {
|
||||||
|
omittedMethods.add("java.lang.reflect.Method.invoke");
|
||||||
|
omittedMethods.add("java.lang.Thread.run");
|
||||||
|
omittedMethods.add("jdk.internal");
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<String, Trace> traces = new HashMap<>();
|
||||||
|
private final Thread samplerThread;
|
||||||
|
private final boolean showDot;
|
||||||
|
private boolean shutdown = false;
|
||||||
|
|
||||||
|
private int sampleRuns;
|
||||||
|
|
||||||
|
public Sampler(boolean showDot) {
|
||||||
|
this.showDot = showDot;
|
||||||
|
samplerThread = new Thread(this::run, "Sampler");
|
||||||
|
samplerThread.setDaemon(true);
|
||||||
|
samplerThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSampleRuns() {
|
||||||
|
return sampleRuns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
shutdown = true;
|
||||||
|
try {
|
||||||
|
samplerThread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
File output = new File(Main.jarPath.getParentFile(), "samples.dot");
|
||||||
|
try {
|
||||||
|
OutputStreamWriter writer = new FileWriter(output);
|
||||||
|
toDot(writer);
|
||||||
|
writer.flush();
|
||||||
|
writer.close();
|
||||||
|
if(showDot)
|
||||||
|
new ProcessBuilder("xdot", "samples.dot").directory(Main.jarPath.getParentFile()).start();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void run() {
|
||||||
|
Thread autoStopper = new Thread(this::stop, "SamplerStop");
|
||||||
|
Runtime.getRuntime().addShutdownHook(autoStopper);
|
||||||
|
long lastTime = System.nanoTime();
|
||||||
|
while(!shutdown) {
|
||||||
|
sample();
|
||||||
|
|
||||||
|
long currentTime = System.nanoTime();
|
||||||
|
LockSupport.parkNanos(SAMPLING_SPEED - currentTime + lastTime);
|
||||||
|
lastTime = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Runtime.getRuntime().removeShutdownHook(autoStopper);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sample() {
|
||||||
|
sampleRuns++;
|
||||||
|
|
||||||
|
Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces();
|
||||||
|
List<StackTraceElement> filteredStack = new LinkedList<>();
|
||||||
|
|
||||||
|
for(Map.Entry<Thread, StackTraceElement[]> entry : stackTraces.entrySet()) {
|
||||||
|
Thread thread = entry.getKey();
|
||||||
|
StackTraceElement[] stack = entry.getValue();
|
||||||
|
if(stack.length == 0 || thread.getName().equals("Sampler"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Thread.State state = thread.getState();
|
||||||
|
|
||||||
|
filteredStack.clear();
|
||||||
|
for(StackTraceElement ste : stack) {
|
||||||
|
String id = ste.getClassName() + "." + ste.getMethodName();
|
||||||
|
if (waitingMethods.contains(id))
|
||||||
|
state = Thread.State.WAITING;
|
||||||
|
if(omittedMethods.stream().noneMatch(id::startsWith))
|
||||||
|
filteredStack.add(0, ste);
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace predecessor = traces.computeIfAbsent(thread.getName(), name -> new Trace(this, thread));
|
||||||
|
predecessor.add(state, null);
|
||||||
|
|
||||||
|
for(StackTraceElement ste : filteredStack) {
|
||||||
|
Trace trace = traces.computeIfAbsent(ste.getClassName() + "." + ste.getMethodName() + ":" + ste.getLineNumber(), id -> new Trace(this, ste));
|
||||||
|
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("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
81
src/main/java/de/steamwar/Trace.java
Normale Datei
81
src/main/java/de/steamwar/Trace.java
Normale Datei
@ -0,0 +1,81 @@
|
|||||||
|
package de.steamwar;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
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 Map<Thread.State, Integer> samples = new EnumMap<>(Thread.State.class);
|
||||||
|
private final Map<Trace, Integer> predecessors = new HashMap<>();
|
||||||
|
|
||||||
|
public Trace(Sampler sampler, StackTraceElement ste) {
|
||||||
|
this.sampler = sampler;
|
||||||
|
id = idCounter++;
|
||||||
|
name = ste.getClassName() + "." + ste.getMethodName() + ":" + ste.getLineNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Trace(Sampler sampler, Thread thread) {
|
||||||
|
this(sampler, thread.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Trace(Sampler sampler, String name) {
|
||||||
|
this.sampler = sampler;
|
||||||
|
id = idCounter++;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ownSampleRuns() {
|
||||||
|
return samples.values().stream().mapToInt(i -> i).sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean filtered() {
|
||||||
|
return (samples.getOrDefault(Thread.State.RUNNABLE, 0) + samples.getOrDefault(Thread.State.BLOCKED, 0)) / (double) sampler.getSampleRuns() < Sampler.FILTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(Thread.State state, Trace predecessor) {
|
||||||
|
samples.compute(state, (s, sample) -> sample == null ? 1 : sample + 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 / sampler.getSampleRuns());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toDot(OutputStreamWriter writer) throws IOException {
|
||||||
|
if (filtered())
|
||||||
|
return;
|
||||||
|
|
||||||
|
int waiting = samples.getOrDefault(Thread.State.WAITING, 0) + samples.getOrDefault(Thread.State.TIMED_WAITING, 0) + samples.getOrDefault(Thread.State.TERMINATED, 0);
|
||||||
|
int runnable = samples.getOrDefault(Thread.State.RUNNABLE, 0) + samples.getOrDefault(Thread.State.NEW, 0);
|
||||||
|
int blocked = samples.getOrDefault(Thread.State.BLOCKED, 0);
|
||||||
|
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(percentage(ownSampleRuns())).append("% ").append(df.format(ownSampleRuns() * Sampler.SAMPLING_SPEED / 1e9)).append("s\\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() / (double)sampler.getSampleRuns() < Sampler.FILTER)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
writer.append(String.valueOf(entry.getKey().id)).append(" -> ").append(String.valueOf(id)).append(" [label=\"").append(percentage(entry.getValue())).append("%\"];\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren