aboutsummaryrefslogtreecommitdiff
path: root/spark-common/src/main/java/me/lucko/spark/common/command
diff options
context:
space:
mode:
Diffstat (limited to 'spark-common/src/main/java/me/lucko/spark/common/command')
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/command/Arguments.java14
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/command/Command.java1
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/command/CommandResponseHandler.java10
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/command/modules/ActivityLogModule.java16
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/command/modules/GcMonitoringModule.java36
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java510
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java292
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java365
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/command/modules/TickMonitoringModule.java21
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/command/sender/CommandSender.java2
10 files changed, 673 insertions, 594 deletions
diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/Arguments.java b/spark-common/src/main/java/me/lucko/spark/common/command/Arguments.java
index 3cd0365..17c49e2 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/command/Arguments.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/command/Arguments.java
@@ -30,6 +30,9 @@ import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+/**
+ * Utility for parsing command-flag like arguments from raw space split strings.
+ */
public class Arguments {
private static final Pattern FLAG_REGEX = Pattern.compile("^--(.+)$");
@@ -110,19 +113,8 @@ public class Arguments {
}
public static final class ParseException extends IllegalArgumentException {
- public ParseException() {
- }
-
public ParseException(String s) {
super(s);
}
-
- public ParseException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public ParseException(Throwable cause) {
- super(cause);
- }
}
}
diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/Command.java b/spark-common/src/main/java/me/lucko/spark/common/command/Command.java
index e1a5146..dad15e6 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/command/Command.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/command/Command.java
@@ -21,6 +21,7 @@
package me.lucko.spark.common.command;
import com.google.common.collect.ImmutableList;
+
import me.lucko.spark.common.SparkPlatform;
import me.lucko.spark.common.command.sender.CommandSender;
diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/CommandResponseHandler.java b/spark-common/src/main/java/me/lucko/spark/common/command/CommandResponseHandler.java
index 874939e..1acb3dc 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/command/CommandResponseHandler.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/command/CommandResponseHandler.java
@@ -22,6 +22,7 @@ package me.lucko.spark.common.command;
import me.lucko.spark.common.SparkPlatform;
import me.lucko.spark.common.command.sender.CommandSender;
+
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
@@ -29,9 +30,11 @@ import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
-import static net.kyori.adventure.text.Component.*;
-import static net.kyori.adventure.text.format.NamedTextColor.*;
-import static net.kyori.adventure.text.format.TextDecoration.*;
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.DARK_GRAY;
+import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
+import static net.kyori.adventure.text.format.NamedTextColor.YELLOW;
+import static net.kyori.adventure.text.format.TextDecoration.BOLD;
public class CommandResponseHandler {
@@ -94,5 +97,4 @@ public class CommandResponseHandler {
return PREFIX.append(message);
}
-
}
diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/ActivityLogModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/ActivityLogModule.java
index ae4613a..2bdb5d6 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/ActivityLogModule.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/ActivityLogModule.java
@@ -20,10 +20,11 @@
package me.lucko.spark.common.command.modules;
-import me.lucko.spark.common.activitylog.ActivityLog.Activity;
+import me.lucko.spark.common.activitylog.Activity;
import me.lucko.spark.common.command.Command;
import me.lucko.spark.common.command.CommandModule;
import me.lucko.spark.common.command.tabcomplete.TabCompleter;
+
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.ClickEvent;
@@ -36,10 +37,15 @@ import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
-import static me.lucko.spark.common.command.CommandResponseHandler.*;
-import static net.kyori.adventure.text.Component.*;
-import static net.kyori.adventure.text.format.NamedTextColor.*;
-import static net.kyori.adventure.text.format.TextDecoration.*;
+import static me.lucko.spark.common.command.CommandResponseHandler.applyPrefix;
+import static net.kyori.adventure.text.Component.space;
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.DARK_GRAY;
+import static net.kyori.adventure.text.format.NamedTextColor.GOLD;
+import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
+import static net.kyori.adventure.text.format.NamedTextColor.WHITE;
+import static net.kyori.adventure.text.format.NamedTextColor.YELLOW;
+import static net.kyori.adventure.text.format.TextDecoration.BOLD;
public class ActivityLogModule implements CommandModule, RowRenderer<Activity> {
diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/GcMonitoringModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/GcMonitoringModule.java
index d66a181..8e2d199 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/GcMonitoringModule.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/GcMonitoringModule.java
@@ -21,6 +21,7 @@
package me.lucko.spark.common.command.modules;
import com.sun.management.GarbageCollectionNotificationInfo;
+
import me.lucko.spark.common.SparkPlatform;
import me.lucko.spark.common.command.Command;
import me.lucko.spark.common.command.CommandModule;
@@ -28,6 +29,7 @@ import me.lucko.spark.common.command.CommandResponseHandler;
import me.lucko.spark.common.monitor.memory.GarbageCollectionMonitor;
import me.lucko.spark.common.monitor.memory.GarbageCollectorStatistics;
import me.lucko.spark.common.util.FormatUtil;
+
import net.kyori.adventure.text.Component;
import java.lang.management.MemoryUsage;
@@ -37,12 +39,18 @@ import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
-import static net.kyori.adventure.text.Component.*;
-import static net.kyori.adventure.text.format.NamedTextColor.*;
-import static net.kyori.adventure.text.format.TextDecoration.*;
+import static net.kyori.adventure.text.Component.empty;
+import static net.kyori.adventure.text.Component.space;
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.DARK_GRAY;
+import static net.kyori.adventure.text.format.NamedTextColor.GOLD;
+import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
+import static net.kyori.adventure.text.format.NamedTextColor.WHITE;
+import static net.kyori.adventure.text.format.TextDecoration.BOLD;
public class GcMonitoringModule implements CommandModule {
- private static final DecimalFormat df = new DecimalFormat("#.##");
+ private static final DecimalFormat DF = new DecimalFormat("#.##");
/** The gc monitoring instance currently running, if any */
private ReportingGcMonitor activeGcMonitor = null;
@@ -106,7 +114,7 @@ public class GcMonitoringModule implements CommandModule {
);
report.add(text()
.content(" ")
- .append(text(df.format(averageCollectionTime), GOLD))
+ .append(text(DF.format(averageCollectionTime), GOLD))
.append(text(" ms avg", GRAY))
.append(text(", ", DARK_GRAY))
.append(text(collectionCount, WHITE))
@@ -147,11 +155,10 @@ public class GcMonitoringModule implements CommandModule {
private static String formatTime(long millis) {
if (millis <= 0) {
- return "0ms";
+ return "0s";
}
long second = millis / 1000;
- //millis = millis % 1000;
long minute = second / 60;
second = second % 60;
@@ -162,9 +169,6 @@ public class GcMonitoringModule implements CommandModule {
if (second != 0) {
sb.append(second).append("s ");
}
- //if (millis != 0) {
- // sb.append(millis).append("ms");
- //}
return sb.toString().trim();
}
@@ -185,15 +189,7 @@ public class GcMonitoringModule implements CommandModule {
@Override
public void onGc(GarbageCollectionNotificationInfo data) {
- String gcType;
- if (data.getGcAction().equals("end of minor GC")) {
- gcType = "Young Gen";
- } else if (data.getGcAction().equals("end of major GC")) {
- gcType = "Old Gen";
- } else {
- gcType = data.getGcAction();
- }
-
+ String gcType = GarbageCollectionMonitor.getGcType(data);
String gcCause = data.getGcCause() != null ? " (cause = " + data.getGcCause() + ")" : "";
Map<String, MemoryUsage> beforeUsages = data.getGcInfo().getMemoryUsageBeforeGc();
@@ -207,7 +203,7 @@ public class GcMonitoringModule implements CommandModule {
.append(text(gcType + " "))
.append(text("GC", RED))
.append(text(" lasting "))
- .append(text(df.format(data.getGcInfo().getDuration()), GOLD))
+ .append(text(DF.format(data.getGcInfo().getDuration()), GOLD))
.append(text(" ms." + gcCause))
.build()
));
diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java
index 409eb38..c8f25c7 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java
@@ -21,13 +21,19 @@
package me.lucko.spark.common.command.modules;
import com.google.common.base.Strings;
+
+import me.lucko.spark.common.SparkPlatform;
+import me.lucko.spark.common.command.Arguments;
import me.lucko.spark.common.command.Command;
import me.lucko.spark.common.command.CommandModule;
+import me.lucko.spark.common.command.CommandResponseHandler;
+import me.lucko.spark.common.command.sender.CommandSender;
import me.lucko.spark.common.command.tabcomplete.TabCompleter;
import me.lucko.spark.common.monitor.cpu.CpuMonitor;
import me.lucko.spark.common.monitor.tick.TickStatistics;
import me.lucko.spark.common.util.FormatUtil;
import me.lucko.spark.common.util.RollingAverage;
+
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.TextColor;
@@ -45,9 +51,17 @@ import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
-import static net.kyori.adventure.text.Component.*;
-import static net.kyori.adventure.text.format.NamedTextColor.*;
-import static net.kyori.adventure.text.format.TextDecoration.*;
+import static net.kyori.adventure.text.Component.empty;
+import static net.kyori.adventure.text.Component.space;
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.DARK_GRAY;
+import static net.kyori.adventure.text.format.NamedTextColor.GOLD;
+import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
+import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
+import static net.kyori.adventure.text.format.NamedTextColor.WHITE;
+import static net.kyori.adventure.text.format.NamedTextColor.YELLOW;
+import static net.kyori.adventure.text.format.TextDecoration.BOLD;
public class HealthModule implements CommandModule {
@@ -57,51 +71,7 @@ public class HealthModule implements CommandModule {
public void registerCommands(Consumer<Command> consumer) {
consumer.accept(Command.builder()
.aliases("tps", "cpu")
- .executor((platform, sender, resp, arguments) -> {
- TickStatistics tickStatistics = platform.getTickStatistics();
- if (tickStatistics != null) {
- resp.replyPrefixed(text("TPS from last 5s, 10s, 1m, 5m, 15m:"));
- resp.replyPrefixed(text()
- .content(" ")
- .append(formatTps(tickStatistics.tps5Sec())).append(text(", "))
- .append(formatTps(tickStatistics.tps10Sec())).append(text(", "))
- .append(formatTps(tickStatistics.tps1Min())).append(text(", "))
- .append(formatTps(tickStatistics.tps5Min())).append(text(", "))
- .append(formatTps(tickStatistics.tps15Min()))
- .build()
- );
- resp.replyPrefixed(empty());
-
- if (tickStatistics.isDurationSupported()) {
- resp.replyPrefixed(text("Tick durations (min/med/95%ile/max ms) from last 10s, 1m:"));
- resp.replyPrefixed(text()
- .content(" ")
- .append(formatTickDurations(tickStatistics.duration10Sec())).append(text("; "))
- .append(formatTickDurations(tickStatistics.duration1Min()))
- .build()
- );
- resp.replyPrefixed(empty());
- }
- }
-
- resp.replyPrefixed(text("CPU usage from last 10s, 1m, 15m:"));
- resp.replyPrefixed(text()
- .content(" ")
- .append(formatCpuUsage(CpuMonitor.systemLoad10SecAvg())).append(text(", "))
- .append(formatCpuUsage(CpuMonitor.systemLoad1MinAvg())).append(text(", "))
- .append(formatCpuUsage(CpuMonitor.systemLoad15MinAvg()))
- .append(text(" (system)", DARK_GRAY))
- .build()
- );
- resp.replyPrefixed(text()
- .content(" ")
- .append(formatCpuUsage(CpuMonitor.processLoad10SecAvg())).append(text(", "))
- .append(formatCpuUsage(CpuMonitor.processLoad1MinAvg())).append(text(", "))
- .append(formatCpuUsage(CpuMonitor.processLoad15MinAvg()))
- .append(text(" (process)", DARK_GRAY))
- .build()
- );
- })
+ .executor(HealthModule::tps)
.tabCompleter(Command.TabCompleter.empty())
.build()
);
@@ -109,197 +79,265 @@ public class HealthModule implements CommandModule {
consumer.accept(Command.builder()
.aliases("healthreport", "health", "ht")
.argumentUsage("memory", null)
- .executor((platform, sender, resp, arguments) -> {
- resp.replyPrefixed(text("Generating server health report..."));
- platform.getPlugin().executeAsync(() -> {
- List<Component> report = new LinkedList<>();
- report.add(empty());
-
- TickStatistics tickStatistics = platform.getTickStatistics();
- if (tickStatistics != null) {
- report.add(text()
- .append(text(">", DARK_GRAY, BOLD))
- .append(space())
- .append(text("TPS from last 5s, 10s, 1m, 5m, 15m:", GOLD))
- .build()
- );
- report.add(text()
- .content(" ")
- .append(formatTps(tickStatistics.tps5Sec())).append(text(", "))
- .append(formatTps(tickStatistics.tps10Sec())).append(text(", "))
- .append(formatTps(tickStatistics.tps1Min())).append(text(", "))
- .append(formatTps(tickStatistics.tps5Min())).append(text(", "))
- .append(formatTps(tickStatistics.tps15Min()))
- .build()
- );
- report.add(empty());
-
- if (tickStatistics.isDurationSupported()) {
- report.add(text()
- .append(text(">", DARK_GRAY, BOLD))
- .append(space())
- .append(text("Tick durations (min/med/95%ile/max ms) from last 10s, 1m:", GOLD))
- .build()
- );
- report.add(text()
- .content(" ")
- .append(formatTickDurations(tickStatistics.duration10Sec())).append(text("; "))
- .append(formatTickDurations(tickStatistics.duration1Min()))
- .build()
- );
- report.add(empty());
- }
- }
-
- report.add(text()
- .append(text(">", DARK_GRAY, BOLD))
- .append(space())
- .append(text("CPU usage from last 10s, 1m, 15m:", GOLD))
- .build()
- );
- report.add(text()
- .content(" ")
- .append(formatCpuUsage(CpuMonitor.systemLoad10SecAvg())).append(text(", "))
- .append(formatCpuUsage(CpuMonitor.systemLoad1MinAvg())).append(text(", "))
- .append(formatCpuUsage(CpuMonitor.systemLoad15MinAvg()))
- .append(text(" (system)", DARK_GRAY))
- .build()
- );
- report.add(text()
- .content(" ")
- .append(formatCpuUsage(CpuMonitor.processLoad10SecAvg())).append(text(", "))
- .append(formatCpuUsage(CpuMonitor.processLoad1MinAvg())).append(text(", "))
- .append(formatCpuUsage(CpuMonitor.processLoad15MinAvg()))
- .append(text(" (process)", DARK_GRAY))
- .build()
- );
- report.add(empty());
-
- MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
- MemoryUsage heapUsage = memoryMXBean.getHeapMemoryUsage();
- report.add(text()
- .append(text(">", DARK_GRAY, BOLD))
- .append(space())
- .append(text("Memory usage:", GOLD))
- .build()
- );
- report.add(text()
- .content(" ")
- .append(text(FormatUtil.formatBytes(heapUsage.getUsed()), WHITE))
- .append(space())
- .append(text("/", GRAY))
- .append(space())
- .append(text(FormatUtil.formatBytes(heapUsage.getMax()), WHITE))
- .append(text(" "))
- .append(text("(", GRAY))
- .append(text(FormatUtil.percent(heapUsage.getUsed(), heapUsage.getMax()), GREEN))
- .append(text(")", GRAY))
- .build()
- );
- report.add(text().content(" ").append(generateMemoryUsageDiagram(heapUsage, 40)).build());
- report.add(empty());
-
- if (arguments.boolFlag("memory")) {
- MemoryUsage nonHeapUsage = memoryMXBean.getNonHeapMemoryUsage();
- report.add(text()
- .append(text(">", DARK_GRAY, BOLD))
- .append(space())
- .append(text("Non-heap memory usage:", GOLD))
- .build()
- );
- report.add(text()
- .content(" ")
- .append(text(FormatUtil.formatBytes(nonHeapUsage.getUsed()), WHITE))
- .build()
- );
- report.add(empty());
-
- List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();
- for (MemoryPoolMXBean memoryPool : memoryPoolMXBeans) {
- if (memoryPool.getType() != MemoryType.HEAP) {
- continue;
- }
-
- MemoryUsage usage = memoryPool.getUsage();
- MemoryUsage collectionUsage = memoryPool.getCollectionUsage();
-
- if (usage.getMax() == -1) {
- usage = new MemoryUsage(usage.getInit(), usage.getUsed(), usage.getCommitted(), usage.getCommitted());
- }
-
- report.add(text()
- .append(text(">", DARK_GRAY, BOLD))
- .append(space())
- .append(text(memoryPool.getName() + " pool usage:", GOLD))
- .build()
- );
- report.add(text()
- .content(" ")
- .append(text(FormatUtil.formatBytes(usage.getUsed()), WHITE))
- .append(space())
- .append(text("/", GRAY))
- .append(space())
- .append(text(FormatUtil.formatBytes(usage.getMax()), WHITE))
- .append(text(" "))
- .append(text("(", GRAY))
- .append(text(FormatUtil.percent(usage.getUsed(), usage.getMax()), GREEN))
- .append(text(")", GRAY))
- .build()
- );
- report.add(text().content(" ").append(generateMemoryPoolDiagram(usage, collectionUsage, 40)).build());
-
- if (collectionUsage != null) {
- report.add(text()
- .content(" ")
- .append(text("-", RED))
- .append(space())
- .append(text("Usage at last GC:", GRAY))
- .append(space())
- .append(text(FormatUtil.formatBytes(collectionUsage.getUsed()), WHITE))
- .build()
- );
- }
- report.add(empty());
- }
- }
-
- try {
- FileStore fileStore = Files.getFileStore(Paths.get("."));
- long totalSpace = fileStore.getTotalSpace();
- long usedSpace = totalSpace - fileStore.getUsableSpace();
- report.add(text()
- .append(text(">", DARK_GRAY, BOLD))
- .append(space())
- .append(text("Disk usage:", GOLD))
- .build()
- );
- report.add(text()
- .content(" ")
- .append(text(FormatUtil.formatBytes(usedSpace), WHITE))
- .append(space())
- .append(text("/", GRAY))
- .append(space())
- .append(text(FormatUtil.formatBytes(totalSpace), WHITE))
- .append(text(" "))
- .append(text("(", GRAY))
- .append(text(FormatUtil.percent(usedSpace, totalSpace), GREEN))
- .append(text(")", GRAY))
- .build()
- );
- report.add(text().content(" ").append(generateDiskUsageDiagram(usedSpace, totalSpace, 40)).build());
- report.add(empty());
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- report.forEach(resp::reply);
- });
- })
+ .executor(HealthModule::healthReport)
.tabCompleter((platform, sender, arguments) -> TabCompleter.completeForOpts(arguments, "--memory"))
.build()
);
}
+ private static void tps(SparkPlatform platform, CommandSender sender, CommandResponseHandler resp, Arguments arguments) {
+ TickStatistics tickStatistics = platform.getTickStatistics();
+ if (tickStatistics != null) {
+ resp.replyPrefixed(text("TPS from last 5s, 10s, 1m, 5m, 15m:"));
+ resp.replyPrefixed(text()
+ .content(" ")
+ .append(formatTps(tickStatistics.tps5Sec())).append(text(", "))
+ .append(formatTps(tickStatistics.tps10Sec())).append(text(", "))
+ .append(formatTps(tickStatistics.tps1Min())).append(text(", "))
+ .append(formatTps(tickStatistics.tps5Min())).append(text(", "))
+ .append(formatTps(tickStatistics.tps15Min()))
+ .build()
+ );
+ resp.replyPrefixed(empty());
+
+ if (tickStatistics.isDurationSupported()) {
+ resp.replyPrefixed(text("Tick durations (min/med/95%ile/max ms) from last 10s, 1m:"));
+ resp.replyPrefixed(text()
+ .content(" ")
+ .append(formatTickDurations(tickStatistics.duration10Sec())).append(text("; "))
+ .append(formatTickDurations(tickStatistics.duration1Min()))
+ .build()
+ );
+ resp.replyPrefixed(empty());
+ }
+ }
+
+ resp.replyPrefixed(text("CPU usage from last 10s, 1m, 15m:"));
+ resp.replyPrefixed(text()
+ .content(" ")
+ .append(formatCpuUsage(CpuMonitor.systemLoad10SecAvg())).append(text(", "))
+ .append(formatCpuUsage(CpuMonitor.systemLoad1MinAvg())).append(text(", "))
+ .append(formatCpuUsage(CpuMonitor.systemLoad15MinAvg()))
+ .append(text(" (system)", DARK_GRAY))
+ .build()
+ );
+ resp.replyPrefixed(text()
+ .content(" ")
+ .append(formatCpuUsage(CpuMonitor.processLoad10SecAvg())).append(text(", "))
+ .append(formatCpuUsage(CpuMonitor.processLoad1MinAvg())).append(text(", "))
+ .append(formatCpuUsage(CpuMonitor.processLoad15MinAvg()))
+ .append(text(" (process)", DARK_GRAY))
+ .build()
+ );
+ }
+
+ private static void healthReport(SparkPlatform platform, CommandSender sender, CommandResponseHandler resp, Arguments arguments) {
+ resp.replyPrefixed(text("Generating server health report..."));
+ platform.getPlugin().executeAsync(() -> {
+ List<Component> report = new LinkedList<>();
+ report.add(empty());
+
+ TickStatistics tickStatistics = platform.getTickStatistics();
+ if (tickStatistics != null) {
+ addTickStats(report, tickStatistics);
+ }
+
+ addCpuStats(report);
+
+ MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
+ addBasicMemoryStats(report, memoryMXBean);
+
+ if (arguments.boolFlag("memory")) {
+ addDetailedMemoryStats(report, memoryMXBean);
+ }
+
+ try {
+ addDiskStats(report);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ report.forEach(resp::reply);
+ });
+ }
+
+ private static void addTickStats(List<Component> report, TickStatistics tickStatistics) {
+ report.add(text()
+ .append(text(">", DARK_GRAY, BOLD))
+ .append(space())
+ .append(text("TPS from last 5s, 10s, 1m, 5m, 15m:", GOLD))
+ .build()
+ );
+ report.add(text()
+ .content(" ")
+ .append(formatTps(tickStatistics.tps5Sec())).append(text(", "))
+ .append(formatTps(tickStatistics.tps10Sec())).append(text(", "))
+ .append(formatTps(tickStatistics.tps1Min())).append(text(", "))
+ .append(formatTps(tickStatistics.tps5Min())).append(text(", "))
+ .append(formatTps(tickStatistics.tps15Min()))
+ .build()
+ );
+ report.add(empty());
+
+ if (tickStatistics.isDurationSupported()) {
+ report.add(text()
+ .append(text(">", DARK_GRAY, BOLD))
+ .append(space())
+ .append(text("Tick durations (min/med/95%ile/max ms) from last 10s, 1m:", GOLD))
+ .build()
+ );
+ report.add(text()
+ .content(" ")
+ .append(formatTickDurations(tickStatistics.duration10Sec())).append(text("; "))
+ .append(formatTickDurations(tickStatistics.duration1Min()))
+ .build()
+ );
+ report.add(empty());
+ }
+ }
+
+ private static void addCpuStats(List<Component> report) {
+ report.add(text()
+ .append(text(">", DARK_GRAY, BOLD))
+ .append(space())
+ .append(text("CPU usage from last 10s, 1m, 15m:", GOLD))
+ .build()
+ );
+ report.add(text()
+ .content(" ")
+ .append(formatCpuUsage(CpuMonitor.systemLoad10SecAvg())).append(text(", "))
+ .append(formatCpuUsage(CpuMonitor.systemLoad1MinAvg())).append(text(", "))
+ .append(formatCpuUsage(CpuMonitor.systemLoad15MinAvg()))
+ .append(text(" (system)", DARK_GRAY))
+ .build()
+ );
+ report.add(text()
+ .content(" ")
+ .append(formatCpuUsage(CpuMonitor.processLoad10SecAvg())).append(text(", "))
+ .append(formatCpuUsage(CpuMonitor.processLoad1MinAvg())).append(text(", "))
+ .append(formatCpuUsage(CpuMonitor.processLoad15MinAvg()))
+ .append(text(" (process)", DARK_GRAY))
+ .build()
+ );
+ report.add(empty());
+ }
+
+ private static void addBasicMemoryStats(List<Component> report, MemoryMXBean memoryMXBean) {
+ MemoryUsage heapUsage = memoryMXBean.getHeapMemoryUsage();
+ report.add(text()
+ .append(text(">", DARK_GRAY, BOLD))
+ .append(space())
+ .append(text("Memory usage:", GOLD))
+ .build()
+ );
+ report.add(text()
+ .content(" ")
+ .append(text(FormatUtil.formatBytes(heapUsage.getUsed()), WHITE))
+ .append(space())
+ .append(text("/", GRAY))
+ .append(space())
+ .append(text(FormatUtil.formatBytes(heapUsage.getMax()), WHITE))
+ .append(text(" "))
+ .append(text("(", GRAY))
+ .append(text(FormatUtil.percent(heapUsage.getUsed(), heapUsage.getMax()), GREEN))
+ .append(text(")", GRAY))
+ .build()
+ );
+ report.add(text().content(" ").append(generateMemoryUsageDiagram(heapUsage, 40)).build());
+ report.add(empty());
+ }
+
+ private static void addDetailedMemoryStats(List<Component> report, MemoryMXBean memoryMXBean) {
+ MemoryUsage nonHeapUsage = memoryMXBean.getNonHeapMemoryUsage();
+ report.add(text()
+ .append(text(">", DARK_GRAY, BOLD))
+ .append(space())
+ .append(text("Non-heap memory usage:", GOLD))
+ .build()
+ );
+ report.add(text()
+ .content(" ")
+ .append(text(FormatUtil.formatBytes(nonHeapUsage.getUsed()), WHITE))
+ .build()
+ );
+ report.add(empty());
+
+ List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();
+ for (MemoryPoolMXBean memoryPool : memoryPoolMXBeans) {
+ if (memoryPool.getType() != MemoryType.HEAP) {
+ continue;
+ }
+
+ MemoryUsage usage = memoryPool.getUsage();
+ MemoryUsage collectionUsage = memoryPool.getCollectionUsage();
+
+ if (usage.getMax() == -1) {
+ usage = new MemoryUsage(usage.getInit(), usage.getUsed(), usage.getCommitted(), usage.getCommitted());
+ }
+
+ report.add(text()
+ .append(text(">", DARK_GRAY, BOLD))
+ .append(space())
+ .append(text(memoryPool.getName() + " pool usage:", GOLD))
+ .build()
+ );
+ report.add(text()
+ .content(" ")
+ .append(text(FormatUtil.formatBytes(usage.getUsed()), WHITE))
+ .append(space())
+ .append(text("/", GRAY))
+ .append(space())
+ .append(text(FormatUtil.formatBytes(usage.getMax()), WHITE))
+ .append(text(" "))
+ .append(text("(", GRAY))
+ .append(text(FormatUtil.percent(usage.getUsed(), usage.getMax()), GREEN))
+ .append(text(")", GRAY))
+ .build()
+ );
+ report.add(text().content(" ").append(generateMemoryPoolDiagram(usage, collectionUsage, 40)).build());
+
+ if (collectionUsage != null) {
+ report.add(text()
+ .content(" ")
+ .append(text("-", RED))
+ .append(space())
+ .append(text("Usage at last GC:", GRAY))
+ .append(space())
+ .append(text(FormatUtil.formatBytes(collectionUsage.getUsed()), WHITE))
+ .build()
+ );
+ }
+ report.add(empty());
+ }
+ }
+
+ private static void addDiskStats(List<Component> report) throws IOException {
+ FileStore fileStore = Files.getFileStore(Paths.get("."));
+ long totalSpace = fileStore.getTotalSpace();
+ long usedSpace = totalSpace - fileStore.getUsableSpace();
+ report.add(text()
+ .append(text(">", DARK_GRAY, BOLD))
+ .append(space())
+ .append(text("Disk usage:", GOLD))
+ .build()
+ );
+ report.add(text()
+ .content(" ")
+ .append(text(FormatUtil.formatBytes(usedSpace), WHITE))
+ .append(space())
+ .append(text("/", GRAY))
+ .append(space())
+ .append(text(FormatUtil.formatBytes(totalSpace), WHITE))
+ .append(text(" "))
+ .append(text("(", GRAY))
+ .append(text(FormatUtil.percent(usedSpace, totalSpace), GREEN))
+ .append(text(")", GRAY))
+ .build()
+ );
+ report.add(text().content(" ").append(generateDiskUsageDiagram(usedSpace, totalSpace, 40)).build());
+ report.add(empty());
+ }
+
public static TextComponent formatTps(double tps) {
TextColor color;
if (tps > 18.0) {
@@ -313,7 +351,7 @@ public class HealthModule implements CommandModule {
return text((tps > 20.0 ? "*" : "") + Math.min(Math.round(tps * 100.0) / 100.0, 20.0), color);
}
- public static TextComponent formatTickDurations(RollingAverage average){
+ public static TextComponent formatTickDurations(RollingAverage average) {
return text()
.append(formatTickDuration(average.getMin()))
.append(text('/', GRAY))
@@ -325,7 +363,7 @@ public class HealthModule implements CommandModule {
.build();
}
- public static TextComponent formatTickDuration(double duration){
+ public static TextComponent formatTickDuration(double duration) {
TextColor color;
if (duration >= 50d) {
color = RED;
diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java
index 39cb8a3..94e44a6 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java
@@ -21,19 +21,25 @@
package me.lucko.spark.common.command.modules;
import me.lucko.spark.common.SparkPlatform;
-import me.lucko.spark.common.activitylog.ActivityLog.Activity;
+import me.lucko.spark.common.activitylog.Activity;
+import me.lucko.spark.common.command.Arguments;
import me.lucko.spark.common.command.Command;
import me.lucko.spark.common.command.CommandModule;
+import me.lucko.spark.common.command.CommandResponseHandler;
+import me.lucko.spark.common.command.sender.CommandSender;
import me.lucko.spark.common.command.tabcomplete.TabCompleter;
import me.lucko.spark.common.heapdump.HeapDump;
import me.lucko.spark.common.heapdump.HeapDumpSummary;
import me.lucko.spark.common.util.FormatUtil;
+
import net.kyori.adventure.text.event.ClickEvent;
-import okhttp3.MediaType;
+
import org.tukaani.xz.LZMA2Options;
import org.tukaani.xz.LZMAOutputStream;
import org.tukaani.xz.XZOutputStream;
+import okhttp3.MediaType;
+
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -48,8 +54,11 @@ import java.util.function.Consumer;
import java.util.function.LongConsumer;
import java.util.zip.GZIPOutputStream;
-import static net.kyori.adventure.text.Component.*;
-import static net.kyori.adventure.text.format.NamedTextColor.*;
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.GOLD;
+import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
+import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
public class HeapAnalysisModule implements CommandModule {
private static final MediaType SPARK_HEAP_MEDIA_TYPE = MediaType.parse("application/x-spark-heap");
@@ -59,44 +68,7 @@ public class HeapAnalysisModule implements CommandModule {
consumer.accept(Command.builder()
.aliases("heapsummary")
.argumentUsage("run-gc-before", null)
- .executor((platform, sender, resp, arguments) -> {
- platform.getPlugin().executeAsync(() -> {
- if (arguments.boolFlag("run-gc-before")) {
- resp.broadcastPrefixed(text("Running garbage collector..."));
- System.gc();
- }
-
- resp.broadcastPrefixed(text("Creating a new heap dump summary, please wait..."));
-
- HeapDumpSummary heapDump;
- try {
- heapDump = HeapDumpSummary.createNew();
- } catch (Exception e) {
- resp.broadcastPrefixed(text("An error occurred whilst inspecting the heap.", RED));
- e.printStackTrace();
- return;
- }
-
- byte[] output = heapDump.formCompressedDataPayload(platform.getPlugin().getPlatformInfo(), sender);
- try {
- String key = SparkPlatform.BYTEBIN_CLIENT.postContent(output, SPARK_HEAP_MEDIA_TYPE, false).key();
- String url = SparkPlatform.VIEWER_URL + key;
-
- resp.broadcastPrefixed(text("Heap dump summmary output:", GOLD));
- resp.broadcast(text()
- .content(url)
- .color(GRAY)
- .clickEvent(ClickEvent.openUrl(url))
- .build()
- );
-
- platform.getActivityLog().addToLog(Activity.urlActivity(sender, System.currentTimeMillis(), "Heap dump summary", url));
- } catch (IOException e) {
- resp.broadcastPrefixed(text("An error occurred whilst uploading the data.", RED));
- e.printStackTrace();
- }
- });
- })
+ .executor(HeapAnalysisModule::heapSummary)
.tabCompleter((platform, sender, arguments) -> TabCompleter.completeForOpts(arguments, "--run-gc-before"))
.build()
);
@@ -104,107 +76,153 @@ public class HeapAnalysisModule implements CommandModule {
consumer.accept(Command.builder()
.aliases("heapdump")
.argumentUsage("compress", "type")
- .executor((platform, sender, resp, arguments) -> {
- platform.getPlugin().executeAsync(() -> {
- Path pluginFolder = platform.getPlugin().getPluginDirectory();
- try {
- Files.createDirectories(pluginFolder);
- } catch (IOException e) {
- // ignore
- }
+ .executor(HeapAnalysisModule::heapDump)
+ .tabCompleter((platform, sender, arguments) -> TabCompleter.completeForOpts(arguments, "--compress", "--run-gc-before", "--include-non-live"))
+ .build()
+ );
+ }
- Path file = pluginFolder.resolve("heap-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + (HeapDump.isOpenJ9() ? ".phd" : ".hprof"));
- boolean liveOnly = !arguments.boolFlag("include-non-live");
+ private static void heapSummary(SparkPlatform platform, CommandSender sender, CommandResponseHandler resp, Arguments arguments) {
+ platform.getPlugin().executeAsync(() -> {
+ if (arguments.boolFlag("run-gc-before")) {
+ resp.broadcastPrefixed(text("Running garbage collector..."));
+ System.gc();
+ }
- if (arguments.boolFlag("run-gc-before")) {
- resp.broadcastPrefixed(text("Running garbage collector..."));
- System.gc();
- }
+ resp.broadcastPrefixed(text("Creating a new heap dump summary, please wait..."));
- resp.broadcastPrefixed(text("Creating a new heap dump, please wait..."));
+ HeapDumpSummary heapDump;
+ try {
+ heapDump = HeapDumpSummary.createNew();
+ } catch (Exception e) {
+ resp.broadcastPrefixed(text("An error occurred whilst inspecting the heap.", RED));
+ e.printStackTrace();
+ return;
+ }
- try {
- HeapDump.dumpHeap(file, liveOnly);
- } catch (Exception e) {
- resp.broadcastPrefixed(text("An error occurred whilst creating a heap dump.", RED));
- e.printStackTrace();
- return;
- }
+ byte[] output = heapDump.formCompressedDataPayload(platform.getPlugin().getPlatformInfo(), sender);
+ try {
+ String key = SparkPlatform.BYTEBIN_CLIENT.postContent(output, SPARK_HEAP_MEDIA_TYPE).key();
+ String url = SparkPlatform.VIEWER_URL + key;
- resp.broadcastPrefixed(text()
- .content("Heap dump written to: ")
- .color(GOLD)
- .append(text(file.toString(), GRAY))
- .build()
- );
- platform.getActivityLog().addToLog(Activity.fileActivity(sender, System.currentTimeMillis(), "Heap dump", file.toString()));
-
-
- CompressionMethod compress = null;
- Iterator<String> compressArgs = arguments.stringFlag("compress").iterator();
- if (compressArgs.hasNext()) {
- try {
- compress = CompressionMethod.valueOf(compressArgs.next().toUpperCase());
- } catch (IllegalArgumentException e) {
- // ignore
- }
- }
+ resp.broadcastPrefixed(text("Heap dump summmary output:", GOLD));
+ resp.broadcast(text()
+ .content(url)
+ .color(GRAY)
+ .clickEvent(ClickEvent.openUrl(url))
+ .build()
+ );
- if (compress != null) {
- resp.broadcastPrefixed(text("Compressing heap dump, please wait..."));
- try {
- long size = Files.size(file);
- AtomicLong lastReport = new AtomicLong(System.currentTimeMillis());
-
- LongConsumer progressHandler = progress -> {
- long timeSinceLastReport = System.currentTimeMillis() - lastReport.get();
- if (timeSinceLastReport > TimeUnit.SECONDS.toMillis(5)) {
- lastReport.set(System.currentTimeMillis());
-
- platform.getPlugin().executeAsync(() -> {
- resp.broadcastPrefixed(text()
- .color(GRAY)
- .append(text("Compressed "))
- .append(text(FormatUtil.formatBytes(progress), GOLD))
- .append(text(" / "))
- .append(text(FormatUtil.formatBytes(size), GOLD))
- .append(text(" so far... ("))
- .append(text(FormatUtil.percent(progress, size), GREEN))
- .append(text(")"))
- .build()
- );
- });
- }
- };
-
- Path compressedFile = compress.compress(file, progressHandler);
- long compressedSize = Files.size(compressedFile);
-
- resp.broadcastPrefixed(text()
- .color(GRAY)
- .append(text("Compression complete: "))
- .append(text(FormatUtil.formatBytes(size), GOLD))
- .append(text(" --> "))
- .append(text(FormatUtil.formatBytes(compressedSize), GOLD))
- .append(text(" ("))
- .append(text(FormatUtil.percent(compressedSize, size), GREEN))
- .append(text(")"))
- .build()
- );
-
- resp.broadcastPrefixed(text()
- .content("Compressed heap dump written to: ")
- .color(GOLD)
- .append(text(compressedFile.toString(), GRAY))
- .build()
- );
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- });
- })
- .tabCompleter((platform, sender, arguments) -> TabCompleter.completeForOpts(arguments, "--compress", "--run-gc-before", "--include-non-live"))
+ platform.getActivityLog().addToLog(Activity.urlActivity(sender, System.currentTimeMillis(), "Heap dump summary", url));
+ } catch (IOException e) {
+ resp.broadcastPrefixed(text("An error occurred whilst uploading the data.", RED));
+ e.printStackTrace();
+ }
+ });
+ }
+
+ private static void heapDump(SparkPlatform platform, CommandSender sender, CommandResponseHandler resp, Arguments arguments) {
+ platform.getPlugin().executeAsync(() -> {
+ Path pluginFolder = platform.getPlugin().getPluginDirectory();
+ try {
+ Files.createDirectories(pluginFolder);
+ } catch (IOException e) {
+ // ignore
+ }
+
+ Path file = pluginFolder.resolve("heap-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + (HeapDump.isOpenJ9() ? ".phd" : ".hprof"));
+ boolean liveOnly = !arguments.boolFlag("include-non-live");
+
+ if (arguments.boolFlag("run-gc-before")) {
+ resp.broadcastPrefixed(text("Running garbage collector..."));
+ System.gc();
+ }
+
+ resp.broadcastPrefixed(text("Creating a new heap dump, please wait..."));
+
+ try {
+ HeapDump.dumpHeap(file, liveOnly);
+ } catch (Exception e) {
+ resp.broadcastPrefixed(text("An error occurred whilst creating a heap dump.", RED));
+ e.printStackTrace();
+ return;
+ }
+
+ resp.broadcastPrefixed(text()
+ .content("Heap dump written to: ")
+ .color(GOLD)
+ .append(text(file.toString(), GRAY))
+ .build()
+ );
+ platform.getActivityLog().addToLog(Activity.fileActivity(sender, System.currentTimeMillis(), "Heap dump", file.toString()));
+
+
+ CompressionMethod compressionMethod = null;
+ Iterator<String> compressArgs = arguments.stringFlag("compress").iterator();
+ if (compressArgs.hasNext()) {
+ try {
+ compressionMethod = CompressionMethod.valueOf(compressArgs.next().toUpperCase());
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ }
+
+ if (compressionMethod != null) {
+ try {
+ heapDumpCompress(platform, resp, file, compressionMethod);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ private static void heapDumpCompress(SparkPlatform platform, CommandResponseHandler resp, Path file, CompressionMethod method) throws IOException {
+ resp.broadcastPrefixed(text("Compressing heap dump, please wait..."));
+
+ long size = Files.size(file);
+ AtomicLong lastReport = new AtomicLong(System.currentTimeMillis());
+
+ LongConsumer progressHandler = progress -> {
+ long timeSinceLastReport = System.currentTimeMillis() - lastReport.get();
+ if (timeSinceLastReport > TimeUnit.SECONDS.toMillis(5)) {
+ lastReport.set(System.currentTimeMillis());
+
+ platform.getPlugin().executeAsync(() -> {
+ resp.broadcastPrefixed(text()
+ .color(GRAY)
+ .append(text("Compressed "))
+ .append(text(FormatUtil.formatBytes(progress), GOLD))
+ .append(text(" / "))
+ .append(text(FormatUtil.formatBytes(size), GOLD))
+ .append(text(" so far... ("))
+ .append(text(FormatUtil.percent(progress, size), GREEN))
+ .append(text(")"))
+ .build()
+ );
+ });
+ }
+ };
+
+ Path compressedFile = method.compress(file, progressHandler);
+ long compressedSize = Files.size(compressedFile);
+
+ resp.broadcastPrefixed(text()
+ .color(GRAY)
+ .append(text("Compression complete: "))
+ .append(text(FormatUtil.formatBytes(size), GOLD))
+ .append(text(" --> "))
+ .append(text(FormatUtil.formatBytes(compressedSize), GOLD))
+ .append(text(" ("))
+ .append(text(FormatUtil.percent(compressedSize, size), GREEN))
+ .append(text(")"))
+ .build()
+ );
+
+ resp.broadcastPrefixed(text()
+ .content("Compressed heap dump written to: ")
+ .color(GOLD)
+ .append(text(compressedFile.toString(), GRAY))
.build()
);
}
diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java
index 094b398..ff577d5 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java
@@ -21,11 +21,14 @@
package me.lucko.spark.common.command.modules;
import com.google.common.collect.Iterables;
+
import me.lucko.spark.common.SparkPlatform;
-import me.lucko.spark.common.activitylog.ActivityLog.Activity;
+import me.lucko.spark.common.activitylog.Activity;
+import me.lucko.spark.common.command.Arguments;
import me.lucko.spark.common.command.Command;
import me.lucko.spark.common.command.CommandModule;
import me.lucko.spark.common.command.CommandResponseHandler;
+import me.lucko.spark.common.command.sender.CommandSender;
import me.lucko.spark.common.command.tabcomplete.CompletionSupplier;
import me.lucko.spark.common.command.tabcomplete.TabCompleter;
import me.lucko.spark.common.sampler.Sampler;
@@ -35,9 +38,11 @@ import me.lucko.spark.common.sampler.ThreadGrouper;
import me.lucko.spark.common.sampler.ThreadNodeOrder;
import me.lucko.spark.common.sampler.async.AsyncSampler;
import me.lucko.spark.common.sampler.node.MergeMode;
-import me.lucko.spark.common.sampler.tick.TickHook;
+import me.lucko.spark.common.tick.TickHook;
import me.lucko.spark.common.util.MethodDisambiguator;
+
import net.kyori.adventure.text.event.ClickEvent;
+
import okhttp3.MediaType;
import java.io.IOException;
@@ -50,8 +55,12 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
-import static net.kyori.adventure.text.Component.*;
-import static net.kyori.adventure.text.format.NamedTextColor.*;
+import static net.kyori.adventure.text.Component.space;
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.DARK_GRAY;
+import static net.kyori.adventure.text.format.NamedTextColor.GOLD;
+import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
public class SamplerModule implements CommandModule {
private static final MediaType SPARK_SAMPLER_MEDIA_TYPE = MediaType.parse("application/x-spark-sampler");
@@ -84,170 +93,7 @@ public class SamplerModule implements CommandModule {
.argumentUsage("force-java-sampler", null)
.argumentUsage("stop --comment", "comment")
.argumentUsage("stop --order-by-time", null)
- .executor((platform, sender, resp, arguments) -> {
- if (arguments.boolFlag("info")) {
- if (this.activeSampler == null) {
- resp.replyPrefixed(text("There isn't an active profiler running."));
- } else {
- long timeout = this.activeSampler.getEndTime();
- if (timeout == -1) {
- resp.replyPrefixed(text("There is an active profiler currently running, with no defined timeout."));
- } else {
- long timeoutDiff = (timeout - System.currentTimeMillis()) / 1000L;
- resp.replyPrefixed(text("There is an active profiler currently running, due to timeout in " + timeoutDiff + " seconds."));
- }
-
- long runningTime = (System.currentTimeMillis() - this.activeSampler.getStartTime()) / 1000L;
- resp.replyPrefixed(text("It has been profiling for " + runningTime + " seconds so far."));
- }
- return;
- }
-
- if (arguments.boolFlag("cancel")) {
- if (this.activeSampler == null) {
- resp.replyPrefixed(text("There isn't an active profiler running."));
- } else {
- close();
- resp.broadcastPrefixed(text("The active profiler has been cancelled.", GOLD));
- }
- return;
- }
-
- if (arguments.boolFlag("stop") || arguments.boolFlag("upload")) {
- if (this.activeSampler == null) {
- resp.replyPrefixed(text("There isn't an active profiler running."));
- } else {
- this.activeSampler.stop();
- resp.broadcastPrefixed(text("The active profiler has been stopped! Uploading results..."));
- ThreadNodeOrder threadOrder = arguments.boolFlag("order-by-time") ? ThreadNodeOrder.BY_TIME : ThreadNodeOrder.BY_NAME;
- String comment = Iterables.getFirst(arguments.stringFlag("comment"), null);
- MethodDisambiguator methodDisambiguator = new MethodDisambiguator();
- MergeMode mergeMode = arguments.boolFlag("separate-parent-calls") ? MergeMode.separateParentCalls(methodDisambiguator) : MergeMode.sameMethod(methodDisambiguator);
- handleUpload(platform, resp, this.activeSampler, threadOrder, comment, mergeMode);
- this.activeSampler = null;
- }
- return;
- }
-
- int timeoutSeconds = arguments.intFlag("timeout");
- if (timeoutSeconds != -1 && timeoutSeconds <= 10) {
- resp.replyPrefixed(text("The specified timeout is not long enough for accurate results to be formed. " +
- "Please choose a value greater than 10.", RED));
- return;
- }
-
- if (timeoutSeconds != -1 && timeoutSeconds < 30) {
- resp.replyPrefixed(text("The accuracy of the output will significantly improve when the profiler is able to run for longer periods. " +
- "Consider setting a timeout value over 30 seconds."));
- }
-
- double intervalMillis = arguments.doubleFlag("interval");
- if (intervalMillis <= 0) {
- intervalMillis = 4;
- }
-
- boolean ignoreSleeping = arguments.boolFlag("ignore-sleeping");
- boolean ignoreNative = arguments.boolFlag("ignore-native");
- boolean forceJavaSampler = arguments.boolFlag("force-java-sampler");
-
- Set<String> threads = arguments.stringFlag("thread");
- ThreadDumper threadDumper;
- if (threads.isEmpty()) {
- // use the server thread
- threadDumper = platform.getPlugin().getDefaultThreadDumper();
- } else if (threads.contains("*")) {
- threadDumper = ThreadDumper.ALL;
- } else {
- if (arguments.boolFlag("regex")) {
- threadDumper = new ThreadDumper.Regex(threads);
- } else {
- // specific matches
- threadDumper = new ThreadDumper.Specific(threads);
- }
- }
-
- ThreadGrouper threadGrouper;
- if (arguments.boolFlag("combine-all")) {
- threadGrouper = ThreadGrouper.AS_ONE;
- } else if (arguments.boolFlag("not-combined")) {
- threadGrouper = ThreadGrouper.BY_NAME;
- } else {
- threadGrouper = ThreadGrouper.BY_POOL;
- }
-
- int ticksOver = arguments.intFlag("only-ticks-over");
- TickHook tickHook = null;
- if (ticksOver != -1) {
- tickHook = platform.getTickHook();
- if (tickHook == null) {
- resp.replyPrefixed(text("Tick counting is not supported!", RED));
- return;
- }
- }
-
- if (this.activeSampler != null) {
- resp.replyPrefixed(text("An active profiler is already running."));
- return;
- }
-
- resp.broadcastPrefixed(text("Initializing a new profiler, please wait..."));
-
- SamplerBuilder builder = new SamplerBuilder();
- builder.threadDumper(threadDumper);
- builder.threadGrouper(threadGrouper);
- if (timeoutSeconds != -1) {
- builder.completeAfter(timeoutSeconds, TimeUnit.SECONDS);
- }
- builder.samplingInterval(intervalMillis);
- builder.ignoreSleeping(ignoreSleeping);
- builder.ignoreNative(ignoreNative);
- builder.forceJavaSampler(forceJavaSampler);
- if (ticksOver != -1) {
- builder.ticksOver(ticksOver, tickHook);
- }
- Sampler sampler = this.activeSampler = builder.start();
-
- resp.broadcastPrefixed(text()
- .append(text("Profiler now active!", GOLD))
- .append(space())
- .append(text("(" + (sampler instanceof AsyncSampler ? "async" : "built-in java") + ")", DARK_GRAY))
- .build()
- );
- if (timeoutSeconds == -1) {
- resp.broadcastPrefixed(text("Use '/" + platform.getPlugin().getCommandName() + " profiler --stop' to stop profiling and upload the results."));
- } else {
- resp.broadcastPrefixed(text("The results will be automatically returned after the profiler has been running for " + timeoutSeconds + " seconds."));
- }
-
- CompletableFuture<? extends Sampler> future = this.activeSampler.getFuture();
-
- // send message if profiling fails
- future.whenCompleteAsync((s, throwable) -> {
- if (throwable != null) {
- resp.broadcastPrefixed(text("Profiler operation failed unexpectedly. Error: " + throwable.toString(), RED));
- throwable.printStackTrace();
- }
- });
-
- // set activeSampler to null when complete.
- future.whenCompleteAsync((s, throwable) -> {
- if (sampler == this.activeSampler) {
- this.activeSampler = null;
- }
- });
-
- // await the result
- if (timeoutSeconds != -1) {
- ThreadNodeOrder threadOrder = arguments.boolFlag("order-by-time") ? ThreadNodeOrder.BY_TIME : ThreadNodeOrder.BY_NAME;
- String comment = Iterables.getFirst(arguments.stringFlag("comment"), null);
- MethodDisambiguator methodDisambiguator = new MethodDisambiguator();
- MergeMode mergeMode = arguments.boolFlag("separate-parent-calls") ? MergeMode.separateParentCalls(methodDisambiguator) : MergeMode.sameMethod(methodDisambiguator);
- future.thenAcceptAsync(s -> {
- resp.broadcastPrefixed(text("The active profiler has completed! Uploading results..."));
- handleUpload(platform, resp, s, threadOrder, comment, mergeMode);
- });
- }
- })
+ .executor(this::profiler)
.tabCompleter((platform, sender, arguments) -> {
if (arguments.contains("--info") || arguments.contains("--cancel")) {
return Collections.emptyList();
@@ -271,11 +117,192 @@ public class SamplerModule implements CommandModule {
);
}
+ private void profiler(SparkPlatform platform, CommandSender sender, CommandResponseHandler resp, Arguments arguments) {
+ if (arguments.boolFlag("info")) {
+ profilerInfo(resp);
+ return;
+ }
+
+ if (arguments.boolFlag("cancel")) {
+ profilerCancel(resp);
+ return;
+ }
+
+ if (arguments.boolFlag("stop") || arguments.boolFlag("upload")) {
+ profilerStop(platform, sender, resp, arguments);
+ return;
+ }
+
+ profilerStart(platform, sender, resp, arguments);
+ }
+
+ private void profilerStart(SparkPlatform platform, CommandSender sender, CommandResponseHandler resp, Arguments arguments) {
+ int timeoutSeconds = arguments.intFlag("timeout");
+ if (timeoutSeconds != -1 && timeoutSeconds <= 10) {
+ resp.replyPrefixed(text("The specified timeout is not long enough for accurate results to be formed. " +
+ "Please choose a value greater than 10.", RED));
+ return;
+ }
+
+ if (timeoutSeconds != -1 && timeoutSeconds < 30) {
+ resp.replyPrefixed(text("The accuracy of the output will significantly improve when the profiler is able to run for longer periods. " +
+ "Consider setting a timeout value over 30 seconds."));
+ }
+
+ double intervalMillis = arguments.doubleFlag("interval");
+ if (intervalMillis <= 0) {
+ intervalMillis = 4;
+ }
+
+ boolean ignoreSleeping = arguments.boolFlag("ignore-sleeping");
+ boolean ignoreNative = arguments.boolFlag("ignore-native");
+ boolean forceJavaSampler = arguments.boolFlag("force-java-sampler");
+
+ Set<String> threads = arguments.stringFlag("thread");
+ ThreadDumper threadDumper;
+ if (threads.isEmpty()) {
+ // use the server thread
+ threadDumper = platform.getPlugin().getDefaultThreadDumper();
+ } else if (threads.contains("*")) {
+ threadDumper = ThreadDumper.ALL;
+ } else {
+ if (arguments.boolFlag("regex")) {
+ threadDumper = new ThreadDumper.Regex(threads);
+ } else {
+ // specific matches
+ threadDumper = new ThreadDumper.Specific(threads);
+ }
+ }
+
+ ThreadGrouper threadGrouper;
+ if (arguments.boolFlag("combine-all")) {
+ threadGrouper = ThreadGrouper.AS_ONE;
+ } else if (arguments.boolFlag("not-combined")) {
+ threadGrouper = ThreadGrouper.BY_NAME;
+ } else {
+ threadGrouper = ThreadGrouper.BY_POOL;
+ }
+
+ int ticksOver = arguments.intFlag("only-ticks-over");
+ TickHook tickHook = null;
+ if (ticksOver != -1) {
+ tickHook = platform.getTickHook();
+ if (tickHook == null) {
+ resp.replyPrefixed(text("Tick counting is not supported!", RED));
+ return;
+ }
+ }
+
+ if (this.activeSampler != null) {
+ resp.replyPrefixed(text("An active profiler is already running."));
+ return;
+ }
+
+ resp.broadcastPrefixed(text("Initializing a new profiler, please wait..."));
+
+ SamplerBuilder builder = new SamplerBuilder();
+ builder.threadDumper(threadDumper);
+ builder.threadGrouper(threadGrouper);
+ if (timeoutSeconds != -1) {
+ builder.completeAfter(timeoutSeconds, TimeUnit.SECONDS);
+ }
+ builder.samplingInterval(intervalMillis);
+ builder.ignoreSleeping(ignoreSleeping);
+ builder.ignoreNative(ignoreNative);
+ builder.forceJavaSampler(forceJavaSampler);
+ if (ticksOver != -1) {
+ builder.ticksOver(ticksOver, tickHook);
+ }
+ Sampler sampler = this.activeSampler = builder.start();
+
+ resp.broadcastPrefixed(text()
+ .append(text("Profiler now active!", GOLD))
+ .append(space())
+ .append(text("(" + (sampler instanceof AsyncSampler ? "async" : "built-in java") + ")", DARK_GRAY))
+ .build()
+ );
+ if (timeoutSeconds == -1) {
+ resp.broadcastPrefixed(text("Use '/" + platform.getPlugin().getCommandName() + " profiler --stop' to stop profiling and upload the results."));
+ } else {
+ resp.broadcastPrefixed(text("The results will be automatically returned after the profiler has been running for " + timeoutSeconds + " seconds."));
+ }
+
+ CompletableFuture<? extends Sampler> future = this.activeSampler.getFuture();
+
+ // send message if profiling fails
+ future.whenCompleteAsync((s, throwable) -> {
+ if (throwable != null) {
+ resp.broadcastPrefixed(text("Profiler operation failed unexpectedly. Error: " + throwable.toString(), RED));
+ throwable.printStackTrace();
+ }
+ });
+
+ // set activeSampler to null when complete.
+ future.whenCompleteAsync((s, throwable) -> {
+ if (sampler == this.activeSampler) {
+ this.activeSampler = null;
+ }
+ });
+
+ // await the result
+ if (timeoutSeconds != -1) {
+ ThreadNodeOrder threadOrder = arguments.boolFlag("order-by-time") ? ThreadNodeOrder.BY_TIME : ThreadNodeOrder.BY_NAME;
+ String comment = Iterables.getFirst(arguments.stringFlag("comment"), null);
+ MethodDisambiguator methodDisambiguator = new MethodDisambiguator();
+ MergeMode mergeMode = arguments.boolFlag("separate-parent-calls") ? MergeMode.separateParentCalls(methodDisambiguator) : MergeMode.sameMethod(methodDisambiguator);
+ future.thenAcceptAsync(s -> {
+ resp.broadcastPrefixed(text("The active profiler has completed! Uploading results..."));
+ handleUpload(platform, resp, s, threadOrder, comment, mergeMode);
+ });
+ }
+ }
+
+ private void profilerInfo(CommandResponseHandler resp) {
+ if (this.activeSampler == null) {
+ resp.replyPrefixed(text("There isn't an active profiler running."));
+ } else {
+ long timeout = this.activeSampler.getEndTime();
+ if (timeout == -1) {
+ resp.replyPrefixed(text("There is an active profiler currently running, with no defined timeout."));
+ } else {
+ long timeoutDiff = (timeout - System.currentTimeMillis()) / 1000L;
+ resp.replyPrefixed(text("There is an active profiler currently running, due to timeout in " + timeoutDiff + " seconds."));
+ }
+
+ long runningTime = (System.currentTimeMillis() - this.activeSampler.getStartTime()) / 1000L;
+ resp.replyPrefixed(text("It has been profiling for " + runningTime + " seconds so far."));
+ }
+ }
+
+ private void profilerCancel(CommandResponseHandler resp) {
+ if (this.activeSampler == null) {
+ resp.replyPrefixed(text("There isn't an active profiler running."));
+ } else {
+ close();
+ resp.broadcastPrefixed(text("The active profiler has been cancelled.", GOLD));
+ }
+ }
+
+ private void profilerStop(SparkPlatform platform, CommandSender sender, CommandResponseHandler resp, Arguments arguments) {
+ if (this.activeSampler == null) {
+ resp.replyPrefixed(text("There isn't an active profiler running."));
+ } else {
+ this.activeSampler.stop();
+ resp.broadcastPrefixed(text("The active profiler has been stopped! Uploading results..."));
+ ThreadNodeOrder threadOrder = arguments.boolFlag("order-by-time") ? ThreadNodeOrder.BY_TIME : ThreadNodeOrder.BY_NAME;
+ String comment = Iterables.getFirst(arguments.stringFlag("comment"), null);
+ MethodDisambiguator methodDisambiguator = new MethodDisambiguator();
+ MergeMode mergeMode = arguments.boolFlag("separate-parent-calls") ? MergeMode.separateParentCalls(methodDisambiguator) : MergeMode.sameMethod(methodDisambiguator);
+ handleUpload(platform, resp, this.activeSampler, threadOrder, comment, mergeMode);
+ this.activeSampler = null;
+ }
+ }
+
private void handleUpload(SparkPlatform platform, CommandResponseHandler resp, Sampler sampler, ThreadNodeOrder threadOrder, String comment, MergeMode mergeMode) {
platform.getPlugin().executeAsync(() -> {
byte[] output = sampler.formCompressedDataPayload(platform.getPlugin().getPlatformInfo(), resp.sender(), threadOrder, comment, mergeMode);
try {
- String key = SparkPlatform.BYTEBIN_CLIENT.postContent(output, SPARK_SAMPLER_MEDIA_TYPE, false).key();
+ String key = SparkPlatform.BYTEBIN_CLIENT.postContent(output, SPARK_SAMPLER_MEDIA_TYPE).key();
String url = SparkPlatform.VIEWER_URL + key;
resp.broadcastPrefixed(text("Profiler results:", GOLD));
diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/TickMonitoringModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/TickMonitoringModule.java
index 043bc65..f5f4fce 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/TickMonitoringModule.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/TickMonitoringModule.java
@@ -25,26 +25,25 @@ import me.lucko.spark.common.command.Command;
import me.lucko.spark.common.command.CommandModule;
import me.lucko.spark.common.command.CommandResponseHandler;
import me.lucko.spark.common.command.tabcomplete.TabCompleter;
+import me.lucko.spark.common.monitor.tick.ReportPredicate;
import me.lucko.spark.common.monitor.tick.TickMonitor;
-import me.lucko.spark.common.monitor.tick.TickMonitor.ReportPredicate;
-import me.lucko.spark.common.sampler.tick.TickHook;
+import me.lucko.spark.common.tick.TickHook;
+
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import java.util.function.Consumer;
-import static net.kyori.adventure.text.Component.*;
+import static net.kyori.adventure.text.Component.text;
public class TickMonitoringModule implements CommandModule {
/** The tick hook instance currently running, if any */
- private TickHook tickHook = null;
- private ReportingTickMonitor activeTickMonitor = null;
+ private TickMonitor activeTickMonitor = null;
@Override
public void close() {
if (this.activeTickMonitor != null) {
- this.tickHook.removeCallback(this.activeTickMonitor);
this.activeTickMonitor.close();
this.activeTickMonitor = null;
}
@@ -58,10 +57,8 @@ public class TickMonitoringModule implements CommandModule {
.argumentUsage("threshold-tick", "tick duration")
.argumentUsage("without-gc", null)
.executor((platform, sender, resp, arguments) -> {
- if (this.tickHook == null) {
- this.tickHook = platform.getTickHook();
- }
- if (this.tickHook == null) {
+ TickHook tickHook = platform.getTickHook();
+ if (tickHook == null) {
resp.replyPrefixed(text("Not supported!", NamedTextColor.RED));
return;
}
@@ -78,8 +75,8 @@ public class TickMonitoringModule implements CommandModule {
reportPredicate = new ReportPredicate.PercentageChangeGt(100);
}
- this.activeTickMonitor = new ReportingTickMonitor(platform, resp, this.tickHook, reportPredicate, !arguments.boolFlag("without-gc"));
- this.tickHook.addCallback(this.activeTickMonitor);
+ this.activeTickMonitor = new ReportingTickMonitor(platform, resp, tickHook, reportPredicate, !arguments.boolFlag("without-gc"));
+ this.activeTickMonitor.start();
} else {
close();
resp.broadcastPrefixed(text("Tick monitor disabled."));
diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/sender/CommandSender.java b/spark-common/src/main/java/me/lucko/spark/common/command/sender/CommandSender.java
index b10c7d8..9feeac2 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/command/sender/CommandSender.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/command/sender/CommandSender.java
@@ -23,7 +23,9 @@ package me.lucko.spark.common.command.sender;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
+
import me.lucko.spark.proto.SparkProtos.CommandSenderData;
+
import net.kyori.adventure.text.Component;
import java.util.UUID;