diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/JsonUtil.java b/src/main/java/ca/spottedleaf/moonrise/common/util/JsonUtil.java new file mode 100644 index 00000000..91efda72 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/common/util/JsonUtil.java @@ -0,0 +1,34 @@ +package ca.spottedleaf.moonrise.common.util; + +import com.google.gson.JsonElement; +import com.google.gson.internal.Streams; +import com.google.gson.stream.JsonWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; + +public final class JsonUtil { + + public static void writeJson(final JsonElement element, final File file) throws IOException { + final StringWriter stringWriter = new StringWriter(); + final JsonWriter jsonWriter = new JsonWriter(stringWriter); + jsonWriter.setIndent(" "); + jsonWriter.setLenient(false); + Streams.write(element, jsonWriter); + + final String jsonString = stringWriter.toString(); + + final File parent = file.getParentFile(); + if (parent != null) { + parent.mkdirs(); + } + file.createNewFile(); + try (final PrintStream out = new PrintStream(new FileOutputStream(file), false, StandardCharsets.UTF_8)) { + out.print(jsonString); + } + } + +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java index 6b798306..5a2f4804 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java @@ -1349,10 +1349,6 @@ private boolean processPendingFullUpdate() { public JsonObject getDebugJson() { final JsonObject ret = new JsonObject(); - ret.addProperty("lock_shift", Integer.valueOf(this.taskScheduler.getChunkSystemLockShift())); - ret.addProperty("ticket_shift", Integer.valueOf(ThreadedTicketLevelPropagator.SECTION_SHIFT)); - ret.addProperty("region_shift", Integer.valueOf(((ChunkSystemServerLevel)this.world).moonrise$getRegionChunkShift())); - ret.add("unload_queue", this.unloadQueue.toDebugJson()); final JsonArray holders = new JsonArray(); diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java index 786d322f..9a8d6b03 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java @@ -6,12 +6,14 @@ import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import ca.spottedleaf.moonrise.common.util.JsonUtil; import ca.spottedleaf.moonrise.common.util.MoonriseCommon; import ca.spottedleaf.moonrise.common.util.TickThread; import ca.spottedleaf.moonrise.common.util.WorldUtil; import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkStatus; +import ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer; import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.executor.RadiusAwarePrioritisedExecutor; import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkFullTask; import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkLightTask; @@ -21,24 +23,34 @@ import ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer; import ca.spottedleaf.moonrise.patches.chunk_system.status.ChunkSystemChunkStep; import ca.spottedleaf.moonrise.patches.chunk_system.util.ParallelSearchRadiusIteration; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.ReportedException; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ChunkLevel; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.FullChunkStatus; import net.minecraft.server.level.GenerationChunkHolder; import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.TicketType; import net.minecraft.util.StaticCache2D; +import net.minecraft.world.entity.Entity; import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.status.ChunkPyramid; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.chunk.status.ChunkStep; +import net.minecraft.world.phys.Vec3; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -867,6 +879,16 @@ public ChunkInfo(final int chunkX, final int chunkZ, final ServerLevel world) { this.world = world; } + public JsonObject toJson() { + final JsonObject ret = new JsonObject(); + + ret.addProperty("chunk-x", Integer.valueOf(this.chunkX)); + ret.addProperty("chunk-z", Integer.valueOf(this.chunkZ)); + ret.addProperty("world-name", WorldUtil.getWorldName(this.world)); + + return ret; + } + @Override public String toString() { return "[( " + this.chunkX + "," + this.chunkZ + ") in '" + WorldUtil.getWorldName(this.world) + "']"; @@ -890,4 +912,113 @@ public static ChunkInfo[] getChunkInfos() { return WAITING_CHUNKS.toArray(new ChunkInfo[0]); } } + + private static JsonObject debugPlayer(final ServerPlayer player) { + final Level world = player.level(); + + final JsonObject ret = new JsonObject(); + + ret.addProperty("name", player.getScoreboardName()); + ret.addProperty("uuid", player.getUUID().toString()); + ret.addProperty("real", ((ChunkSystemServerPlayer)player).moonrise$isRealPlayer()); + + ret.addProperty("world-name", WorldUtil.getWorldName(world)); + + final Vec3 pos = player.position(); + + ret.addProperty("x", pos.x); + ret.addProperty("y", pos.y); + ret.addProperty("z", pos.z); + + final Entity.RemovalReason removalReason = player.getRemovalReason(); + + ret.addProperty("removal-reason", removalReason == null ? "null" : removalReason.name()); + + ret.add("view-distances", ((ChunkSystemServerPlayer)player).moonrise$getViewDistanceHolder().toJson()); + + return ret; + } + + public JsonObject getDebugJson() { + final JsonObject ret = new JsonObject(); + + ret.addProperty("lock_shift", Integer.valueOf(this.getChunkSystemLockShift())); + ret.addProperty("ticket_shift", Integer.valueOf(ThreadedTicketLevelPropagator.SECTION_SHIFT)); + ret.addProperty("region_shift", Integer.valueOf(((ChunkSystemServerLevel)this.world).moonrise$getRegionChunkShift())); + + ret.addProperty("name", WorldUtil.getWorldName(this.world)); + ret.addProperty("view-distance", ((ChunkSystemServerLevel)this.world).moonrise$getPlayerChunkLoader().getAPIViewDistance()); + ret.addProperty("tick-distance", ((ChunkSystemServerLevel)this.world).moonrise$getPlayerChunkLoader().getAPITickDistance()); + ret.addProperty("send-distance", ((ChunkSystemServerLevel)this.world).moonrise$getPlayerChunkLoader().getAPISendViewDistance()); + + final JsonArray players = new JsonArray(); + ret.add("players", players); + + for (final ServerPlayer player : this.world.players()) { + players.add(debugPlayer(player)); + } + + ret.add("chunk-holder-manager", this.chunkHolderManager.getDebugJson()); + + return ret; + } + + public static JsonObject debugAllWorlds(final MinecraftServer server) { + final JsonObject ret = new JsonObject(); + + ret.addProperty("data-version", 2); + + final JsonArray allPlayers = new JsonArray(); + ret.add("all-players", allPlayers); + + for (final ServerPlayer player : server.getPlayerList().getPlayers()) { + allPlayers.add(debugPlayer(player)); + } + + final JsonArray chunkWaitInfos = new JsonArray(); + ret.add("chunk-wait-infos", chunkWaitInfos); + + for (final ChunkTaskScheduler.ChunkInfo info : getChunkInfos()) { + chunkWaitInfos.add(info.toJson()); + } + + final JsonArray worlds = new JsonArray(); + ret.add("worlds", worlds); + + for (final ServerLevel world : server.getAllLevels()) { + worlds.add(((ChunkSystemServerLevel)world).moonrise$getChunkTaskScheduler().getDebugJson()); + } + + return ret; + } + + public static File getChunkDebugFile() { + return new File( + new File(new File("."), "debug"), + "chunks-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt" + ); + } + + public static void dumpAllChunkLoadInfo(final MinecraftServer server, final boolean writeDebugInfo) { + final ChunkInfo[] chunkInfos = getChunkInfos(); + if (chunkInfos.length > 0) { + LOGGER.error("Chunk wait task info below: "); + for (final ChunkInfo chunkInfo : chunkInfos) { + final NewChunkHolder holder = ((ChunkSystemServerLevel)chunkInfo.world).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkInfo.chunkX, chunkInfo.chunkZ); + LOGGER.error("Chunk wait: " + chunkInfo); + LOGGER.error("Chunk holder: " + holder); + } + + if (writeDebugInfo) { + final File file = getChunkDebugFile(); + LOGGER.error("Writing chunk information dump to " + file); + try { + JsonUtil.writeJson(ChunkTaskScheduler.debugAllWorlds(server), file); + LOGGER.error("Successfully written chunk information!"); + } catch (final Throwable thr) { + LOGGER.error("Failed to dump chunk information to file " + file.toString(), thr); + } + } + } + } } \ No newline at end of file diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/command/MoonriseCommand.java b/src/main/java/ca/spottedleaf/moonrise/patches/command/MoonriseCommand.java index 75fe0507..c2abacf9 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/command/MoonriseCommand.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/command/MoonriseCommand.java @@ -1,15 +1,18 @@ package ca.spottedleaf.moonrise.patches.command; import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import ca.spottedleaf.moonrise.common.util.JsonUtil; import ca.spottedleaf.moonrise.common.util.MoonriseCommon; import ca.spottedleaf.moonrise.common.util.MoonriseConstants; import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; import ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder; import ca.spottedleaf.moonrise.patches.starlight.light.StarLightLightingProvider; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.context.CommandContext; +import com.mojang.logging.LogUtils; import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import net.minecraft.commands.CommandSourceStack; @@ -24,6 +27,7 @@ import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.ProtoChunk; import net.minecraft.world.phys.Vec3; +import org.slf4j.Logger; import java.io.File; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -32,6 +36,8 @@ public final class MoonriseCommand { + private static final Logger LOGGER = LogUtils.getLogger(); + public static void register(final CommandDispatcher dispatcher) { dispatcher.register( Commands.literal("moonrise").requires((final CommandSourceStack src) -> { @@ -62,6 +68,14 @@ public static void register(final CommandDispatcher dispatch return MoonriseCommand.relight(ctx, IntegerArgumentType.getInteger(ctx, "radius")); }) ) + ).then( + Commands.literal("debug") + .then( + Commands.literal("chunks") + .executes((final CommandContext ctx) -> { + return MoonriseCommand.debugChunks(ctx); + }) + ) ) ); } @@ -238,4 +252,36 @@ public static int relight(final CommandContext ctx, final in return ret; } + + public static int debugChunks(final CommandContext ctx) { + final File file = ChunkTaskScheduler.getChunkDebugFile(); + + ctx.getSource().sendSuccess(() -> { + return MutableComponent.create( + new PlainTextContents.LiteralContents( + "Writing chunk information dump to '" + file + "'" + ) + ); + }, true); + try { + JsonUtil.writeJson(ChunkTaskScheduler.debugAllWorlds(ctx.getSource().getServer()), file); + + ctx.getSource().sendSuccess(() -> { + return MutableComponent.create( + new PlainTextContents.LiteralContents( + "Wrote chunk information dump to '" + file + "'" + ) + ); + }, true); + } catch (final Throwable throwable) { + LOGGER.error("Failed to dump chunk information to file '" + file.getAbsolutePath() + "'", throwable); + ctx.getSource().sendFailure(MutableComponent.create( + new PlainTextContents.LiteralContents( + "Failed to dump chunk information, see console" + ) + )); + } + + return 0; + } }