aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0331-Chunk-debug-command.patch
diff options
context:
space:
mode:
authorJake Potrebic <[email protected]>2021-12-22 10:02:31 -0800
committerGitHub <[email protected]>2021-12-22 10:02:31 -0800
commit82eaf4ee15d77303cdd352cce9b33c2f3e5d14d7 (patch)
tree53e4a42978675fc17acc76b85dcb0c3569ab3e3b /patches/server/0331-Chunk-debug-command.patch
parent6e5ceb34eb2ef4d6c0a35c46d0eded9099bb2fee (diff)
downloadPaper-82eaf4ee15d77303cdd352cce9b33c2f3e5d14d7.tar.gz
Paper-82eaf4ee15d77303cdd352cce9b33c2f3e5d14d7.zip
Fix duplicated BlockPistonRetractEvent call (#7111)
Diffstat (limited to 'patches/server/0331-Chunk-debug-command.patch')
-rw-r--r--patches/server/0331-Chunk-debug-command.patch430
1 files changed, 430 insertions, 0 deletions
diff --git a/patches/server/0331-Chunk-debug-command.patch b/patches/server/0331-Chunk-debug-command.patch
new file mode 100644
index 0000000000..13d30fbe3c
--- /dev/null
+++ b/patches/server/0331-Chunk-debug-command.patch
@@ -0,0 +1,430 @@
+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/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java
+index 1eb45df9dca5d0c31ac46709e706136a246cb8ea..005361c38b02713fb823d0be40954400d59f0c4d 100644
+--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java
++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java
+@@ -6,13 +6,15 @@ import com.google.common.collect.ImmutableSet;
+ import com.google.common.collect.Iterables;
+ import com.google.common.collect.Lists;
+ import com.google.common.collect.Maps;
+-import net.minecraft.resources.ResourceLocation;
+ import net.minecraft.server.MinecraftServer;
++import net.minecraft.server.level.ChunkHolder;
+ import net.minecraft.server.level.ServerChunkCache;
+ import net.minecraft.server.level.ServerLevel;
+ import net.minecraft.world.entity.Entity;
+ import net.minecraft.world.entity.EntityType;
+ import net.minecraft.world.level.ChunkPos;
++import net.minecraft.resources.ResourceLocation;
++import net.minecraft.server.MCUtil;
+ import org.apache.commons.lang3.tuple.MutablePair;
+ import org.apache.commons.lang3.tuple.Pair;
+ import org.bukkit.Bukkit;
+@@ -41,7 +43,7 @@ import java.util.stream.Collectors;
+
+ public class PaperCommand extends Command {
+ private static final String BASE_PERM = "bukkit.command.paper.";
+- private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version").build();
++ private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo").build();
+
+ public PaperCommand(String name) {
+ super(name);
+@@ -69,6 +71,21 @@ public class PaperCommand extends Command {
+ if (args.length == 3)
+ return getListMatchingLast(sender, args, EntityType.getEntityNameList().stream().map(ResourceLocation::toString).sorted().toArray(String[]::new));
+ break;
++ case "debug":
++ if (args.length == 2) {
++ return getListMatchingLast(sender, args, "help", "chunks");
++ }
++ break;
++ case "chunkinfo":
++ List<String> worldNames = new ArrayList<>();
++ worldNames.add("*");
++ for (org.bukkit.World world : Bukkit.getWorlds()) {
++ worldNames.add(world.getName());
++ }
++ if (args.length == 2) {
++ return getListMatchingLast(sender, args, worldNames);
++ }
++ break;
+ }
+ return Collections.emptyList();
+ }
+@@ -135,6 +152,12 @@ public class PaperCommand extends Command {
+ case "reload":
+ doReload(sender);
+ break;
++ case "debug":
++ doDebug(sender, args);
++ break;
++ case "chunkinfo":
++ doChunkInfo(sender, args);
++ break;
+ case "ver":
+ if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set)
+ case "version":
+@@ -152,6 +175,114 @@ public class PaperCommand extends Command {
+ return true;
+ }
+
++ private void doChunkInfo(CommandSender sender, String[] args) {
++ List<org.bukkit.World> worlds;
++ if (args.length < 2 || args[1].equals("*")) {
++ worlds = Bukkit.getWorlds();
++ } else {
++ worlds = new ArrayList<>(args.length - 1);
++ for (int i = 1; i < args.length; ++i) {
++ org.bukkit.World world = Bukkit.getWorld(args[i]);
++ if (world == null) {
++ sender.sendMessage(ChatColor.RED + "World '" + args[i] + "' is invalid");
++ return;
++ }
++ worlds.add(world);
++ }
++ }
++
++ int accumulatedTotal = 0;
++ int accumulatedInactive = 0;
++ int accumulatedBorder = 0;
++ int accumulatedTicking = 0;
++ int accumulatedEntityTicking = 0;
++
++ for (org.bukkit.World bukkitWorld : worlds) {
++ ServerLevel world = ((CraftWorld)bukkitWorld).getHandle();
++
++ int total = 0;
++ int inactive = 0;
++ int border = 0;
++ int ticking = 0;
++ int entityTicking = 0;
++
++ for (ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunkMap.values()) {
++ if (chunk.getFullChunkUnchecked() == null) {
++ continue;
++ }
++
++ ++total;
++
++ ChunkHolder.FullChunkStatus state = ChunkHolder.getFullChunkStatus(chunk.getTicketLevel());
++
++ switch (state) {
++ case INACCESSIBLE:
++ ++inactive;
++ continue;
++ case BORDER:
++ ++border;
++ continue;
++ case TICKING:
++ ++ticking;
++ continue;
++ case ENTITY_TICKING:
++ ++entityTicking;
++ continue;
++ }
++ }
++
++ accumulatedTotal += total;
++ accumulatedInactive += inactive;
++ accumulatedBorder += border;
++ accumulatedTicking += ticking;
++ accumulatedEntityTicking += entityTicking;
++
++ sender.sendMessage(ChatColor.BLUE + "Chunks in " + ChatColor.GREEN + bukkitWorld.getName() + ChatColor.DARK_AQUA + ":");
++ sender.sendMessage(ChatColor.BLUE + "Total: " + ChatColor.DARK_AQUA + total + ChatColor.BLUE + " Inactive: " + ChatColor.DARK_AQUA
++ + inactive + ChatColor.BLUE + " Border: " + ChatColor.DARK_AQUA + border + ChatColor.BLUE + " Ticking: "
++ + ChatColor.DARK_AQUA + ticking + ChatColor.BLUE + " Entity: " + ChatColor.DARK_AQUA + entityTicking);
++ }
++ if (worlds.size() > 1) {
++ sender.sendMessage(ChatColor.BLUE + "Chunks in " + ChatColor.GREEN + "all listed worlds" + ChatColor.DARK_AQUA + ":");
++ sender.sendMessage(ChatColor.BLUE + "Total: " + ChatColor.DARK_AQUA + accumulatedTotal + ChatColor.BLUE + " Inactive: " + ChatColor.DARK_AQUA
++ + accumulatedInactive + ChatColor.BLUE + " Border: " + ChatColor.DARK_AQUA + accumulatedBorder + ChatColor.BLUE + " Ticking: "
++ + ChatColor.DARK_AQUA + accumulatedTicking + ChatColor.BLUE + " Entity: " + ChatColor.DARK_AQUA + accumulatedEntityTicking);
++ }
++ }
++
++ private void doDebug(CommandSender sender, String[] args) {
++ if (args.length < 2) {
++ sender.sendMessage(ChatColor.RED + "Use /paper debug [chunks] help for more information on a specific command");
++ return;
++ }
++
++ String debugType = args[1].toLowerCase(Locale.ENGLISH);
++ switch (debugType) {
++ case "chunks":
++ if (args.length >= 3 && args[2].toLowerCase(Locale.ENGLISH).equals("help")) {
++ sender.sendMessage(ChatColor.RED + "Use /paper debug chunks to dump loaded chunk information to a file");
++ 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(ChatColor.GREEN + "Writing chunk information dump to " + file.toString());
++ try {
++ MCUtil.dumpChunks(file);
++ sender.sendMessage(ChatColor.GREEN + "Successfully written chunk information!");
++ } catch (Throwable thr) {
++ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr);
++ sender.sendMessage(ChatColor.RED + "Failed to dump chunk information, see console");
++ }
++
++ break;
++ case "help":
++ // fall through to default
++ default:
++ sender.sendMessage(ChatColor.RED + "Use /paper debug [chunks] help for more information on a specific command");
++ return;
++ }
++ }
++
+ /*
+ * Ported from MinecraftForge - author: LexManos <[email protected]> - License: LGPLv2.1
+ */
+diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
+index 3b10ef3801ffd47707836b3ed3482e99ddd0050b..2fe519d4059fac06781c30e140895b604e13104f 100644
+--- a/src/main/java/net/minecraft/server/MCUtil.java
++++ b/src/main/java/net/minecraft/server/MCUtil.java
+@@ -9,13 +9,27 @@ import net.minecraft.core.BlockPos;
+ import net.minecraft.core.Direction;
+ import net.minecraft.nbt.CompoundTag;
+ import net.minecraft.network.chat.Component;
++import net.minecraft.server.level.ChunkHolder;
++import net.minecraft.server.level.ChunkMap;
++import net.minecraft.server.level.DistanceManager;
++import net.minecraft.server.level.ServerChunkCache;
+ 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 com.google.gson.JsonArray;
++import com.google.gson.JsonObject;
++import com.google.gson.internal.Streams;
++import com.google.gson.stream.JsonWriter;
+ import com.mojang.authlib.GameProfile;
++import com.mojang.datafixers.util.Either;
++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
+ import org.bukkit.Location;
+ import org.bukkit.block.BlockFace;
+ import org.bukkit.craftbukkit.CraftWorld;
+@@ -24,8 +38,11 @@ import org.spigotmc.AsyncCatcher;
+
+ import javax.annotation.Nonnull;
+ import javax.annotation.Nullable;
++import java.io.*;
++import java.util.ArrayList;
+ 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;
+@@ -535,6 +552,172 @@ public final class MCUtil {
+ return null;
+ }
+
++ public static ChunkStatus getChunkStatus(ChunkHolder chunk) {
++ List<ChunkStatus> statuses = net.minecraft.server.level.ServerChunkCache.CHUNK_STATUSES;
++ for (int i = statuses.size() - 1; i >= 0; --i) {
++ ChunkStatus curr = statuses.get(i);
++ CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = chunk.getFutureIfPresentUnchecked(curr);
++ if (future != ChunkHolder.UNLOADED_CHUNK_FUTURE) {
++ return curr;
++ }
++ }
++ return null; // unloaded
++ }
++
++ 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;
++ Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunks = chunkMap.visibleChunkMap;
++ DistanceManager chunkMapDistance = chunkMap.distanceManager;
++ List<ChunkHolder> allChunks = new ArrayList<>(visibleChunks.values());
++ List<ServerPlayer> players = world.players;
++
++ int fullLoadedChunks = 0;
++
++ for (ChunkHolder chunk : allChunks) {
++ if (chunk.getFullChunkUnchecked() != 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.keepLoadedRange);
++ worldData.addProperty("visible-chunk-count", visibleChunks.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, "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);
+ }