Improvements for Dump paper commands (#12512)

This commit is contained in:
Pedro 2025-05-06 16:45:17 -04:00 committed by GitHub
parent 42a2a6c2b5
commit 753cff7c8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 82 additions and 34 deletions

View File

@ -2,12 +2,13 @@ package io.papermc.paper.command.subcommands;
import com.destroystokyo.paper.util.SneakyThrow; import com.destroystokyo.paper.util.SneakyThrow;
import io.papermc.paper.command.PaperSubcommand; import io.papermc.paper.command.PaperSubcommand;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
@ -16,6 +17,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -23,6 +25,7 @@ import org.bukkit.event.HandlerList;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredListener; import org.bukkit.plugin.RegisteredListener;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.DefaultQualifier; import org.checkerframework.framework.qual.DefaultQualifier;
import static net.kyori.adventure.text.Component.newline; import static net.kyori.adventure.text.Component.newline;
@ -35,6 +38,8 @@ import static net.kyori.adventure.text.format.NamedTextColor.WHITE;
@DefaultQualifier(NonNull.class) @DefaultQualifier(NonNull.class)
public final class DumpListenersCommand implements PaperSubcommand { public final class DumpListenersCommand implements PaperSubcommand {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss");
private static final String COMMAND_ARGUMENT_TO_FILE = "tofile";
private static final MethodHandle EVENT_TYPES_HANDLE; private static final MethodHandle EVENT_TYPES_HANDLE;
static { static {
@ -49,7 +54,7 @@ public final class DumpListenersCommand implements PaperSubcommand {
@Override @Override
public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
if (args.length >= 1 && args[0].equals("tofile")) { if (args.length >= 1 && args[0].equals(COMMAND_ARGUMENT_TO_FILE)) {
this.dumpToFile(sender); this.dumpToFile(sender);
return true; return true;
} }
@ -58,45 +63,69 @@ public final class DumpListenersCommand implements PaperSubcommand {
} }
private void dumpToFile(final CommandSender sender) { private void dumpToFile(final CommandSender sender) {
final File file = new File("debug/listeners-" Path parent = Path.of("debug");
+ DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt"); Path path = parent.resolve("listeners-" + FORMATTER.format(LocalDateTime.now()) + ".txt");
file.getParentFile().mkdirs(); sender.sendMessage(
try (final PrintWriter writer = new PrintWriter(file)) { text("Writing listeners into directory", GREEN)
for (final String eventClass : eventClassNames()) { .appendSpace()
final HandlerList handlers; .append(
try { text(parent.toString(), WHITE)
handlers = (HandlerList) findClass(eventClass).getMethod("getHandlerList").invoke(null); .hoverEvent(text("Click to copy the full path of debug directory", WHITE))
} catch (final ReflectiveOperationException e) { .clickEvent(ClickEvent.copyToClipboard(parent.toAbsolutePath().toString()))
continue; )
} );
if (handlers.getRegisteredListeners().length != 0) { try {
writer.println(eventClass); Files.createDirectories(parent);
} Files.createFile(path);
for (final RegisteredListener registeredListener : handlers.getRegisteredListeners()) { try (final PrintWriter writer = new PrintWriter(path.toFile())){
writer.println(" - " + registeredListener); for (final String eventClass : eventClassNames()) {
final HandlerList handlers;
try {
handlers = (HandlerList) findClass(eventClass).getMethod("getHandlerList").invoke(null);
} catch (final ReflectiveOperationException e) {
continue;
}
if (handlers.getRegisteredListeners().length != 0) {
writer.println(eventClass);
}
for (final RegisteredListener registeredListener : handlers.getRegisteredListeners()) {
writer.println(" - " + registeredListener);
}
} }
} }
} catch (final IOException ex) { } catch (final IOException ex) {
throw new RuntimeException(ex); sender.sendMessage(text("Failed to write dumped listener! See the console for more info.", RED));
MinecraftServer.LOGGER.warn("Error occurred while dumping listeners", ex);
return;
} }
sender.sendMessage(text("Dumped listeners to " + file, GREEN)); sender.sendMessage(
text("Successfully written listeners into", GREEN)
.appendSpace()
.append(
text(path.toString(), WHITE)
.hoverEvent(text("Click to copy the full path of the file", WHITE))
.clickEvent(ClickEvent.copyToClipboard(path.toAbsolutePath().toString()))
)
);
} }
private void doDumpListeners(final CommandSender sender, final String[] args) { private void doDumpListeners(final CommandSender sender, final String[] args) {
if (args.length == 0) { if (args.length == 0) {
sender.sendMessage(text("Usage: /paper dumplisteners tofile|<className>", RED)); sender.sendMessage(text("Usage: /paper dumplisteners " + COMMAND_ARGUMENT_TO_FILE + "|<className>", RED));
return; return;
} }
final String className = args[0];
try { try {
final HandlerList handlers = (HandlerList) findClass(args[0]).getMethod("getHandlerList").invoke(null); final HandlerList handlers = (HandlerList) findClass(className).getMethod("getHandlerList").invoke(null);
if (handlers.getRegisteredListeners().length == 0) { if (handlers.getRegisteredListeners().length == 0) {
sender.sendMessage(text(args[0] + " does not have any registered listeners.")); sender.sendMessage(text(className + " does not have any registered listeners."));
return; return;
} }
sender.sendMessage(text("Listeners for " + args[0] + ":")); sender.sendMessage(text("Listeners for " + className + ":"));
for (final RegisteredListener listener : handlers.getRegisteredListeners()) { for (final RegisteredListener listener : handlers.getRegisteredListeners()) {
final Component hoverText = text("Priority: " + listener.getPriority().name() + " (" + listener.getPriority().getSlot() + ")", WHITE) final Component hoverText = text("Priority: " + listener.getPriority().name() + " (" + listener.getPriority().getSlot() + ")", WHITE)
@ -115,12 +144,12 @@ public final class DumpListenersCommand implements PaperSubcommand {
sender.sendMessage(text("Total listeners: " + handlers.getRegisteredListeners().length)); sender.sendMessage(text("Total listeners: " + handlers.getRegisteredListeners().length));
} catch (final ClassNotFoundException e) { } catch (final ClassNotFoundException e) {
sender.sendMessage(text("Unable to find a class named '" + args[0] + "'. Make sure to use the fully qualified name.", RED)); sender.sendMessage(text("Unable to find a class named '" + className + "'. Make sure to use the fully qualified name.", RED));
} catch (final NoSuchMethodException e) { } catch (final NoSuchMethodException e) {
sender.sendMessage(text("Class '" + args[0] + "' does not have a valid getHandlerList method.", RED)); sender.sendMessage(text("Class '" + className + "' does not have a valid getHandlerList method.", RED));
} catch (final ReflectiveOperationException e) { } catch (final ReflectiveOperationException e) {
sender.sendMessage(text("Something went wrong, see the console for more details.", RED)); sender.sendMessage(text("Something went wrong, see the console for more details.", RED));
MinecraftServer.LOGGER.warn("Error occurred while dumping listeners for class " + args[0], e); MinecraftServer.LOGGER.warn("Error occurred while dumping listeners for class {}", className, e);
} }
} }
@ -137,7 +166,7 @@ public final class DumpListenersCommand implements PaperSubcommand {
private static List<String> suggestions() { private static List<String> suggestions() {
final List<String> ret = new ArrayList<>(); final List<String> ret = new ArrayList<>();
ret.add("tofile"); ret.add(COMMAND_ARGUMENT_TO_FILE);
ret.addAll(eventClassNames()); ret.addAll(eventClassNames());
return ret; return ret;
} }

View File

@ -5,6 +5,7 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive; import com.google.gson.JsonPrimitive;
import com.google.gson.Strictness;
import com.google.gson.internal.Streams; import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import io.papermc.paper.command.PaperSubcommand; import io.papermc.paper.command.PaperSubcommand;
@ -15,7 +16,6 @@ import io.papermc.paper.plugin.entrypoint.classloader.group.PaperPluginClassLoad
import io.papermc.paper.plugin.entrypoint.classloader.group.SimpleListPluginClassLoaderGroup; import io.papermc.paper.plugin.entrypoint.classloader.group.SimpleListPluginClassLoaderGroup;
import io.papermc.paper.plugin.entrypoint.classloader.group.SpigotPluginClassLoaderGroup; import io.papermc.paper.plugin.entrypoint.classloader.group.SpigotPluginClassLoaderGroup;
import io.papermc.paper.plugin.entrypoint.classloader.group.StaticPluginClassLoaderGroup; import io.papermc.paper.plugin.entrypoint.classloader.group.StaticPluginClassLoaderGroup;
import io.papermc.paper.plugin.entrypoint.dependency.GraphDependencyContext;
import io.papermc.paper.plugin.entrypoint.dependency.SimpleMetaDependencyTree; import io.papermc.paper.plugin.entrypoint.dependency.SimpleMetaDependencyTree;
import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; import io.papermc.paper.plugin.provider.entrypoint.DependencyContext;
import io.papermc.paper.plugin.entrypoint.strategy.modern.ModernPluginLoadingStrategy; import io.papermc.paper.plugin.entrypoint.strategy.modern.ModernPluginLoadingStrategy;
@ -27,12 +27,14 @@ import io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage;
import io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup; import io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup;
import io.papermc.paper.plugin.storage.ConfiguredProviderStorage; import io.papermc.paper.plugin.storage.ConfiguredProviderStorage;
import io.papermc.paper.plugin.storage.ProviderStorage; import io.papermc.paper.plugin.storage.ProviderStorage;
import net.kyori.adventure.text.event.ClickEvent;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier; import org.checkerframework.framework.qual.DefaultQualifier;
import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.io.StringWriter; import java.io.StringWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -47,6 +49,7 @@ import java.util.Map;
import static net.kyori.adventure.text.Component.text; 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.GREEN;
import static net.kyori.adventure.text.format.NamedTextColor.RED; import static net.kyori.adventure.text.format.NamedTextColor.RED;
import static net.kyori.adventure.text.format.NamedTextColor.WHITE;
@DefaultQualifier(NonNull.class) @DefaultQualifier(NonNull.class)
public final class DumpPluginsCommand implements PaperSubcommand { public final class DumpPluginsCommand implements PaperSubcommand {
@ -60,24 +63,40 @@ public final class DumpPluginsCommand implements PaperSubcommand {
private void dumpPlugins(final CommandSender sender, final String[] args) { private void dumpPlugins(final CommandSender sender, final String[] args) {
Path parent = Path.of("debug"); Path parent = Path.of("debug");
Path path = parent.resolve("plugin-info-" + FORMATTER.format(LocalDateTime.now()) + ".txt"); Path path = parent.resolve("plugin-info-" + FORMATTER.format(LocalDateTime.now()) + ".json");
try { try {
Files.createDirectories(parent); Files.createDirectories(parent);
Files.createFile(path); Files.createFile(path);
sender.sendMessage(text("Writing plugin information to " + path, GREEN)); sender.sendMessage(
text("Writing plugin information into directory", GREEN)
.appendSpace()
.append(
text(parent.toString(), WHITE)
.hoverEvent(text("Click to copy the full path of debug directory", WHITE))
.clickEvent(ClickEvent.copyToClipboard(parent.toAbsolutePath().toString()))
)
);
final JsonObject data = this.writeDebug(); final JsonObject data = this.writeDebug();
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setIndent(" "); jsonWriter.setIndent(" ");
jsonWriter.setLenient(false); jsonWriter.setStrictness(Strictness.STRICT);
Streams.write(data, jsonWriter); Streams.write(data, jsonWriter);
try (PrintStream out = new PrintStream(Files.newOutputStream(path), false, StandardCharsets.UTF_8)) { try (PrintStream out = new PrintStream(Files.newOutputStream(path), false, StandardCharsets.UTF_8)) {
out.print(stringWriter); out.print(stringWriter);
} }
sender.sendMessage(text("Successfully written plugin debug information!", GREEN)); sender.sendMessage(
text("Successfully written plugin debug information into", GREEN)
.appendSpace()
.append(
text(path.toString(), WHITE)
.hoverEvent(text("Click to copy the full path of the file", WHITE))
.clickEvent(ClickEvent.copyToClipboard(path.toAbsolutePath().toString()))
)
);
} catch (Throwable e) { } catch (Throwable e) {
sender.sendMessage(text("Failed to write plugin information! See the console for more info.", RED)); sender.sendMessage(text("Failed to write plugin information! See the console for more info.", RED));
MinecraftServer.LOGGER.warn("Error occurred while dumping plugin info", e); MinecraftServer.LOGGER.warn("Error occurred while dumping plugin info", e);
@ -97,6 +116,7 @@ public final class DumpPluginsCommand implements PaperSubcommand {
return root; return root;
} }
@SuppressWarnings("unchecked")
private void writeProviders(JsonObject root) { private void writeProviders(JsonObject root) {
JsonObject rootProviders = new JsonObject(); JsonObject rootProviders = new JsonObject();
root.add("providers", rootProviders); root.add("providers", rootProviders);
@ -116,7 +136,6 @@ public final class DumpPluginsCommand implements PaperSubcommand {
providerObj.addProperty("soft-dependencies", provider.getMeta().getPluginSoftDependencies().toString()); providerObj.addProperty("soft-dependencies", provider.getMeta().getPluginSoftDependencies().toString());
providerObj.addProperty("load-before", provider.getMeta().getLoadBeforePlugins().toString()); providerObj.addProperty("load-before", provider.getMeta().getLoadBeforePlugins().toString());
providers.add(providerObj); providers.add(providerObj);
pluginProviders.add((PluginProvider<Object>) provider); pluginProviders.add((PluginProvider<Object>) provider);
} }