aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0313-Add-debug-for-sync-chunk-loads.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patches/server/0313-Add-debug-for-sync-chunk-loads.patch')
-rw-r--r--patches/server/0313-Add-debug-for-sync-chunk-loads.patch333
1 files changed, 333 insertions, 0 deletions
diff --git a/patches/server/0313-Add-debug-for-sync-chunk-loads.patch b/patches/server/0313-Add-debug-for-sync-chunk-loads.patch
new file mode 100644
index 0000000000..bf5532d58e
--- /dev/null
+++ b/patches/server/0313-Add-debug-for-sync-chunk-loads.patch
@@ -0,0 +1,333 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Fri, 19 Jul 2019 03:29:14 -0700
+Subject: [PATCH] Add debug for sync chunk loads
+
+This patch adds a tool to find calls to getChunkAt which would load
+chunks, however it must be enabled by setting the startup flag
+-Dpaper.debug-sync-loads=true
+
+- To get a debug log for sync loads, the command is
+ /paper syncloadinfo
+- To clear clear the currently stored sync load info, use
+ /paper syncloadinfo clear
+
+diff --git a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..605a4a83d0a098a9977da00c710e798396dc5256
+--- /dev/null
++++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java
+@@ -0,0 +1,177 @@
++package com.destroystokyo.paper.io;
++
++import com.google.gson.JsonArray;
++import com.google.gson.JsonObject;
++import com.mojang.datafixers.util.Pair;
++import it.unimi.dsi.fastutil.longs.Long2IntMap;
++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
++import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
++
++import java.util.ArrayList;
++import java.util.List;
++import java.util.Map;
++import java.util.WeakHashMap;
++import net.minecraft.world.level.ChunkPos;
++import net.minecraft.world.level.Level;
++
++public class SyncLoadFinder {
++
++ public static final boolean ENABLED = Boolean.getBoolean("paper.debug-sync-loads");
++
++ private static final WeakHashMap<Level, Object2ObjectOpenHashMap<ThrowableWithEquals, SyncLoadInformation>> SYNC_LOADS = new WeakHashMap<>();
++
++ private static final class SyncLoadInformation {
++
++ public int times;
++
++ public final Long2IntOpenHashMap coordinateTimes = new Long2IntOpenHashMap();
++ }
++
++ public static void clear() {
++ SYNC_LOADS.clear();
++ }
++
++ public static void logSyncLoad(final Level world, final int chunkX, final int chunkZ) {
++ if (!ENABLED) {
++ return;
++ }
++
++ final ThrowableWithEquals stacktrace = new ThrowableWithEquals(Thread.currentThread().getStackTrace());
++
++ SYNC_LOADS.compute(world, (final Level keyInMap, Object2ObjectOpenHashMap<ThrowableWithEquals, SyncLoadInformation> map) -> {
++ if (map == null) {
++ map = new Object2ObjectOpenHashMap<>();
++ }
++
++ map.compute(stacktrace, (ThrowableWithEquals keyInMap0, SyncLoadInformation valueInMap) -> {
++ if (valueInMap == null) {
++ valueInMap = new SyncLoadInformation();
++ }
++
++ ++valueInMap.times;
++
++ valueInMap.coordinateTimes.compute(ChunkPos.asLong(chunkX, chunkZ), (Long keyInMap1, Integer valueInMap1) -> {
++ return valueInMap1 == null ? Integer.valueOf(1) : Integer.valueOf(valueInMap1.intValue() + 1);
++ });
++
++ return valueInMap;
++ });
++
++ return map;
++ });
++ }
++
++ public static JsonObject serialize() {
++ final JsonObject ret = new JsonObject();
++
++ final JsonArray worldsData = new JsonArray();
++
++ for (final Map.Entry<Level, Object2ObjectOpenHashMap<ThrowableWithEquals, SyncLoadInformation>> entry : SYNC_LOADS.entrySet()) {
++ final Level world = entry.getKey();
++
++ final JsonObject worldData = new JsonObject();
++
++ worldData.addProperty("name", world.getWorld().getName());
++
++ final List<Pair<ThrowableWithEquals, SyncLoadInformation>> data = new ArrayList<>();
++
++ entry.getValue().forEach((ThrowableWithEquals stacktrace, SyncLoadInformation times) -> {
++ data.add(new Pair<>(stacktrace, times));
++ });
++
++ data.sort((Pair<ThrowableWithEquals, SyncLoadInformation> pair1, Pair<ThrowableWithEquals, SyncLoadInformation> pair2) -> {
++ return Integer.compare(pair2.getSecond().times, pair1.getSecond().times); // reverse order
++ });
++
++ final JsonArray stacktraces = new JsonArray();
++
++ for (Pair<ThrowableWithEquals, SyncLoadInformation> pair : data) {
++ final JsonObject stacktrace = new JsonObject();
++
++ stacktrace.addProperty("times", pair.getSecond().times);
++
++ final JsonArray traces = new JsonArray();
++
++ for (StackTraceElement element : pair.getFirst().stacktrace) {
++ traces.add(String.valueOf(element));
++ }
++
++ stacktrace.add("stacktrace", traces);
++
++ final JsonArray coordinates = new JsonArray();
++
++ for (Long2IntMap.Entry coordinate : pair.getSecond().coordinateTimes.long2IntEntrySet()) {
++ final long key = coordinate.getLongKey();
++ final int times = coordinate.getIntValue();
++ final ChunkPos chunkPos = new ChunkPos(key);
++ coordinates.add("(" + chunkPos.x + "," + chunkPos.z + "): " + times);
++ }
++
++ stacktrace.add("coordinates", coordinates);
++
++ stacktraces.add(stacktrace);
++ }
++
++
++ worldData.add("stacktraces", stacktraces);
++ worldsData.add(worldData);
++ }
++
++ ret.add("worlds", worldsData);
++
++ return ret;
++ }
++
++ static final class ThrowableWithEquals {
++
++ private final StackTraceElement[] stacktrace;
++ private final int hash;
++
++ public ThrowableWithEquals(final StackTraceElement[] stacktrace) {
++ this.stacktrace = stacktrace;
++ this.hash = ThrowableWithEquals.hash(stacktrace);
++ }
++
++ public static int hash(final StackTraceElement[] stacktrace) {
++ int hash = 0;
++
++ for (int i = 0; i < stacktrace.length; ++i) {
++ hash *= 31;
++ hash += stacktrace[i].hashCode();
++ }
++
++ return hash;
++ }
++
++ @Override
++ public int hashCode() {
++ return this.hash;
++ }
++
++ @Override
++ public boolean equals(final Object obj) {
++ if (obj == null || obj.getClass() != this.getClass()) {
++ return false;
++ }
++
++ final ThrowableWithEquals other = (ThrowableWithEquals)obj;
++ final StackTraceElement[] otherStackTrace = other.stacktrace;
++
++ if (this.stacktrace.length != otherStackTrace.length || this.hash != other.hash) {
++ return false;
++ }
++
++ if (this == obj) {
++ return true;
++ }
++
++ for (int i = 0; i < this.stacktrace.length; ++i) {
++ if (!this.stacktrace[i].equals(otherStackTrace[i])) {
++ return false;
++ }
++ }
++
++ return true;
++ }
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java
+index e1820a339452cd3388dd7cbb928c5f58779a77b6..3010d57efcc97fb409bfe43b1fc9af198c099a67 100644
+--- a/src/main/java/io/papermc/paper/command/PaperCommand.java
++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
+@@ -38,6 +38,7 @@ public final class PaperCommand extends Command {
+ commands.put(Set.of("reload"), new ReloadCommand());
+ commands.put(Set.of("version"), new VersionCommand());
+ commands.put(Set.of("dumpplugins"), new DumpPluginsCommand());
++ commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand());
+
+ 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/SyncLoadInfoCommand.java b/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..95d6022c9cfb2e36ec5a71be6e34354027c2ec08
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java
+@@ -0,0 +1,88 @@
++package io.papermc.paper.command.subcommands;
++
++import com.destroystokyo.paper.io.SyncLoadFinder;
++import com.google.gson.JsonObject;
++import com.google.gson.internal.Streams;
++import com.google.gson.stream.JsonWriter;
++import io.papermc.paper.command.CommandUtil;
++import io.papermc.paper.command.PaperSubcommand;
++import java.io.File;
++import java.io.FileOutputStream;
++import java.io.PrintStream;
++import java.io.StringWriter;
++import java.nio.charset.StandardCharsets;
++import java.time.LocalDateTime;
++import java.time.format.DateTimeFormatter;
++import java.util.List;
++
++import net.kyori.adventure.text.event.ClickEvent;
++import net.kyori.adventure.text.event.HoverEvent;
++import net.minecraft.server.MinecraftServer;
++import org.bukkit.command.CommandSender;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
++
++import static net.kyori.adventure.text.Component.text;
++import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
++import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
++import static net.kyori.adventure.text.format.NamedTextColor.RED;
++import static net.kyori.adventure.text.format.NamedTextColor.WHITE;
++
++@DefaultQualifier(NonNull.class)
++public final class SyncLoadInfoCommand implements PaperSubcommand {
++ @Override
++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
++ this.doSyncLoadInfo(sender, args);
++ return true;
++ }
++
++ @Override
++ public List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
++ return CommandUtil.getListMatchingLast(sender, args, "clear");
++ }
++
++ private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss");
++
++ private void doSyncLoadInfo(final CommandSender sender, final String[] args) {
++ if (!SyncLoadFinder.ENABLED) {
++ String systemFlag = "-Dpaper.debug-sync-loads=true";
++ sender.sendMessage(text().color(RED).append(text("This command requires the server startup flag '")).append(
++ text(systemFlag, WHITE).clickEvent(ClickEvent.copyToClipboard(systemFlag))
++ .hoverEvent(HoverEvent.showText(text("Click to copy the system flag")))).append(
++ text("' to be set.")));
++ return;
++ }
++
++ if (args.length > 0 && args[0].equals("clear")) {
++ SyncLoadFinder.clear();
++ sender.sendMessage(text("Sync load data cleared.", GRAY));
++ return;
++ }
++
++ File file = new File(new File(new File("."), "debug"),
++ "sync-load-info-" + FORMATTER.format(LocalDateTime.now()) + ".txt");
++ file.getParentFile().mkdirs();
++ sender.sendMessage(text("Writing sync load info to " + file, GREEN));
++
++
++ try {
++ final JsonObject data = SyncLoadFinder.serialize();
++
++ StringWriter stringWriter = new StringWriter();
++ JsonWriter jsonWriter = new JsonWriter(stringWriter);
++ jsonWriter.setIndent(" ");
++ jsonWriter.setLenient(false);
++ Streams.write(data, jsonWriter);
++
++ try (
++ PrintStream out = new PrintStream(new FileOutputStream(file), false, StandardCharsets.UTF_8)
++ ) {
++ out.print(stringWriter);
++ }
++ sender.sendMessage(text("Successfully written sync load information!", GREEN));
++ } catch (Throwable thr) {
++ sender.sendMessage(text("Failed to write sync load information! See the console for more info.", RED));
++ MinecraftServer.LOGGER.warn("Error occurred while dumping sync chunk load info", thr);
++ }
++ }
++}
+diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+index 1049452baaf0381ffbf15b30da956c2a1994da35..60f678c26fb5144386d8697ddfd5b6d841563b6f 100644
+--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+@@ -206,6 +206,7 @@ public class ServerChunkCache extends ChunkSource {
+
+ Objects.requireNonNull(completablefuture);
+ if (!completablefuture.isDone()) { // Paper
++ com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x, z); // Paper - Add debug for sync chunk loads
+ this.level.timings.syncChunkLoad.startTiming(); // Paper
+ chunkproviderserver_b.managedBlock(completablefuture::isDone);
+ this.level.timings.syncChunkLoad.stopTiming(); // Paper
+diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+index c85d5b941b5cb1c37cd2619d419006c5956f6eaf..f1fa4cb11f69e248dd55b8aa69f5d07268f182a1 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -422,6 +422,13 @@ public class ServerLevel extends Level implements WorldGenLevel {
+ this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
+ }
+
++ // Paper start
++ @Override
++ public boolean hasChunk(int chunkX, int chunkZ) {
++ return this.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) != null;
++ }
++ // Paper end
++
+ /** @deprecated */
+ @Deprecated
+ @VisibleForTesting