--- a/net/minecraft/network/protocol/PacketUtils.java
+++ b/net/minecraft/network/protocol/PacketUtils.java
@@ -6,56 +6,105 @@
 import net.minecraft.CrashReportCategory;
 import net.minecraft.ReportedException;
 import net.minecraft.network.PacketListener;
+import org.slf4j.Logger;
+
+// CraftBukkit start
+import net.minecraft.server.MinecraftServer;
 import net.minecraft.server.RunningOnDifferentThreadException;
 import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.network.ServerCommonPacketListenerImpl;
+// CraftBukkit end
 import net.minecraft.util.thread.BlockableEventLoop;
-import org.slf4j.Logger;
 
 public class PacketUtils {
+
     private static final Logger LOGGER = LogUtils.getLogger();
 
+    // Paper start - detailed watchdog information
+    public static final java.util.concurrent.ConcurrentLinkedDeque<PacketListener> packetProcessing = new java.util.concurrent.ConcurrentLinkedDeque<>();
+    static final java.util.concurrent.atomic.AtomicLong totalMainThreadPacketsProcessed = new java.util.concurrent.atomic.AtomicLong();
+
+    public static long getTotalProcessedPackets() {
+        return totalMainThreadPacketsProcessed.get();
+    }
+
+    public static java.util.List<PacketListener> getCurrentPacketProcessors() {
+        java.util.List<PacketListener> ret = new java.util.ArrayList<>(4);
+        for (PacketListener listener : packetProcessing) {
+            ret.add(listener);
+        }
+
+        return ret;
+    }
+    // Paper end - detailed watchdog information
+
+    public PacketUtils() {}
+
     public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T listener, ServerLevel world) throws RunningOnDifferentThreadException {
-        ensureRunningOnSameThread(packet, listener, world.getServer());
+        PacketUtils.ensureRunningOnSameThread(packet, listener, (BlockableEventLoop) world.getServer());
     }
 
     public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T listener, BlockableEventLoop<?> engine) throws RunningOnDifferentThreadException {
         if (!engine.isSameThread()) {
             engine.executeIfPossible(() -> {
+                packetProcessing.push(listener); // Paper - detailed watchdog information
+                try { // Paper - detailed watchdog information
+                if (listener instanceof ServerCommonPacketListenerImpl serverCommonPacketListener && serverCommonPacketListener.processedDisconnect) return; // CraftBukkit - Don't handle sync packets for kicked players
                 if (listener.shouldHandleMessage(packet)) {
-                    try {
+                    co.aikar.timings.Timing timing = co.aikar.timings.MinecraftTimings.getPacketTiming(packet); // Paper - timings
+                    try (co.aikar.timings.Timing ignored = timing.startTiming()) { // Paper - timings
                         packet.handle(listener);
-                    } catch (Exception var4) {
-                        if (var4 instanceof ReportedException reportedException && reportedException.getCause() instanceof OutOfMemoryError) {
-                            throw makeReportedException(var4, packet, listener);
+                    } catch (Exception exception) {
+                        if (exception instanceof ReportedException) {
+                            ReportedException reportedexception = (ReportedException) exception;
+
+                            if (reportedexception.getCause() instanceof OutOfMemoryError) {
+                                throw PacketUtils.makeReportedException(exception, packet, listener);
+                            }
                         }
 
-                        listener.onPacketError(packet, var4);
+                        listener.onPacketError(packet, exception);
                     }
                 } else {
-                    LOGGER.debug("Ignoring packet due to disconnection: {}", packet);
+                    PacketUtils.LOGGER.debug("Ignoring packet due to disconnection: {}", packet);
                 }
+                // Paper start - detailed watchdog information
+                } finally {
+                    totalMainThreadPacketsProcessed.getAndIncrement();
+                    packetProcessing.pop();
+                }
+                // Paper end - detailed watchdog information
+
             });
             throw RunningOnDifferentThreadException.RUNNING_ON_DIFFERENT_THREAD;
         }
     }
 
     public static <T extends PacketListener> ReportedException makeReportedException(Exception exception, Packet<T> packet, T listener) {
-        if (exception instanceof ReportedException reportedException) {
-            fillCrashReport(reportedException.getReport(), listener, packet);
-            return reportedException;
+        if (exception instanceof ReportedException reportedexception) {
+            PacketUtils.fillCrashReport(reportedexception.getReport(), listener, packet);
+            return reportedexception;
         } else {
-            CrashReport crashReport = CrashReport.forThrowable(exception, "Main thread packet handler");
-            fillCrashReport(crashReport, listener, packet);
-            return new ReportedException(crashReport);
+            CrashReport crashreport = CrashReport.forThrowable(exception, "Main thread packet handler");
+
+            PacketUtils.fillCrashReport(crashreport, listener, packet);
+            return new ReportedException(crashreport);
         }
     }
 
     public static <T extends PacketListener> void fillCrashReport(CrashReport report, T listener, @Nullable Packet<T> packet) {
         if (packet != null) {
-            CrashReportCategory crashReportCategory = report.addCategory("Incoming Packet");
-            crashReportCategory.setDetail("Type", () -> packet.type().toString());
-            crashReportCategory.setDetail("Is Terminal", () -> Boolean.toString(packet.isTerminal()));
-            crashReportCategory.setDetail("Is Skippable", () -> Boolean.toString(packet.isSkippable()));
+            CrashReportCategory crashreportsystemdetails = report.addCategory("Incoming Packet");
+
+            crashreportsystemdetails.setDetail("Type", () -> {
+                return packet.type().toString();
+            });
+            crashreportsystemdetails.setDetail("Is Terminal", () -> {
+                return Boolean.toString(packet.isTerminal());
+            });
+            crashreportsystemdetails.setDetail("Is Skippable", () -> {
+                return Boolean.toString(packet.isSkippable());
+            });
         }
 
         listener.fillCrashReport(report);
