aboutsummaryrefslogtreecommitdiff
path: root/spark-common/src
diff options
context:
space:
mode:
authorLuck <git@lucko.me>2020-04-09 20:15:25 +0100
committerLuck <git@lucko.me>2020-04-09 20:15:25 +0100
commita235245e92cdeefecbd0bba33c68c0141415a359 (patch)
treefadccf87267d935fcb3e1137611a49ecb2cc0e5a /spark-common/src
parent6f2153e33d232d5b52418531abc0007dcdc466e9 (diff)
downloadspark-a235245e92cdeefecbd0bba33c68c0141415a359.tar.gz
spark-a235245e92cdeefecbd0bba33c68c0141415a359.tar.bz2
spark-a235245e92cdeefecbd0bba33c68c0141415a359.zip
Add '/spark gc' and '/spark gcmonitor' commands to help track GC activity
Example output: https://i.imgur.com/JihIDvk.png
Diffstat (limited to 'spark-common/src')
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java6
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/command/modules/GcMonitoringModule.java226
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java (renamed from spark-common/src/main/java/me/lucko/spark/common/command/modules/MemoryModule.java)2
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/command/modules/TickMonitoringModule.java2
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/monitor/tick/TickMonitor.java4
5 files changed, 234 insertions, 6 deletions
diff --git a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java
index f439729..50ef7d8 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java
@@ -27,8 +27,9 @@ 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.modules.ActivityLogModule;
+import me.lucko.spark.common.command.modules.GcMonitoringModule;
import me.lucko.spark.common.command.modules.HealthModule;
-import me.lucko.spark.common.command.modules.MemoryModule;
+import me.lucko.spark.common.command.modules.HeapAnalysisModule;
import me.lucko.spark.common.command.modules.SamplerModule;
import me.lucko.spark.common.command.modules.TickMonitoringModule;
import me.lucko.spark.common.command.sender.CommandSender;
@@ -78,7 +79,8 @@ public class SparkPlatform {
new SamplerModule(),
new HealthModule(),
new TickMonitoringModule(),
- new MemoryModule(),
+ new GcMonitoringModule(),
+ new HeapAnalysisModule(),
new ActivityLogModule()
);
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
new file mode 100644
index 0000000..2e67a99
--- /dev/null
+++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/GcMonitoringModule.java
@@ -0,0 +1,226 @@
+/*
+ * This file is part of spark.
+ *
+ * Copyright (c) lucko (Luck) <luck@lucko.me>
+ * Copyright (c) contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+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;
+import me.lucko.spark.common.command.CommandResponseHandler;
+import me.lucko.spark.common.monitor.memory.GarbageCollectionMonitor;
+import me.lucko.spark.common.util.FormatUtil;
+import net.kyori.text.Component;
+import net.kyori.text.TextComponent;
+import net.kyori.text.format.TextColor;
+import net.kyori.text.format.TextDecoration;
+
+import java.lang.management.GarbageCollectorMXBean;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryUsage;
+import java.text.DecimalFormat;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+public class GcMonitoringModule implements CommandModule {
+ private static final DecimalFormat df = new DecimalFormat("#.##");
+
+ /** The gc monitoring instance currently running, if any */
+ private ReportingGcMonitor activeGcMonitor = null;
+
+ @Override
+ public void close() {
+ if (this.activeGcMonitor != null) {
+ this.activeGcMonitor.close();
+ this.activeGcMonitor = null;
+ }
+ }
+
+ @Override
+ public void registerCommands(Consumer<Command> consumer) {
+ consumer.accept(Command.builder()
+ .aliases("gc")
+ .executor((platform, sender, resp, arguments) -> {
+ resp.replyPrefixed(TextComponent.of("Calculating GC collection averages..."));
+
+ List<Component> report = new LinkedList<>();
+ report.add(TextComponent.empty());
+ report.add(TextComponent.builder("")
+ .append(TextComponent.builder(">").color(TextColor.DARK_GRAY).decoration(TextDecoration.BOLD, true).build())
+ .append(TextComponent.space())
+ .append(TextComponent.of("GC collection averages", TextColor.GOLD))
+ .build()
+ );
+
+ for (GarbageCollectorMXBean collector : ManagementFactory.getGarbageCollectorMXBeans()) {
+ double collectionTime = collector.getCollectionTime();
+ long collectionCount = collector.getCollectionCount();
+
+ report.add(TextComponent.empty());
+
+ if (collectionCount == 0) {
+ report.add(TextComponent.builder(" ")
+ .append(TextComponent.of(collector.getName() + " collector:", TextColor.GRAY))
+ .build()
+ );
+ report.add(TextComponent.builder(" ")
+ .append(TextComponent.of(0, TextColor.WHITE))
+ .append(TextComponent.of(" collections", TextColor.GRAY))
+ .build()
+ );
+ continue;
+ }
+
+ double averageCollectionTime = collectionTime / collectionCount;
+
+ report.add(TextComponent.builder(" ")
+ .append(TextComponent.of(collector.getName() + " collector:", TextColor.GRAY))
+ .build()
+ );
+ report.add(TextComponent.builder(" ")
+ .append(TextComponent.of(df.format(averageCollectionTime), TextColor.GOLD))
+ .append(TextComponent.of(" ms avg", TextColor.GRAY))
+ .append(TextComponent.of(", ", TextColor.DARK_GRAY))
+ .append(TextComponent.of(collectionCount, TextColor.WHITE))
+ .append(TextComponent.of(" total collections", TextColor.GRAY))
+ .build()
+ );
+ }
+
+ if (report.size() == 1) {
+ resp.replyPrefixed(TextComponent.of("No garbage collectors are reporting data."));
+ } else {
+ report.forEach(resp::reply);
+ }
+ })
+ .build()
+ );
+
+ consumer.accept(Command.builder()
+ .aliases("gcmonitor", "gcmonitoring")
+ .executor((platform, sender, resp, arguments) -> {
+ if (this.activeGcMonitor == null) {
+ this.activeGcMonitor = new ReportingGcMonitor(platform, resp);
+ resp.broadcastPrefixed(TextComponent.of("GC monitor enabled."));
+ } else {
+ close();
+ resp.broadcastPrefixed(TextComponent.of("GC monitor disabled."));
+ }
+ })
+ .build()
+ );
+ }
+
+ private static class ReportingGcMonitor extends GarbageCollectionMonitor implements GarbageCollectionMonitor.Listener {
+ private final SparkPlatform platform;
+ private final CommandResponseHandler resp;
+
+ ReportingGcMonitor(SparkPlatform platform, CommandResponseHandler resp) {
+ this.platform = platform;
+ this.resp = resp;
+ addListener(this);
+ }
+
+ protected void sendMessage(Component message) {
+ this.resp.broadcastPrefixed(message);
+ }
+
+ @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 gcCause = data.getGcCause() != null ? " (cause = " + data.getGcCause() + ")" : "";
+
+ Map<String, MemoryUsage> beforeUsages = data.getGcInfo().getMemoryUsageBeforeGc();
+ Map<String, MemoryUsage> afterUsages = data.getGcInfo().getMemoryUsageAfterGc();
+
+ this.platform.getPlugin().executeAsync(() -> {
+ List<Component> report = new LinkedList<>();
+ report.add(CommandResponseHandler.applyPrefix(TextComponent.builder("").color(TextColor.GRAY)
+ .append(TextComponent.of(gcType + " "))
+ .append(TextComponent.of("GC", TextColor.RED))
+ .append(TextComponent.of(" lasting "))
+ .append(TextComponent.of(df.format(data.getGcInfo().getDuration()), TextColor.GOLD))
+ .append(TextComponent.of(" ms." + gcCause))
+ .build()
+ ));
+
+ for (Map.Entry<String, MemoryUsage> entry : afterUsages.entrySet()) {
+ String type = entry.getKey();
+ MemoryUsage after = entry.getValue();
+ MemoryUsage before = beforeUsages.get(type);
+
+ if (before == null) {
+ continue;
+ }
+
+ long diff = before.getUsed() - after.getUsed();
+ if (diff == 0) {
+ continue;
+ }
+
+ if (diff > 0) {
+ report.add(TextComponent.builder(" ")
+ .append(TextComponent.of(FormatUtil.formatBytes(diff), TextColor.GOLD))
+ .append(TextComponent.of(" freed from ", TextColor.DARK_GRAY))
+ .append(TextComponent.of(type, TextColor.GRAY))
+ .build()
+ );
+ report.add(TextComponent.builder(" ")
+ .append(TextComponent.of(FormatUtil.formatBytes(before.getUsed()), TextColor.WHITE))
+ .append(TextComponent.of(" → ", TextColor.GRAY))
+ .append(TextComponent.of(FormatUtil.formatBytes(after.getUsed()), TextColor.WHITE))
+ .append(TextComponent.space())
+ .append(TextComponent.of("(", TextColor.GRAY))
+ .append(TextComponent.of(FormatUtil.percent(diff, before.getUsed()), TextColor.GREEN))
+ .append(TextComponent.of(")", TextColor.GRAY))
+ .build()
+ );
+ } else {
+ report.add(TextComponent.builder(" ")
+ .append(TextComponent.of(FormatUtil.formatBytes(-diff), TextColor.GOLD))
+ .append(TextComponent.of(" moved to ", TextColor.DARK_GRAY))
+ .append(TextComponent.of(type, TextColor.GRAY))
+ .build()
+ );
+ report.add(TextComponent.builder(" ")
+ .append(TextComponent.of(FormatUtil.formatBytes(before.getUsed()), TextColor.WHITE))
+ .append(TextComponent.of(" → ", TextColor.GRAY))
+ .append(TextComponent.of(FormatUtil.formatBytes(after.getUsed()), TextColor.WHITE))
+ .build()
+ );
+ }
+ }
+
+ report.forEach(this.resp::broadcast);
+ });
+ }
+
+ }
+}
diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/MemoryModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java
index 4a87eaf..77d7b3e 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/MemoryModule.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java
@@ -50,7 +50,7 @@ import java.util.function.Consumer;
import java.util.function.LongConsumer;
import java.util.zip.GZIPOutputStream;
-public class MemoryModule implements CommandModule {
+public class HeapAnalysisModule implements CommandModule {
private static final MediaType SPARK_HEAP_MEDIA_TYPE = MediaType.parse("application/x-spark-heap");
@Override
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 7014770..bef5d78 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
@@ -52,7 +52,7 @@ public class TickMonitoringModule implements CommandModule {
@Override
public void registerCommands(Consumer<Command> consumer) {
consumer.accept(Command.builder()
- .aliases("tickmonitoring")
+ .aliases("tickmonitor", "tickmonitoring")
.argumentUsage("threshold", "percentage increase")
.argumentUsage("threshold-tick", "tick duration")
.argumentUsage("without-gc", null)
diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/tick/TickMonitor.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/tick/TickMonitor.java
index 4a755d1..de94dc6 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/monitor/tick/TickMonitor.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/tick/TickMonitor.java
@@ -166,9 +166,9 @@ public abstract class TickMonitor implements TickHook.Callback, GarbageCollectio
String gcType;
if (data.getGcAction().equals("end of minor GC")) {
- gcType = "Young Gen GC";
+ gcType = "Young Gen";
} else if (data.getGcAction().equals("end of major GC")) {
- gcType = "Old Gen GC";
+ gcType = "Old Gen";
} else {
gcType = data.getGcAction();
}