From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Mon, 29 Feb 2016 21:02:09 -0600 Subject: [PATCH] Paper command diff --git a/src/main/java/io/papermc/paper/command/CommandUtil.java b/src/main/java/io/papermc/paper/command/CommandUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..953c30500892e5f0c55b8597bc708ea85bf56d6e --- /dev/null +++ b/src/main/java/io/papermc/paper/command/CommandUtil.java @@ -0,0 +1,69 @@ +package io.papermc.paper.command; + +import com.google.common.base.Functions; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import net.minecraft.resources.ResourceLocation; +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) +public final class CommandUtil { + private CommandUtil() { + } + + // Code from Mojang - copyright them + public static List getListMatchingLast( + final CommandSender sender, + final String[] args, + final String... matches + ) { + return getListMatchingLast(sender, args, Arrays.asList(matches)); + } + + public static boolean matches(final String s, final String s1) { + return s1.regionMatches(true, 0, s, 0, s.length()); + } + + public static List getListMatchingLast( + final CommandSender sender, + final String[] strings, + final Collection collection + ) { + String last = strings[strings.length - 1]; + ArrayList results = Lists.newArrayList(); + + if (!collection.isEmpty()) { + Iterator iterator = Iterables.transform(collection, Functions.toStringFunction()).iterator(); + + while (iterator.hasNext()) { + String s1 = (String) iterator.next(); + + if (matches(last, s1) && (sender.hasPermission(PaperCommand.BASE_PERM + s1) || sender.hasPermission("bukkit.command.paper"))) { + results.add(s1); + } + } + + if (results.isEmpty()) { + iterator = collection.iterator(); + + while (iterator.hasNext()) { + Object object = iterator.next(); + + if (object instanceof ResourceLocation && matches(last, ((ResourceLocation) object).getPath())) { + results.add(String.valueOf(object)); + } + } + } + } + + return results; + } + // end copy stuff +} diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..b3a58bf4b654e336826dc04da9e2f80ff8b9a9a7 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/PaperCommand.java @@ -0,0 +1,145 @@ +package io.papermc.paper.command; + +import io.papermc.paper.command.subcommands.EntityCommand; +import io.papermc.paper.command.subcommands.HeapDumpCommand; +import io.papermc.paper.command.subcommands.ReloadCommand; +import io.papermc.paper.command.subcommands.VersionCommand; +import it.unimi.dsi.fastutil.Pair; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import net.minecraft.Util; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.bukkit.plugin.PluginManager; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.RED; + +@DefaultQualifier(NonNull.class) +public final class PaperCommand extends Command { + static final String BASE_PERM = "bukkit.command.paper."; + // subcommand label -> subcommand + private static final Map SUBCOMMANDS = Util.make(() -> { + final Map, PaperSubcommand> commands = new HashMap<>(); + + commands.put(Set.of("heap"), new HeapDumpCommand()); + commands.put(Set.of("entity"), new EntityCommand()); + commands.put(Set.of("reload"), new ReloadCommand()); + commands.put(Set.of("version"), new VersionCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + }); + // alias -> subcommand label + private static final Map ALIASES = Util.make(() -> { + final Map> aliases = new HashMap<>(); + + aliases.put("version", Set.of("ver")); + + return aliases.entrySet().stream() + .flatMap(entry -> entry.getValue().stream().map(s -> Map.entry(s, entry.getKey()))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + }); + + public PaperCommand(final String name) { + super(name); + this.description = "Paper related commands"; + this.usageMessage = "/paper [" + String.join(" | ", SUBCOMMANDS.keySet()) + "]"; + final List permissions = new ArrayList<>(); + permissions.add("bukkit.command.paper"); + permissions.addAll(SUBCOMMANDS.keySet().stream().map(s -> BASE_PERM + s).toList()); + this.setPermission(String.join(";", permissions)); + final PluginManager pluginManager = Bukkit.getServer().getPluginManager(); + for (final String perm : permissions) { + pluginManager.addPermission(new Permission(perm, PermissionDefault.OP)); + } + } + + private static boolean testPermission(final CommandSender sender, final String permission) { + if (sender.hasPermission(BASE_PERM + permission) || sender.hasPermission("bukkit.command.paper")) { + return true; + } + sender.sendMessage(text("I'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error.", RED)); + return false; + } + + @Override + public List tabComplete( + final CommandSender sender, + final String alias, + final String[] args, + final @Nullable Location location + ) throws IllegalArgumentException { + if (args.length <= 1) { + return CommandUtil.getListMatchingLast(sender, args, SUBCOMMANDS.keySet()); + } + + final @Nullable Pair subCommand = resolveCommand(args[0]); + if (subCommand != null) { + return subCommand.second().tabComplete(sender, subCommand.first(), Arrays.copyOfRange(args, 1, args.length)); + } + + return Collections.emptyList(); + } + + @Override + public boolean execute( + final CommandSender sender, + final String commandLabel, + final String[] args + ) { + if (!testPermission(sender)) { + return true; + } + + if (args.length == 0) { + sender.sendMessage(text("Usage: " + this.usageMessage, RED)); + return false; + } + final @Nullable Pair subCommand = resolveCommand(args[0]); + + if (subCommand == null) { + sender.sendMessage(text("Usage: " + this.usageMessage, RED)); + return false; + } + + if (!testPermission(sender, subCommand.first())) { + return true; + } + final String[] choppedArgs = Arrays.copyOfRange(args, 1, args.length); + return subCommand.second().execute(sender, subCommand.first(), choppedArgs); + } + + private static @Nullable Pair resolveCommand(String label) { + label = label.toLowerCase(Locale.ENGLISH); + @Nullable PaperSubcommand subCommand = SUBCOMMANDS.get(label); + if (subCommand == null) { + final @Nullable String command = ALIASES.get(label); + if (command != null) { + label = command; + subCommand = SUBCOMMANDS.get(command); + } + } + + if (subCommand != null) { + return Pair.of(label, subCommand); + } + + return null; + } +} diff --git a/src/main/java/io/papermc/paper/command/PaperCommands.java b/src/main/java/io/papermc/paper/command/PaperCommands.java new file mode 100644 index 0000000000000000000000000000000000000000..6a00f3d38da8107825ab1d405f337fd077b09f72 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/PaperCommands.java @@ -0,0 +1,27 @@ +package io.papermc.paper.command; + +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.Command; + +import java.util.HashMap; +import java.util.Map; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) +public final class PaperCommands { + + private PaperCommands() { + } + + private static final Map COMMANDS = new HashMap<>(); + static { + COMMANDS.put("paper", new PaperCommand("paper")); + } + + public static void registerCommands(final MinecraftServer server) { + COMMANDS.forEach((s, command) -> { + server.server.getCommandMap().register(s, "Paper", command); + }); + } +} diff --git a/src/main/java/io/papermc/paper/command/PaperSubcommand.java b/src/main/java/io/papermc/paper/command/PaperSubcommand.java new file mode 100644 index 0000000000000000000000000000000000000000..6ff5d42a866d2752c73a766815aa190b2b0dc36f --- /dev/null +++ b/src/main/java/io/papermc/paper/command/PaperSubcommand.java @@ -0,0 +1,16 @@ +package io.papermc.paper.command; + +import java.util.Collections; +import java.util.List; +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) +public interface PaperSubcommand { + boolean execute(CommandSender sender, String subCommand, String[] args); + + default List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { + return Collections.emptyList(); + } +} diff --git a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..68f99e93ed3e843b4001a7a27620f88a48b85e67 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java @@ -0,0 +1,145 @@ +package io.papermc.paper.command.subcommands; + +import com.google.common.collect.Maps; +import io.papermc.paper.command.CommandUtil; +import io.papermc.paper.command.PaperSubcommand; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.ChunkPos; +import org.apache.commons.lang3.tuple.MutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.entity.Player; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.RED; + +@DefaultQualifier(NonNull.class) +public final class EntityCommand implements PaperSubcommand { + @Override + public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { + this.listEntities(sender, args); + return true; + } + + @Override + public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { + if (args.length == 1) { + return CommandUtil.getListMatchingLast(sender, args, "help", "list"); + } else if (args.length == 2) { + return CommandUtil.getListMatchingLast(sender, args, Registry.ENTITY_TYPE.keySet().stream().map(ResourceLocation::toString).sorted().toArray(String[]::new)); + } + return Collections.emptyList(); + } + + /* + * Ported from MinecraftForge - author: LexManos - License: LGPLv2.1 + */ + private void listEntities(final CommandSender sender, final String[] args) { + // help + if (args.length < 1 || !args[0].toLowerCase(Locale.ENGLISH).equals("list")) { + sender.sendMessage(text("Use /paper entity [list] help for more information on a specific command", RED)); + return; + } + + if ("list".equals(args[0].toLowerCase(Locale.ENGLISH))) { + String filter = "*"; + if (args.length > 1) { + if (args[1].toLowerCase(Locale.ENGLISH).equals("help")) { + sender.sendMessage(text("Use /paper entity list [filter] [worldName] to get entity info that matches the optional filter.", RED)); + return; + } + filter = args[1]; + } + final String cleanfilter = filter.replace("?", ".?").replace("*", ".*?"); + Set names = Registry.ENTITY_TYPE.keySet().stream() + .filter(n -> n.toString().matches(cleanfilter)) + .collect(Collectors.toSet()); + if (names.isEmpty()) { + sender.sendMessage(text("Invalid filter, does not match any entities. Use /paper entity list for a proper list", RED)); + sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED)); + return; + } + String worldName; + if (args.length > 2) { + worldName = args[2]; + } else if (sender instanceof Player) { + worldName = ((Player) sender).getWorld().getName(); + } else { + sender.sendMessage(text("Please specify the name of a world", RED)); + sender.sendMessage(text("To do so without a filter, specify '*' as the filter", RED)); + sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED)); + return; + } + Map>> list = Maps.newHashMap(); + @Nullable World bukkitWorld = Bukkit.getWorld(worldName); + if (bukkitWorld == null) { + sender.sendMessage(text("Could not load world for " + worldName + ". Please select a valid world.", RED)); + sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED)); + return; + } + ServerLevel world = ((CraftWorld) bukkitWorld).getHandle(); + Map nonEntityTicking = Maps.newHashMap(); + ServerChunkCache chunkProviderServer = world.getChunkSource(); + world.getAllEntities().forEach(e -> { + ResourceLocation key = EntityType.getKey(e.getType()); + + MutablePair> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap())); + ChunkPos chunk = e.chunkPosition(); + info.left++; + info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); + if (!chunkProviderServer.isPositionTicking(e)) { + nonEntityTicking.merge(key, 1, Integer::sum); + } + }); + if (names.size() == 1) { + ResourceLocation name = names.iterator().next(); + Pair> info = list.get(name); + int nonTicking = nonEntityTicking.getOrDefault(name, 0); + if (info == null) { + sender.sendMessage(text("No entities found.", RED)); + return; + } + sender.sendMessage("Entity: " + name + " Total Ticking: " + (info.getLeft() - nonTicking) + ", Total Non-Ticking: " + nonTicking); + info.getRight().entrySet().stream() + .sorted((a, b) -> !a.getValue().equals(b.getValue()) ? b.getValue() - a.getValue() : a.getKey().toString().compareTo(b.getKey().toString())) + .limit(10).forEach(e -> sender.sendMessage(" " + e.getValue() + ": " + e.getKey().x + ", " + e.getKey().z + (chunkProviderServer.isPositionTicking(e.getKey().toLong()) ? " (Ticking)" : " (Non-Ticking)"))); + } else { + List> info = list.entrySet().stream() + .filter(e -> names.contains(e.getKey())) + .map(e -> Pair.of(e.getKey(), e.getValue().left)) + .sorted((a, b) -> !a.getRight().equals(b.getRight()) ? b.getRight() - a.getRight() : a.getKey().toString().compareTo(b.getKey().toString())) + .toList(); + + if (info.isEmpty()) { + sender.sendMessage(text("No entities found.", RED)); + return; + } + + int count = info.stream().mapToInt(Pair::getRight).sum(); + int nonTickingCount = nonEntityTicking.values().stream().mapToInt(Integer::intValue).sum(); + sender.sendMessage("Total Ticking: " + (count - nonTickingCount) + ", Total Non-Ticking: " + nonTickingCount); + info.forEach(e -> { + int nonTicking = nonEntityTicking.getOrDefault(e.getKey(), 0); + sender.sendMessage(" " + (e.getValue() - nonTicking) + " (" + nonTicking + ") " + ": " + e.getKey()); + }); + sender.sendMessage("* First number is ticking entities, second number is non-ticking entities"); + } + } + } +} diff --git a/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java b/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..cd2e4d792e972b8bf1e07b8961594a670ae949cf --- /dev/null +++ b/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java @@ -0,0 +1,38 @@ +package io.papermc.paper.command.subcommands; + +import io.papermc.paper.command.PaperSubcommand; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.CraftServer; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +import static net.kyori.adventure.text.Component.text; +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.YELLOW; + +@DefaultQualifier(NonNull.class) +public final class HeapDumpCommand implements PaperSubcommand { + @Override + public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { + this.dumpHeap(sender); + return true; + } + + private void dumpHeap(final CommandSender sender) { + java.nio.file.Path dir = java.nio.file.Paths.get("./dumps"); + String name = "heap-dump-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()); + + Command.broadcastCommandMessage(sender, text("Writing JVM heap data...", YELLOW)); + + java.nio.file.Path file = CraftServer.dumpHeap(dir, name); + if (file != null) { + Command.broadcastCommandMessage(sender, text("Heap dump saved to " + file, GREEN)); + } else { + Command.broadcastCommandMessage(sender, text("Failed to write heap dump, see server log for details", RED)); + } + } +} diff --git a/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..bd68139ae635f2ad7ec8e7a21e0056a139c4c62e --- /dev/null +++ b/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java @@ -0,0 +1,33 @@ +package io.papermc.paper.command.subcommands; + +import io.papermc.paper.command.PaperSubcommand; +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.CraftServer; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.GREEN; +import static net.kyori.adventure.text.format.NamedTextColor.RED; + +@DefaultQualifier(NonNull.class) +public final class ReloadCommand implements PaperSubcommand { + @Override + public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { + this.doReload(sender); + return true; + } + + private void doReload(final CommandSender sender) { + Command.broadcastCommandMessage(sender, text("Please note that this command is not supported and may cause issues.", RED)); + Command.broadcastCommandMessage(sender, text("If you encounter any issues please use the /stop command to restart your server.", RED)); + + MinecraftServer server = ((CraftServer) sender.getServer()).getServer(); + server.paperConfigurations.reloadConfigs(server); + server.server.reloadCount++; + + Command.broadcastCommandMessage(sender, text("Paper config reload complete.", GREEN)); + } +} diff --git a/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java b/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..ae60bd96b5284d54676d8e7e4dd5d170b526ec1e --- /dev/null +++ b/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java @@ -0,0 +1,21 @@ +package io.papermc.paper.command.subcommands; + +import io.papermc.paper.command.PaperSubcommand; +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) +public final class VersionCommand implements PaperSubcommand { + @Override + public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { + final @Nullable Command ver = MinecraftServer.getServer().server.getCommandMap().getCommand("version"); + if (ver != null) { + ver.execute(sender, "paper", new String[0]); + } + return true; + } +} diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java index 08ae7a96e93c0d8547f560b3f753804525621c6b..8f29bb843fc456384f7b4e216afca5018fb7f794 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java @@ -191,6 +191,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface // Paper start paperConfigurations.initializeGlobalConfiguration(); paperConfigurations.initializeWorldDefaultsConfiguration(); + io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper end this.setPvpAllowed(dedicatedserverproperties.pvp); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 837fb51698e6650c6df720f798b7196322b6e7bb..80333fe069e417ef692cb7b80292ed42d6e820a1 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -894,6 +894,7 @@ public final class CraftServer implements Server { this.commandMap.clearCommands(); this.reloadData(); org.spigotmc.SpigotConfig.registerCommands(); // Spigot + io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); @@ -2404,6 +2405,34 @@ public final class CraftServer implements Server { // Spigot end // Paper start + @SuppressWarnings({"rawtypes", "unchecked"}) + public static java.nio.file.Path dumpHeap(java.nio.file.Path dir, String name) { + try { + java.nio.file.Files.createDirectories(dir); + + javax.management.MBeanServer server = java.lang.management.ManagementFactory.getPlatformMBeanServer(); + java.nio.file.Path file; + + try { + Class clazz = Class.forName("openj9.lang.management.OpenJ9DiagnosticsMXBean"); + Object openj9Mbean = java.lang.management.ManagementFactory.newPlatformMXBeanProxy(server, "openj9.lang.management:type=OpenJ9Diagnostics", clazz); + java.lang.reflect.Method m = clazz.getMethod("triggerDumpToFile", String.class, String.class); + file = dir.resolve(name + ".phd"); + m.invoke(openj9Mbean, "heap", file.toString()); + } catch (ClassNotFoundException e) { + Class clazz = Class.forName("com.sun.management.HotSpotDiagnosticMXBean"); + Object hotspotMBean = java.lang.management.ManagementFactory.newPlatformMXBeanProxy(server, "com.sun.management:type=HotSpotDiagnostic", clazz); + java.lang.reflect.Method m = clazz.getMethod("dumpHeap", String.class, boolean.class); + file = dir.resolve(name + ".hprof"); + m.invoke(hotspotMBean, file.toString(), true); + } + + return file; + } catch (Throwable t) { + Bukkit.getLogger().log(Level.SEVERE, "Could not write heap", t); + return null; + } + } private Iterable adventure$audiences; @Override public Iterable audiences() {