From a235245e92cdeefecbd0bba33c68c0141415a359 Mon Sep 17 00:00:00 2001 From: Luck Date: Thu, 9 Apr 2020 20:15:25 +0100 Subject: Add '/spark gc' and '/spark gcmonitor' commands to help track GC activity Example output: https://i.imgur.com/JihIDvk.png --- .../java/me/lucko/spark/common/SparkPlatform.java | 6 +- .../common/command/modules/GcMonitoringModule.java | 226 +++++++++++++++++ .../common/command/modules/HeapAnalysisModule.java | 273 +++++++++++++++++++++ .../spark/common/command/modules/MemoryModule.java | 273 --------------------- .../command/modules/TickMonitoringModule.java | 2 +- .../spark/common/monitor/tick/TickMonitor.java | 4 +- 6 files changed, 506 insertions(+), 278 deletions(-) create mode 100644 spark-common/src/main/java/me/lucko/spark/common/command/modules/GcMonitoringModule.java create mode 100644 spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java delete mode 100644 spark-common/src/main/java/me/lucko/spark/common/command/modules/MemoryModule.java (limited to 'spark-common/src') 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) + * 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 . + */ + +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 consumer) { + consumer.accept(Command.builder() + .aliases("gc") + .executor((platform, sender, resp, arguments) -> { + resp.replyPrefixed(TextComponent.of("Calculating GC collection averages...")); + + List 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 beforeUsages = data.getGcInfo().getMemoryUsageBeforeGc(); + Map afterUsages = data.getGcInfo().getMemoryUsageAfterGc(); + + this.platform.getPlugin().executeAsync(() -> { + List 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 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/HeapAnalysisModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java new file mode 100644 index 0000000..77d7b3e --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java @@ -0,0 +1,273 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * 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 . + */ + +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.command.Command; +import me.lucko.spark.common.command.CommandModule; +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.text.TextComponent; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.format.TextColor; +import okhttp3.MediaType; +import org.tukaani.xz.LZMA2Options; +import org.tukaani.xz.LZMAOutputStream; +import org.tukaani.xz.XZOutputStream; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Iterator; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; +import java.util.function.LongConsumer; +import java.util.zip.GZIPOutputStream; + +public class HeapAnalysisModule implements CommandModule { + private static final MediaType SPARK_HEAP_MEDIA_TYPE = MediaType.parse("application/x-spark-heap"); + + @Override + public void registerCommands(Consumer consumer) { + 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(TextComponent.of("Running garbage collector...")); + System.gc(); + } + + resp.broadcastPrefixed(TextComponent.of("Creating a new heap dump summary, please wait...")); + + HeapDumpSummary heapDump; + try { + heapDump = HeapDumpSummary.createNew(); + } catch (Exception e) { + resp.broadcastPrefixed(TextComponent.of("An error occurred whilst inspecting the heap.", TextColor.RED)); + e.printStackTrace(); + return; + } + + byte[] output = heapDump.formCompressedDataPayload(sender); + try { + String key = SparkPlatform.BYTEBIN_CLIENT.postContent(output, SPARK_HEAP_MEDIA_TYPE, false).key(); + String url = SparkPlatform.VIEWER_URL + key; + + resp.broadcastPrefixed(TextComponent.of("Heap dump summmary output:", TextColor.GOLD)); + resp.broadcast(TextComponent.builder(url) + .color(TextColor.GRAY) + .clickEvent(ClickEvent.openUrl(url)) + .build() + ); + + platform.getActivityLog().addToLog(Activity.urlActivity(sender, System.currentTimeMillis(), "Heap dump summary", url)); + } catch (IOException e) { + resp.broadcastPrefixed(TextComponent.of("An error occurred whilst uploading the data.", TextColor.RED)); + e.printStackTrace(); + } + }); + }) + .tabCompleter((platform, sender, arguments) -> TabCompleter.completeForOpts(arguments, "--run-gc-before")) + .build() + ); + + consumer.accept(Command.builder() + .aliases("heapdump") + .argumentUsage("compress", "type") + .argumentUsage("run-gc-before", null) + .argumentUsage("include-non-live", null) + .executor((platform, sender, resp, 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(TextComponent.of("Running garbage collector...")); + System.gc(); + } + + resp.broadcastPrefixed(TextComponent.of("Creating a new heap dump, please wait...")); + + try { + HeapDump.dumpHeap(file, liveOnly); + } catch (Exception e) { + resp.broadcastPrefixed(TextComponent.of("An error occurred whilst creating a heap dump.", TextColor.RED)); + e.printStackTrace(); + return; + } + + resp.broadcastPrefixed(TextComponent.builder("Heap dump written to: ", TextColor.GOLD) + .append(TextComponent.of(file.toString(), TextColor.GRAY)) + .build() + ); + platform.getActivityLog().addToLog(Activity.fileActivity(sender, System.currentTimeMillis(), "Heap dump", file.toString())); + + + CompressionMethod compress = null; + Iterator compressArgs = arguments.stringFlag("compress").iterator(); + if (compressArgs.hasNext()) { + try { + compress = CompressionMethod.valueOf(compressArgs.next().toUpperCase()); + } catch (IllegalArgumentException e) { + // ignore + } + } + + if (compress != null) { + resp.broadcastPrefixed(TextComponent.of("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(TextComponent.builder("").color(TextColor.GRAY) + .append(TextComponent.of("Compressed ")) + .append(TextComponent.of(FormatUtil.formatBytes(progress), TextColor.GOLD)) + .append(TextComponent.of(" / ")) + .append(TextComponent.of(FormatUtil.formatBytes(size), TextColor.GOLD)) + .append(TextComponent.of(" so far... (")) + .append(TextComponent.of(FormatUtil.percent(progress, size), TextColor.GREEN)) + .append(TextComponent.of(")")) + .build() + ); + }); + } + }; + + Path compressedFile = compress.compress(file, progressHandler); + long compressedSize = Files.size(compressedFile); + + resp.broadcastPrefixed(TextComponent.builder("").color(TextColor.GRAY) + .append(TextComponent.of("Compression complete: ")) + .append(TextComponent.of(FormatUtil.formatBytes(size), TextColor.GOLD)) + .append(TextComponent.of(" --> ")) + .append(TextComponent.of(FormatUtil.formatBytes(compressedSize), TextColor.GOLD)) + .append(TextComponent.of(" (")) + .append(TextComponent.of(FormatUtil.percent(compressedSize, size), TextColor.GREEN)) + .append(TextComponent.of(")")) + .build() + ); + + resp.broadcastPrefixed(TextComponent.builder("Compressed heap dump written to: ", TextColor.GOLD) + .append(TextComponent.of(compressedFile.toString(), TextColor.GRAY)) + .build() + ); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + }) + .tabCompleter((platform, sender, arguments) -> TabCompleter.completeForOpts(arguments, "--compress", "--run-gc-before", "--include-non-live")) + .build() + ); + } + + public enum CompressionMethod { + GZIP { + @Override + public Path compress(Path file, LongConsumer progressHandler) throws IOException { + Path compressedFile = file.getParent().resolve(file.getFileName().toString() + ".gz"); + try (InputStream in = Files.newInputStream(file)) { + try (OutputStream out = Files.newOutputStream(compressedFile)) { + try (GZIPOutputStream compressionOut = new GZIPOutputStream(out, 1024 * 64)) { + copy(in, compressionOut, progressHandler); + } + } + } + return compressedFile; + } + }, + XZ { + @Override + public Path compress(Path file, LongConsumer progressHandler) throws IOException { + Path compressedFile = file.getParent().resolve(file.getFileName().toString() + ".xz"); + try (InputStream in = Files.newInputStream(file)) { + try (OutputStream out = Files.newOutputStream(compressedFile)) { + try (XZOutputStream compressionOut = new XZOutputStream(out, new LZMA2Options())) { + copy(in, compressionOut, progressHandler); + } + } + } + return compressedFile; + } + }, + LZMA { + @Override + public Path compress(Path file, LongConsumer progressHandler) throws IOException { + Path compressedFile = file.getParent().resolve(file.getFileName().toString() + ".lzma"); + try (InputStream in = Files.newInputStream(file)) { + try (OutputStream out = Files.newOutputStream(compressedFile)) { + try (LZMAOutputStream compressionOut = new LZMAOutputStream(out, new LZMA2Options(), true)) { + copy(in, compressionOut, progressHandler); + } + } + } + return compressedFile; + } + }; + + public abstract Path compress(Path file, LongConsumer progressHandler) throws IOException; + + private static long copy(InputStream from, OutputStream to, LongConsumer progress) throws IOException { + byte[] buf = new byte[1024 * 64]; + long total = 0; + long iterations = 0; + while (true) { + int r = from.read(buf); + if (r == -1) { + break; + } + to.write(buf, 0, r); + total += r; + + // report progress every 5MB + if (iterations++ % ((1024 / 64) * 5) == 0) { + progress.accept(total); + } + } + return total; + } + } + +} 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/MemoryModule.java deleted file mode 100644 index 4a87eaf..0000000 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/MemoryModule.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * This file is part of spark. - * - * Copyright (c) lucko (Luck) - * 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 . - */ - -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.command.Command; -import me.lucko.spark.common.command.CommandModule; -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.text.TextComponent; -import net.kyori.text.event.ClickEvent; -import net.kyori.text.format.TextColor; -import okhttp3.MediaType; -import org.tukaani.xz.LZMA2Options; -import org.tukaani.xz.LZMAOutputStream; -import org.tukaani.xz.XZOutputStream; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Iterator; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Consumer; -import java.util.function.LongConsumer; -import java.util.zip.GZIPOutputStream; - -public class MemoryModule implements CommandModule { - private static final MediaType SPARK_HEAP_MEDIA_TYPE = MediaType.parse("application/x-spark-heap"); - - @Override - public void registerCommands(Consumer consumer) { - 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(TextComponent.of("Running garbage collector...")); - System.gc(); - } - - resp.broadcastPrefixed(TextComponent.of("Creating a new heap dump summary, please wait...")); - - HeapDumpSummary heapDump; - try { - heapDump = HeapDumpSummary.createNew(); - } catch (Exception e) { - resp.broadcastPrefixed(TextComponent.of("An error occurred whilst inspecting the heap.", TextColor.RED)); - e.printStackTrace(); - return; - } - - byte[] output = heapDump.formCompressedDataPayload(sender); - try { - String key = SparkPlatform.BYTEBIN_CLIENT.postContent(output, SPARK_HEAP_MEDIA_TYPE, false).key(); - String url = SparkPlatform.VIEWER_URL + key; - - resp.broadcastPrefixed(TextComponent.of("Heap dump summmary output:", TextColor.GOLD)); - resp.broadcast(TextComponent.builder(url) - .color(TextColor.GRAY) - .clickEvent(ClickEvent.openUrl(url)) - .build() - ); - - platform.getActivityLog().addToLog(Activity.urlActivity(sender, System.currentTimeMillis(), "Heap dump summary", url)); - } catch (IOException e) { - resp.broadcastPrefixed(TextComponent.of("An error occurred whilst uploading the data.", TextColor.RED)); - e.printStackTrace(); - } - }); - }) - .tabCompleter((platform, sender, arguments) -> TabCompleter.completeForOpts(arguments, "--run-gc-before")) - .build() - ); - - consumer.accept(Command.builder() - .aliases("heapdump") - .argumentUsage("compress", "type") - .argumentUsage("run-gc-before", null) - .argumentUsage("include-non-live", null) - .executor((platform, sender, resp, 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(TextComponent.of("Running garbage collector...")); - System.gc(); - } - - resp.broadcastPrefixed(TextComponent.of("Creating a new heap dump, please wait...")); - - try { - HeapDump.dumpHeap(file, liveOnly); - } catch (Exception e) { - resp.broadcastPrefixed(TextComponent.of("An error occurred whilst creating a heap dump.", TextColor.RED)); - e.printStackTrace(); - return; - } - - resp.broadcastPrefixed(TextComponent.builder("Heap dump written to: ", TextColor.GOLD) - .append(TextComponent.of(file.toString(), TextColor.GRAY)) - .build() - ); - platform.getActivityLog().addToLog(Activity.fileActivity(sender, System.currentTimeMillis(), "Heap dump", file.toString())); - - - CompressionMethod compress = null; - Iterator compressArgs = arguments.stringFlag("compress").iterator(); - if (compressArgs.hasNext()) { - try { - compress = CompressionMethod.valueOf(compressArgs.next().toUpperCase()); - } catch (IllegalArgumentException e) { - // ignore - } - } - - if (compress != null) { - resp.broadcastPrefixed(TextComponent.of("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(TextComponent.builder("").color(TextColor.GRAY) - .append(TextComponent.of("Compressed ")) - .append(TextComponent.of(FormatUtil.formatBytes(progress), TextColor.GOLD)) - .append(TextComponent.of(" / ")) - .append(TextComponent.of(FormatUtil.formatBytes(size), TextColor.GOLD)) - .append(TextComponent.of(" so far... (")) - .append(TextComponent.of(FormatUtil.percent(progress, size), TextColor.GREEN)) - .append(TextComponent.of(")")) - .build() - ); - }); - } - }; - - Path compressedFile = compress.compress(file, progressHandler); - long compressedSize = Files.size(compressedFile); - - resp.broadcastPrefixed(TextComponent.builder("").color(TextColor.GRAY) - .append(TextComponent.of("Compression complete: ")) - .append(TextComponent.of(FormatUtil.formatBytes(size), TextColor.GOLD)) - .append(TextComponent.of(" --> ")) - .append(TextComponent.of(FormatUtil.formatBytes(compressedSize), TextColor.GOLD)) - .append(TextComponent.of(" (")) - .append(TextComponent.of(FormatUtil.percent(compressedSize, size), TextColor.GREEN)) - .append(TextComponent.of(")")) - .build() - ); - - resp.broadcastPrefixed(TextComponent.builder("Compressed heap dump written to: ", TextColor.GOLD) - .append(TextComponent.of(compressedFile.toString(), TextColor.GRAY)) - .build() - ); - } catch (IOException e) { - e.printStackTrace(); - } - } - }); - }) - .tabCompleter((platform, sender, arguments) -> TabCompleter.completeForOpts(arguments, "--compress", "--run-gc-before", "--include-non-live")) - .build() - ); - } - - public enum CompressionMethod { - GZIP { - @Override - public Path compress(Path file, LongConsumer progressHandler) throws IOException { - Path compressedFile = file.getParent().resolve(file.getFileName().toString() + ".gz"); - try (InputStream in = Files.newInputStream(file)) { - try (OutputStream out = Files.newOutputStream(compressedFile)) { - try (GZIPOutputStream compressionOut = new GZIPOutputStream(out, 1024 * 64)) { - copy(in, compressionOut, progressHandler); - } - } - } - return compressedFile; - } - }, - XZ { - @Override - public Path compress(Path file, LongConsumer progressHandler) throws IOException { - Path compressedFile = file.getParent().resolve(file.getFileName().toString() + ".xz"); - try (InputStream in = Files.newInputStream(file)) { - try (OutputStream out = Files.newOutputStream(compressedFile)) { - try (XZOutputStream compressionOut = new XZOutputStream(out, new LZMA2Options())) { - copy(in, compressionOut, progressHandler); - } - } - } - return compressedFile; - } - }, - LZMA { - @Override - public Path compress(Path file, LongConsumer progressHandler) throws IOException { - Path compressedFile = file.getParent().resolve(file.getFileName().toString() + ".lzma"); - try (InputStream in = Files.newInputStream(file)) { - try (OutputStream out = Files.newOutputStream(compressedFile)) { - try (LZMAOutputStream compressionOut = new LZMAOutputStream(out, new LZMA2Options(), true)) { - copy(in, compressionOut, progressHandler); - } - } - } - return compressedFile; - } - }; - - public abstract Path compress(Path file, LongConsumer progressHandler) throws IOException; - - private static long copy(InputStream from, OutputStream to, LongConsumer progress) throws IOException { - byte[] buf = new byte[1024 * 64]; - long total = 0; - long iterations = 0; - while (true) { - int r = from.read(buf); - if (r == -1) { - break; - } - to.write(buf, 0, r); - total += r; - - // report progress every 5MB - if (iterations++ % ((1024 / 64) * 5) == 0) { - progress.accept(total); - } - } - return total; - } - } - -} 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 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(); } -- cgit