diff options
Diffstat (limited to 'spark-common/src/main/java/me/lucko/spark/common/command')
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; |