diff options
author | Jake Potrebic <[email protected]> | 2021-12-22 10:02:31 -0800 |
---|---|---|
committer | GitHub <[email protected]> | 2021-12-22 10:02:31 -0800 |
commit | 82eaf4ee15d77303cdd352cce9b33c2f3e5d14d7 (patch) | |
tree | 53e4a42978675fc17acc76b85dcb0c3569ab3e3b /patches/server/0331-Chunk-debug-command.patch | |
parent | 6e5ceb34eb2ef4d6c0a35c46d0eded9099bb2fee (diff) | |
download | Paper-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.patch | 430 |
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); + } |