12
0
Dieser Commit ist enthalten in:
Lixfel 2021-12-05 21:21:05 +01:00
Ursprung 322a89dc51
Commit 230209436d
6 geänderte Dateien mit 304 neuen und 43 gelöschten Zeilen

13
pom.xml
Datei anzeigen

@ -5,13 +5,13 @@
<modelVersion>4.0.0</modelVersion>
<groupId>de.steamwar</groupId>
<artifactId>AttachTool</artifactId>
<artifactId>LixfelsProfiler</artifactId>
<version>1.0</version>
<properties>
<file.encoding>UTF-8</file.encoding>
<maven.compiler.source>16</maven.compiler.source>
<maven.compiler.target>16</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<build>
@ -24,11 +24,16 @@
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>de.steamwar.AttachTool</mainClass>
<mainClass>de.steamwar.Main</mainClass>
</manifest>
<manifestEntries>
<Agent-Class>de.steamwar.Agent</Agent-Class>
<Premain-Class>de.steamwar.Agent</Premain-Class>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
<finalName>LixfelsProfiler</finalName>
</build>
</project>

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -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("}");
}
}

Datei anzeigen

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