Paper/patches/server/0346-Add-debug-for-sync-chunk-loads.patch
Nassim Jahnke c0936a71bd
Updated Upstream (Bukkit/CraftBukkit/Spigot) (#9440)
Upstream has released updates that appear to apply and compile correctly.
This update has not been tested by PaperMC and as with ANY update, please do your own testing

Bukkit Changes:
01aa02eb PR-858: Add LivingEntity#playHurtAnimation()
9421320f PR-884: Refinements to new ban API for improved compatibility and correctness
37a60b45 SPIGOT-6455, SPIGOT-7030, PR-750: Improve ban API
4eeb174b All smithing inventories are now the new smithing inventory
f2bb168e PR-880: Add methods to get/set FallingBlock CancelDrop
e7a807fa PR-879: Add Player#sendHealthUpdate()
692b8e96 SPIGOT-7370: Remove float value conversion in plugin.yml
2d033390 SPIGOT-7403: Add direct API for waxed signs
16a08373 PR-876: Add missing Raider API and 'no action ticks'

CraftBukkit Changes:
b60a95c8c PR-1189: Add LivingEntity#playHurtAnimation()
95c335c63 PR-1226: Fix VehicleEnterEvent not being called for certain entities
0a0fc3bee PR-1227: Refinements to new ban API for improved compatibility and correctness
0d0b1e5dc Revert bad change to PathfinderGoalSit causing all cats to sit
648196070 SPIGOT-6455, SPIGOT-7030, PR-1054: Improve ban API
31fe848d6 All smithing inventories are now the new smithing inventory
9a919a143 SPIGOT-7416: SmithItemEvent not firing in Smithing Table
9f64f0d22 PR-1221: Add methods to get/set FallingBlock CancelDrop
3be9ac171 PR-1220: Add Player#sendHealthUpdate()
c1279f775 PR-1209: Clean up various patches
c432e4397 Fix Raider#setCelebrating() implementation
504d96665 SPIGOT-7403: Add direct API for waxed signs
c68c1f1b3 PR-1216: Add missing Raider API and 'no action ticks'
85b89c3dd Increase outdated build delay

Spigot Changes:
9ebce8af Rebuild patches
64b565e6 Rebuild patches
2023-07-04 10:22:56 +02:00

332 lines
14 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
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..0bb4aaa546939b67a5d22865190f30478a9337c1
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java
@@ -0,0 +1,175 @@
+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.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(IOUtil.getCoordinateKey(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();
+ coordinates.add("(" + IOUtil.getCoordinateX(key) + "," + IOUtil.getCoordinateZ(key) + "): " + 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 8f16640fc2f1233c10392d7e32a54d784fd8e370..1e9f191dc0f9d98f4404d2796f15b13912860b13 100644
--- a/src/main/java/io/papermc/paper/command/PaperCommand.java
+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
@@ -41,6 +41,7 @@ public final class PaperCommand extends Command {
commands.put(Set.of("dumpplugins"), new DumpPluginsCommand());
commands.put(Set.of("fixlight"), new FixLightCommand());
commands.put(Set.of("debug", "chunkinfo", "holderinfo"), new ChunkDebugCommand());
+ 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 603f9d1f501a18214f11a6e401f2c43d9c3cf8eb..1c327067d488cc916d082a797b161cb7836ffa2e 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -294,6 +294,7 @@ public class ServerChunkCache extends ChunkSource {
// Paper start - async chunk io/loading
io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.pushChunkWait(this.level, x1, z1); // Paper - rewrite chunk system
// Paper end
+ com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x1, z1); // Paper - sync load info
this.level.timings.syncChunkLoad.startTiming(); // Paper
chunkproviderserver_b.managedBlock(completablefuture::isDone);
io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.popChunkWait(); // Paper - async chunk debug // Paper - rewrite chunk system
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 62ea2bb2fdd1f2de31b08c88193887989fbd3ece..fc6cf61664b4c64c9319c61b447a02e2cc38558f 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -651,6 +651,13 @@ public class ServerLevel extends Level implements WorldGenLevel {
this.entityLookup = new io.papermc.paper.chunk.system.entity.EntityLookup(this, new EntityCallbacks()); // Paper - rewrite chunk system
}
+ // Paper start
+ @Override
+ public boolean hasChunk(int chunkX, int chunkZ) {
+ return this.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) != null;
+ }
+ // Paper end
+
/** @deprecated */
@Deprecated
@VisibleForTesting