diff options
Diffstat (limited to 'patches/removed/1.19.2-legacy-chunksystem/0016-Chunk-debug-command.patch')
-rw-r--r-- | patches/removed/1.19.2-legacy-chunksystem/0016-Chunk-debug-command.patch | 432 |
1 files changed, 432 insertions, 0 deletions
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0016-Chunk-debug-command.patch b/patches/removed/1.19.2-legacy-chunksystem/0016-Chunk-debug-command.patch new file mode 100644 index 0000000000..79581b2b72 --- /dev/null +++ b/patches/removed/1.19.2-legacy-chunksystem/0016-Chunk-debug-command.patch @@ -0,0 +1,432 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf <[email protected]> +Date: Sat, 1 Jun 2019 13:00:55 -0700 +Subject: [PATCH] Chunk debug command + +Prints all chunk information to a text file into the debug +folder in the root server folder. The format is in JSON, and +the data format is described in MCUtil#dumpChunks(File) + +The command will output server version and all online players to the +file as well. We do not log anything but the location, world and +username of the player. + +Also logs the value of these config values (note not all are paper's): +- keep spawn loaded value +- spawn radius +- view distance + +Each chunk has the following logged: +- Coordinate +- Ticket level & its corresponding state +- Whether it is queued for unload +- Chunk status (may be unloaded) +- All tickets on the chunk + +Example log: +https://gist.githubusercontent.com/Spottedleaf/0131e7710ffd5d531e5fd246c3367380/raw/169ae1b2e240485f99bc7a6bd8e78d90e3af7397/chunks-2019-06-01_19.57.05.txt + +For references on certain keywords (ticket, status, etc), please see: + +https://bugs.mojang.com/browse/MC-141484?focusedCommentId=528273&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-528273 +https://bugs.mojang.com/browse/MC-141484?focusedCommentId=528577&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-528577 + +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index b3a58bf4b654e336826dc04da9e2f80ff8b9a9a7..8e773f522521d2dd6349c87b582a3337b76f161f 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -1,5 +1,6 @@ + package io.papermc.paper.command; + ++import io.papermc.paper.command.subcommands.ChunkDebugCommand; + import io.papermc.paper.command.subcommands.EntityCommand; + import io.papermc.paper.command.subcommands.HeapDumpCommand; + import io.papermc.paper.command.subcommands.ReloadCommand; +@@ -40,6 +41,7 @@ public final class PaperCommand extends Command { + commands.put(Set.of("entity"), new EntityCommand()); + commands.put(Set.of("reload"), new ReloadCommand()); + commands.put(Set.of("version"), new VersionCommand()); ++ commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..28a9550449be9a212f054b02e43fbd8a3781efcf +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java +@@ -0,0 +1,166 @@ ++package io.papermc.paper.command.subcommands; ++ ++import io.papermc.paper.command.CommandUtil; ++import io.papermc.paper.command.PaperSubcommand; ++import java.io.File; ++import java.time.LocalDateTime; ++import java.time.format.DateTimeFormatter; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; ++import java.util.Locale; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ServerLevel; ++import org.bukkit.Bukkit; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftWorld; ++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.BLUE; ++import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class ChunkDebugCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ switch (subCommand) { ++ case "debug" -> this.doDebug(sender, args); ++ case "chunkinfo" -> this.doChunkInfo(sender, args); ++ } ++ return true; ++ } ++ ++ @Override ++ public List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ switch (subCommand) { ++ case "debug" -> { ++ if (args.length == 1) { ++ return CommandUtil.getListMatchingLast(sender, args, "help", "chunks"); ++ } ++ } ++ case "chunkinfo" -> { ++ List<String> worldNames = new ArrayList<>(); ++ worldNames.add("*"); ++ for (org.bukkit.World world : Bukkit.getWorlds()) { ++ worldNames.add(world.getName()); ++ } ++ if (args.length == 1) { ++ return CommandUtil.getListMatchingLast(sender, args, worldNames); ++ } ++ } ++ } ++ return Collections.emptyList(); ++ } ++ ++ private void doChunkInfo(final CommandSender sender, final String[] args) { ++ List<org.bukkit.World> worlds; ++ if (args.length < 1 || args[0].equals("*")) { ++ worlds = Bukkit.getWorlds(); ++ } else { ++ worlds = new ArrayList<>(args.length); ++ for (final String arg : args) { ++ org.bukkit.@Nullable World world = Bukkit.getWorld(arg); ++ if (world == null) { ++ sender.sendMessage(text("World '" + arg + "' is invalid", RED)); ++ return; ++ } ++ worlds.add(world); ++ } ++ } ++ ++ int accumulatedTotal = 0; ++ int accumulatedInactive = 0; ++ int accumulatedBorder = 0; ++ int accumulatedTicking = 0; ++ int accumulatedEntityTicking = 0; ++ ++ for (final org.bukkit.World bukkitWorld : worlds) { ++ final ServerLevel world = ((CraftWorld) bukkitWorld).getHandle(); ++ ++ int total = 0; ++ int inactive = 0; ++ int border = 0; ++ int ticking = 0; ++ int entityTicking = 0; ++ ++ for (final ChunkHolder chunk : net.minecraft.server.ChunkSystem.getVisibleChunkHolders(world)) { ++ if (chunk.getFullChunkNowUnchecked() == null) { ++ continue; ++ } ++ ++ ++total; ++ ++ ChunkHolder.FullChunkStatus state = chunk.getFullStatus(); ++ ++ switch (state) { ++ case INACCESSIBLE -> ++inactive; ++ case BORDER -> ++border; ++ case TICKING -> ++ticking; ++ case ENTITY_TICKING -> ++entityTicking; ++ } ++ } ++ ++ accumulatedTotal += total; ++ accumulatedInactive += inactive; ++ accumulatedBorder += border; ++ accumulatedTicking += ticking; ++ accumulatedEntityTicking += entityTicking; ++ ++ sender.sendMessage(text().append(text("Chunks in ", BLUE), text(bukkitWorld.getName(), GREEN), text(":"))); ++ sender.sendMessage(text().color(DARK_AQUA).append( ++ text("Total: ", BLUE), text(total), ++ text(" Inactive: ", BLUE), text(inactive), ++ text(" Border: ", BLUE), text(border), ++ text(" Ticking: ", BLUE), text(ticking), ++ text(" Entity: ", BLUE), text(entityTicking) ++ )); ++ } ++ if (worlds.size() > 1) { ++ sender.sendMessage(text().append(text("Chunks in ", BLUE), text("all listed worlds", GREEN), text(":", DARK_AQUA))); ++ sender.sendMessage(text().color(DARK_AQUA).append( ++ text("Total: ", BLUE), text(accumulatedTotal), ++ text(" Inactive: ", BLUE), text(accumulatedInactive), ++ text(" Border: ", BLUE), text(accumulatedBorder), ++ text(" Ticking: ", BLUE), text(accumulatedTicking), ++ text(" Entity: ", BLUE), text(accumulatedEntityTicking) ++ )); ++ } ++ } ++ ++ private void doDebug(final CommandSender sender, final String[] args) { ++ if (args.length < 1) { ++ sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED)); ++ return; ++ } ++ ++ final String debugType = args[0].toLowerCase(Locale.ENGLISH); ++ switch (debugType) { ++ case "chunks" -> { ++ if (args.length >= 2 && args[1].toLowerCase(Locale.ENGLISH).equals("help")) { ++ sender.sendMessage(text("Use /paper debug chunks [world] to dump loaded chunk information to a file", RED)); ++ break; ++ } ++ File file = new File(new File(new File("."), "debug"), ++ "chunks-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt"); ++ sender.sendMessage(text("Writing chunk information dump to " + file, GREEN)); ++ try { ++ MCUtil.dumpChunks(file); ++ sender.sendMessage(text("Successfully written chunk information!", GREEN)); ++ } catch (Throwable thr) { ++ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); ++ sender.sendMessage(text("Failed to dump chunk information, see console", RED)); ++ } ++ } ++ // "help" & default ++ default -> sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED)); ++ } ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index b310d51b7fe3e8cef0a450674725969fe1ce78a4..2e56c52e3ee45b0304a9e6a5eab863ef96b2aab0 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -1,15 +1,27 @@ + package net.minecraft.server; + + import com.google.common.util.concurrent.ThreadFactoryBuilder; ++import com.google.gson.JsonArray; ++import com.google.gson.JsonObject; ++import com.google.gson.internal.Streams; ++import com.google.gson.stream.JsonWriter; ++import com.mojang.datafixers.util.Either; + import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; + import java.lang.ref.Cleaner; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ChunkMap; ++import net.minecraft.server.level.DistanceManager; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.Ticket; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.ClipContext; + import net.minecraft.world.level.Level; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; + import org.apache.commons.lang.exception.ExceptionUtils; + import org.bukkit.Location; + import org.bukkit.block.BlockFace; +@@ -19,8 +31,11 @@ import org.spigotmc.AsyncCatcher; + + import javax.annotation.Nonnull; + import javax.annotation.Nullable; ++import java.io.*; ++import java.nio.charset.StandardCharsets; + import java.util.List; + import java.util.Queue; ++import java.util.Set; + import java.util.concurrent.CompletableFuture; + import java.util.concurrent.ExecutionException; + import java.util.concurrent.LinkedBlockingQueue; +@@ -505,6 +520,163 @@ public final class MCUtil { + } + } + ++ public static ChunkStatus getChunkStatus(ChunkHolder chunk) { ++ return chunk.getChunkHolderStatus(); ++ } ++ ++ public static void dumpChunks(File file) throws IOException { ++ file.getParentFile().mkdirs(); ++ file.createNewFile(); ++ /* ++ * Json format: ++ * ++ * Main data format: ++ * -server-version:<string> ++ * -data-version:<int> ++ * -worlds: ++ * -name:<world name> ++ * -view-distance:<int> ++ * -keep-spawn-loaded:<boolean> ++ * -keep-spawn-loaded-range:<int> ++ * -visible-chunk-count:<int> ++ * -loaded-chunk-count:<int> ++ * -verified-fully-loaded-chunks:<int> ++ * -players:<array of player> ++ * -chunk-data:<array of chunks> ++ * ++ * Player format: ++ * -name:<string> ++ * -x:<double> ++ * -y:<double> ++ * -z:<double> ++ * ++ * Chunk Format: ++ * -x:<integer> ++ * -z:<integer> ++ * -ticket-level:<integer> ++ * -state:<string> ++ * -queued-for-unload:<boolean> ++ * -status:<string> ++ * -tickets:<array of tickets> ++ * ++ * ++ * Ticket format: ++ * -ticket-type:<string> ++ * -ticket-level:<int> ++ * -add-tick:<long> ++ * -object-reason:<string> // This depends on the type of ticket. ie POST_TELEPORT -> entity id ++ */ ++ List<org.bukkit.World> worlds = org.bukkit.Bukkit.getWorlds(); ++ JsonObject data = new JsonObject(); ++ ++ data.addProperty("server-version", org.bukkit.Bukkit.getVersion()); ++ data.addProperty("data-version", 0); ++ ++ JsonArray worldsData = new JsonArray(); ++ ++ for (org.bukkit.World bukkitWorld : worlds) { ++ JsonObject worldData = new JsonObject(); ++ ++ ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle(); ++ ChunkMap chunkMap = world.getChunkSource().chunkMap; ++ DistanceManager chunkMapDistance = chunkMap.distanceManager; ++ List<ChunkHolder> allChunks = net.minecraft.server.ChunkSystem.getVisibleChunkHolders(world); ++ List<ServerPlayer> players = world.players; ++ ++ int fullLoadedChunks = 0; ++ ++ for (ChunkHolder chunk : allChunks) { ++ if (chunk.getFullChunkNowUnchecked() != null) { ++ ++fullLoadedChunks; ++ } ++ } ++ ++ // sorting by coordinate makes the log easier to read ++ allChunks.sort((ChunkHolder v1, ChunkHolder v2) -> { ++ if (v1.pos.x != v2.pos.x) { ++ return Integer.compare(v1.pos.x, v2.pos.x); ++ } ++ return Integer.compare(v1.pos.z, v2.pos.z); ++ }); ++ ++ worldData.addProperty("name", world.getWorld().getName()); ++ worldData.addProperty("view-distance", world.spigotConfig.viewDistance); ++ worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory); ++ worldData.addProperty("keep-spawn-loaded-range", world.paperConfig().spawn.keepSpawnLoadedRange * 16); ++ worldData.addProperty("visible-chunk-count", allChunks.size()); ++ worldData.addProperty("loaded-chunk-count", chunkMap.entitiesInLevel.size()); ++ worldData.addProperty("verified-fully-loaded-chunks", fullLoadedChunks); ++ ++ JsonArray playersData = new JsonArray(); ++ ++ for (ServerPlayer player : players) { ++ JsonObject playerData = new JsonObject(); ++ ++ playerData.addProperty("name", player.getScoreboardName()); ++ playerData.addProperty("x", player.getX()); ++ playerData.addProperty("y", player.getY()); ++ playerData.addProperty("z", player.getZ()); ++ ++ playersData.add(playerData); ++ ++ } ++ ++ worldData.add("players", playersData); ++ ++ JsonArray chunksData = new JsonArray(); ++ ++ for (ChunkHolder playerChunk : allChunks) { ++ JsonObject chunkData = new JsonObject(); ++ ++ Set<Ticket<?>> tickets = chunkMapDistance.tickets.get(playerChunk.pos.longKey); ++ ChunkStatus status = getChunkStatus(playerChunk); ++ ++ chunkData.addProperty("x", playerChunk.pos.x); ++ chunkData.addProperty("z", playerChunk.pos.z); ++ chunkData.addProperty("ticket-level", playerChunk.getTicketLevel()); ++ chunkData.addProperty("state", ChunkHolder.getFullChunkStatus(playerChunk.getTicketLevel()).toString()); ++ chunkData.addProperty("queued-for-unload", chunkMap.toDrop.contains(playerChunk.pos.longKey)); ++ chunkData.addProperty("status", status == null ? "unloaded" : status.toString()); ++ ++ JsonArray ticketsData = new JsonArray(); ++ ++ if (tickets != null) { ++ for (Ticket<?> ticket : tickets) { ++ JsonObject ticketData = new JsonObject(); ++ ++ ticketData.addProperty("ticket-type", ticket.getType().toString()); ++ ticketData.addProperty("ticket-level", ticket.getTicketLevel()); ++ ticketData.addProperty("object-reason", String.valueOf(ticket.key)); ++ ticketData.addProperty("add-tick", ticket.createdTick); ++ ++ ticketsData.add(ticketData); ++ } ++ } ++ ++ chunkData.add("tickets", ticketsData); ++ chunksData.add(chunkData); ++ } ++ ++ ++ worldData.add("chunk-data", chunksData); ++ worldsData.add(worldData); ++ } ++ ++ data.add("worlds", worldsData); ++ ++ StringWriter stringWriter = new StringWriter(); ++ JsonWriter jsonWriter = new JsonWriter(stringWriter); ++ jsonWriter.setIndent(" "); ++ jsonWriter.setLenient(false); ++ Streams.write(data, jsonWriter); ++ ++ String fileData = stringWriter.toString(); ++ ++ try (PrintStream out = new PrintStream(new FileOutputStream(file), false, StandardCharsets.UTF_8)) { ++ out.print(fileData); ++ } ++ } ++ + public static int getTicketLevelFor(net.minecraft.world.level.chunk.ChunkStatus status) { + return net.minecraft.server.level.ChunkMap.MAX_VIEW_DISTANCE + net.minecraft.world.level.chunk.ChunkStatus.getDistance(status); + } |