aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/removed/1.19.2-legacy-chunksystem/0016-Chunk-debug-command.patch
diff options
context:
space:
mode:
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.patch432
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);
+ }