diff options
author | Luck <git@lucko.me> | 2020-04-09 20:15:25 +0100 |
---|---|---|
committer | Luck <git@lucko.me> | 2020-04-09 20:15:25 +0100 |
commit | a235245e92cdeefecbd0bba33c68c0141415a359 (patch) | |
tree | fadccf87267d935fcb3e1137611a49ecb2cc0e5a /spark-common/src | |
parent | 6f2153e33d232d5b52418531abc0007dcdc466e9 (diff) | |
download | spark-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.java | 6 | ||||
-rw-r--r-- | spark-common/src/main/java/me/lucko/spark/common/command/modules/GcMonitoringModule.java | 226 | ||||
-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.java | 2 | ||||
-rw-r--r-- | spark-common/src/main/java/me/lucko/spark/common/monitor/tick/TickMonitor.java | 4 |
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(); } |