aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0014-Timings-v2.patch
diff options
context:
space:
mode:
authorNassim Jahnke <[email protected]>2023-12-05 20:12:12 +0100
committerNassim Jahnke <[email protected]>2023-12-05 20:14:54 +0100
commit603b32976b6fe397beeba53488a8321ddf52f27a (patch)
treeb31746c189deb430260540c34009364538e56b4a /patches/server/0014-Timings-v2.patch
parent9b562217628996a5059d7254356c0f9b953e489e (diff)
downloadPaper-603b32976b6fe397beeba53488a8321ddf52f27a.tar.gz
Paper-603b32976b6fe397beeba53488a8321ddf52f27a.zip
More work
Diffstat (limited to 'patches/server/0014-Timings-v2.patch')
-rw-r--r--patches/server/0014-Timings-v2.patch2100
1 files changed, 2100 insertions, 0 deletions
diff --git a/patches/server/0014-Timings-v2.patch b/patches/server/0014-Timings-v2.patch
new file mode 100644
index 0000000000..3da8649bd1
--- /dev/null
+++ b/patches/server/0014-Timings-v2.patch
@@ -0,0 +1,2100 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Aikar <[email protected]>
+Date: Thu, 3 Mar 2016 04:00:11 -0600
+Subject: [PATCH] Timings v2
+
+
+diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..59affb62cb487d60e8c3e32decf89d6cb7d22f8d
+--- /dev/null
++++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
+@@ -0,0 +1,169 @@
++package co.aikar.timings;
++
++import com.google.common.collect.MapMaker;
++import io.papermc.paper.configuration.GlobalConfiguration;
++import net.minecraft.commands.CommandFunction;
++import net.minecraft.network.protocol.Packet;
++import net.minecraft.world.level.block.Block;
++import net.minecraft.world.level.block.entity.BlockEntity;
++import org.bukkit.plugin.Plugin;
++import org.bukkit.scheduler.BukkitTask;
++
++import org.bukkit.craftbukkit.scheduler.CraftTask;
++
++import java.util.Map;
++
++// TODO: Re-implement missing timers
++@Deprecated(forRemoval = true)
++public final class MinecraftTimings {
++
++ public static final Timing serverOversleep = Timings.ofSafe("Server Oversleep");
++ public static final Timing playerListTimer = Timings.ofSafe("Player List");
++ public static final Timing commandFunctionsTimer = Timings.ofSafe("Command Functions");
++ public static final Timing connectionTimer = Timings.ofSafe("Connection Handler");
++ public static final Timing tickablesTimer = Timings.ofSafe("Tickables");
++ public static final Timing minecraftSchedulerTimer = Timings.ofSafe("Minecraft Scheduler");
++ public static final Timing bukkitSchedulerTimer = Timings.ofSafe("Bukkit Scheduler");
++ public static final Timing bukkitSchedulerPendingTimer = Timings.ofSafe("Bukkit Scheduler - Pending");
++ public static final Timing bukkitSchedulerFinishTimer = Timings.ofSafe("Bukkit Scheduler - Finishing");
++ public static final Timing chunkIOTickTimer = Timings.ofSafe("ChunkIOTick");
++ public static final Timing timeUpdateTimer = Timings.ofSafe("Time Update");
++ public static final Timing serverCommandTimer = Timings.ofSafe("Server Command");
++ public static final Timing savePlayers = Timings.ofSafe("Save Players");
++
++ public static final Timing tickEntityTimer = Timings.ofSafe("## tickEntity");
++ public static final Timing tickTileEntityTimer = Timings.ofSafe("## tickTileEntity");
++ public static final Timing packetProcessTimer = Timings.ofSafe("## Packet Processing");
++ public static final Timing scheduledBlocksTimer = Timings.ofSafe("## Scheduled Blocks");
++ public static final Timing structureGenerationTimer = Timings.ofSafe("Structure Generation");
++
++ public static final Timing processQueueTimer = Timings.ofSafe("processQueue");
++ public static final Timing processTasksTimer = Timings.ofSafe("processTasks");
++
++ public static final Timing playerCommandTimer = Timings.ofSafe("playerCommand");
++
++ public static final Timing entityActivationCheckTimer = Timings.ofSafe("entityActivationCheck");
++
++ public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update");
++ public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate");
++
++ private static final Map<Class<?>, String> taskNameCache = new MapMaker().weakKeys().makeMap();
++
++ private MinecraftTimings() {}
++
++ public static Timing getInternalTaskName(String taskName) {
++ return Timings.ofSafe(taskName);
++ }
++
++ /**
++ * Gets a timer associated with a plugins tasks.
++ * @param bukkitTask
++ * @param period
++ * @return
++ */
++ public static Timing getPluginTaskTimings(BukkitTask bukkitTask, long period) {
++ if (!bukkitTask.isSync()) {
++ return NullTimingHandler.NULL;
++ }
++ Plugin plugin;
++
++ CraftTask craftTask = (CraftTask) bukkitTask;
++
++ final Class<?> taskClass = craftTask.getTaskClass();
++ if (bukkitTask.getOwner() != null) {
++ plugin = bukkitTask.getOwner();
++ } else {
++ plugin = TimingsManager.getPluginByClassloader(taskClass);
++ }
++
++ final String taskname = taskNameCache.computeIfAbsent(taskClass, clazz -> {
++ try {
++ String clsName = !clazz.isMemberClass()
++ ? clazz.getName()
++ : clazz.getCanonicalName();
++ if (clsName != null && clsName.contains("$Lambda$")) {
++ clsName = clsName.replaceAll("(Lambda\\$.*?)/.*", "$1");
++ }
++ return clsName != null ? clsName : "UnknownTask";
++ } catch (Throwable ex) {
++ new Exception("Error occurred detecting class name", ex).printStackTrace();
++ return "MangledClassFile";
++ }
++ });
++
++ StringBuilder name = new StringBuilder(64);
++ name.append("Task: ").append(taskname);
++ if (period > 0) {
++ name.append(" (interval:").append(period).append(")");
++ } else {
++ name.append(" (Single)");
++ }
++
++ if (plugin == null) {
++ return Timings.ofSafe(null, name.toString());
++ }
++
++ return Timings.ofSafe(plugin, name.toString());
++ }
++
++ /**
++ * Get a named timer for the specified entity type to track type specific timings.
++ * @param entityType
++ * @return
++ */
++ public static Timing getEntityTimings(String entityType, String type) {
++ return Timings.ofSafe("Minecraft", "## tickEntity - " + entityType + " - " + type, tickEntityTimer);
++ }
++
++ /**
++ * Get a named timer for the specified tile entity type to track type specific timings.
++ * @param entity
++ * @return
++ */
++ public static Timing getTileEntityTimings(BlockEntity entity) {
++ String entityType = entity.getClass().getName();
++ return Timings.ofSafe("Minecraft", "## tickTileEntity - " + entityType, tickTileEntityTimer);
++ }
++ public static Timing getCancelTasksTimer() {
++ return Timings.ofSafe("Cancel Tasks");
++ }
++ public static Timing getCancelTasksTimer(Plugin plugin) {
++ return Timings.ofSafe(plugin, "Cancel Tasks");
++ }
++
++ public static void stopServer() {
++ TimingsManager.stopServer();
++ }
++
++ public static Timing getBlockTiming(Block block) {
++ return Timings.ofSafe("## Scheduled Block: " + block.toString(), scheduledBlocksTimer);
++ }
++/*
++ public static Timing getStructureTiming(StructureGenerator structureGenerator) {
++ return Timings.ofSafe("Structure Generator - " + structureGenerator.getName(), structureGenerationTimer);
++ }*/
++
++ public static Timing getPacketTiming(Packet packet) {
++ return Timings.ofSafe("## Packet - " + packet.getClass().getName(), packetProcessTimer);
++ }
++
++ public static Timing getCommandFunctionTiming(CommandFunction function) {
++ return Timings.ofSafe("Command Function - " + function.getId());
++ }
++
++ public static void processConfig(GlobalConfiguration.Timings config) {
++ TimingsManager.url = config.url;
++ if (!TimingsManager.url.endsWith("/")) {
++ TimingsManager.url += "/";
++ }
++ TimingsManager.privacy = config.serverNamePrivacy;
++ if (!config.hiddenConfigEntries.contains("proxies.velocity.secret")) {
++ config.hiddenConfigEntries.add("proxies.velocity.secret");
++ }
++ TimingsManager.hiddenConfigs.addAll(config.hiddenConfigEntries);
++ co.aikar.timings.Timings.setVerboseTimingsEnabled(config.verbose);
++ co.aikar.timings.Timings.setTimingsEnabled(config.enabled);
++ co.aikar.timings.Timings.setHistoryInterval(config.historyInterval * 20);
++ co.aikar.timings.Timings.setHistoryLength(config.historyLength * 20);
++ }
++}
+diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..38f01952153348d937e326da0ec102cd9b0f80af
+--- /dev/null
++++ b/src/main/java/co/aikar/timings/TimingsExport.java
+@@ -0,0 +1,386 @@
++/*
++ * This file is licensed under the MIT License (MIT).
++ *
++ * Copyright (c) 2014 Daniel Ennis <http://aikar.co>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a copy
++ * of this software and associated documentation files (the "Software"), to deal
++ * in the Software without restriction, including without limitation the rights
++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++ * copies of the Software, and to permit persons to whom the Software is
++ * furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice shall be included in
++ * all copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
++ * THE SOFTWARE.
++ */
++package co.aikar.timings;
++
++import com.google.common.collect.Sets;
++import io.papermc.paper.adventure.PaperAdventure;
++import net.kyori.adventure.text.event.ClickEvent;
++import net.kyori.adventure.text.format.NamedTextColor;
++import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
++import net.minecraft.server.MinecraftServer;
++import org.apache.commons.lang.StringUtils;
++import org.bukkit.Bukkit;
++import org.bukkit.Material;
++import org.bukkit.configuration.ConfigurationSection;
++import org.bukkit.configuration.MemorySection;
++import org.bukkit.entity.EntityType;
++import org.json.simple.JSONObject;
++import org.json.simple.JSONValue;
++import oshi.SystemInfo;
++import oshi.hardware.HardwareAbstractionLayer;
++
++import java.io.ByteArrayOutputStream;
++import java.io.IOException;
++import java.io.InputStream;
++import java.io.OutputStream;
++import java.lang.management.ManagementFactory;
++import java.lang.management.OperatingSystemMXBean;
++import java.lang.management.RuntimeMXBean;
++import java.net.HttpURLConnection;
++import java.net.InetAddress;
++import java.net.URL;
++import java.util.List;
++import java.util.Map;
++import java.util.Set;
++import java.util.logging.Level;
++import java.util.zip.GZIPOutputStream;
++
++import static co.aikar.timings.TimingsManager.HISTORY;
++import static co.aikar.util.JSONUtil.appendObjectData;
++import static co.aikar.util.JSONUtil.createObject;
++import static co.aikar.util.JSONUtil.pair;
++import static co.aikar.util.JSONUtil.toArray;
++import static co.aikar.util.JSONUtil.toArrayMapper;
++import static co.aikar.util.JSONUtil.toObjectMapper;
++import static net.kyori.adventure.text.Component.text;
++
++@SuppressWarnings({"rawtypes", "SuppressionAnnotation"})
++@Deprecated(forRemoval = true)
++public class TimingsExport extends Thread {
++
++ private final TimingsReportListener listeners;
++ private final Map out;
++ private final TimingHistory[] history;
++ private static long lastReport = 0;
++
++ private TimingsExport(TimingsReportListener listeners, Map out, TimingHistory[] history) {
++ super("Timings paste thread");
++ this.listeners = listeners;
++ this.out = out;
++ this.history = history;
++ }
++
++ /**
++ * Checks if any pending reports are being requested, and builds one if needed.
++ */
++ public static void reportTimings() {
++ if (Timings.requestingReport.isEmpty()) {
++ return;
++ }
++ TimingsReportListener listeners = new TimingsReportListener(Timings.requestingReport);
++ listeners.addConsoleIfNeeded();
++
++ Timings.requestingReport.clear();
++ long now = System.currentTimeMillis();
++ final long lastReportDiff = now - lastReport;
++ if (lastReportDiff < 60000) {
++ listeners.sendMessage(text("Please wait at least 1 minute in between Timings reports. (" + (int)((60000 - lastReportDiff) / 1000) + " seconds)", NamedTextColor.RED));
++ listeners.done();
++ return;
++ }
++ final long lastStartDiff = now - TimingsManager.timingStart;
++ if (lastStartDiff < 180000) {
++ listeners.sendMessage(text("Please wait at least 3 minutes before generating a Timings report. Unlike Timings v1, v2 benefits from longer timings and is not as useful with short timings. (" + (int)((180000 - lastStartDiff) / 1000) + " seconds)", NamedTextColor.RED));
++ listeners.done();
++ return;
++ }
++ listeners.sendMessage(text("Preparing Timings Report...", NamedTextColor.GREEN));
++ lastReport = now;
++ Map parent = createObject(
++ // Get some basic system details about the server
++ pair("version", Bukkit.getVersion()),
++ pair("maxplayers", Bukkit.getMaxPlayers()),
++ pair("start", TimingsManager.timingStart / 1000),
++ pair("end", System.currentTimeMillis() / 1000),
++ pair("online-mode", Bukkit.getServer().getOnlineMode()),
++ pair("sampletime", (System.currentTimeMillis() - TimingsManager.timingStart) / 1000),
++ pair("datapacks", toArrayMapper(MinecraftServer.getServer().getPackRepository().getSelectedPacks(), pack -> {
++ return PlainTextComponentSerializer.plainText().serialize(PaperAdventure.asAdventure(pack.getChatLink(true)));
++ }))
++ );
++ if (!TimingsManager.privacy) {
++ appendObjectData(parent,
++ pair("server", Bukkit.getUnsafe().getTimingsServerName()),
++ pair("motd", Bukkit.getServer().getMotd()),
++ pair("icon", Bukkit.getServer().getServerIcon().getData())
++ );
++ }
++
++ final Runtime runtime = Runtime.getRuntime();
++ RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
++
++ OperatingSystemMXBean osInfo = ManagementFactory.getOperatingSystemMXBean();
++
++ HardwareAbstractionLayer hardwareInfo = new SystemInfo().getHardware();
++
++ parent.put("system", createObject(
++ pair("timingcost", getCost()),
++ pair("loadavg", osInfo.getSystemLoadAverage()),
++ pair("name", System.getProperty("os.name")),
++ pair("version", System.getProperty("os.version")),
++ pair("jvmversion", System.getProperty("java.version")),
++ pair("jvmvendor", System.getProperty("java.vendor")),
++ pair("jvmvendorversion", System.getProperty("java.vendor.version")),
++ pair("arch", System.getProperty("os.arch")),
++ pair("maxmem", runtime.maxMemory()),
++ pair("memory", createObject(
++ pair("heap", ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().toString()),
++ pair("nonheap", ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage().toString()),
++ pair("finalizing", ManagementFactory.getMemoryMXBean().getObjectPendingFinalizationCount())
++ )),
++ pair("cpu", runtime.availableProcessors()),
++ pair("cpuname", hardwareInfo.getProcessor().getProcessorIdentifier().getName().trim()),
++ pair("runtime", runtimeBean.getUptime()),
++ pair("flags", StringUtils.join(runtimeBean.getInputArguments(), " ")),
++ pair("gc", toObjectMapper(ManagementFactory.getGarbageCollectorMXBeans(), input -> pair(input.getName(), toArray(input.getCollectionCount(), input.getCollectionTime()))))
++ )
++ );
++
++ parent.put("worlds", toObjectMapper(MinecraftServer.getServer().getAllLevels(), world -> {
++ if (world.getWorld().getName().equals("worldeditregentempworld")) return null;
++ return pair(world.getWorld().getName(), createObject(
++ pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> {
++ return pair(rule, world.getWorld().getGameRuleValue(rule));
++ })),
++ pair("ticking-distance", world.getChunkSource().chunkMap.getEffectiveViewDistance())
++ ));
++ }));
++
++ Set<Material> tileEntityTypeSet = Sets.newHashSet();
++ Set<EntityType> entityTypeSet = Sets.newHashSet();
++
++ int size = HISTORY.size();
++ TimingHistory[] history = new TimingHistory[size + 1];
++ int i = 0;
++ for (TimingHistory timingHistory : HISTORY) {
++ tileEntityTypeSet.addAll(timingHistory.tileEntityTypeSet);
++ entityTypeSet.addAll(timingHistory.entityTypeSet);
++ history[i++] = timingHistory;
++ }
++
++ history[i] = new TimingHistory(); // Current snapshot
++ tileEntityTypeSet.addAll(history[i].tileEntityTypeSet);
++ entityTypeSet.addAll(history[i].entityTypeSet);
++
++
++ Map handlers = createObject();
++ Map groupData;
++ synchronized (TimingIdentifier.GROUP_MAP) {
++ for (TimingIdentifier.TimingGroup group : TimingIdentifier.GROUP_MAP.values()) {
++ synchronized (group.handlers) {
++ for (TimingHandler id : group.handlers) {
++
++ if (!id.isTimed() && !id.isSpecial()) {
++ continue;
++ }
++
++ String name = id.identifier.name;
++ if (name.startsWith("##")) {
++ name = name.substring(3);
++ }
++ handlers.put(id.id, toArray(
++ group.id,
++ name
++ ));
++ }
++ }
++ }
++
++ groupData = toObjectMapper(
++ TimingIdentifier.GROUP_MAP.values(), group -> pair(group.id, group.name));
++ }
++
++ parent.put("idmap", createObject(
++ pair("groups", groupData),
++ pair("handlers", handlers),
++ pair("worlds", toObjectMapper(TimingHistory.worldMap.entrySet(), input -> pair(input.getValue(), input.getKey()))),
++ pair("tileentity",
++ toObjectMapper(tileEntityTypeSet, input -> pair(input.ordinal(), input.name()))),
++ pair("entity",
++ toObjectMapper(entityTypeSet, input -> pair(input.ordinal(), input.name())))
++ ));
++
++ // Information about loaded plugins
++
++ parent.put("plugins", toObjectMapper(Bukkit.getPluginManager().getPlugins(),
++ plugin -> pair(plugin.getName(), createObject(
++ pair("version", plugin.getDescription().getVersion()),
++ pair("description", String.valueOf(plugin.getDescription().getDescription()).trim()),
++ pair("website", plugin.getDescription().getWebsite()),
++ pair("authors", StringUtils.join(plugin.getDescription().getAuthors(), ", "))
++ ))));
++
++
++
++ // Information on the users Config
++
++ parent.put("config", createObject(
++ pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)),
++ pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)),
++ pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null))
++ ));
++
++ new TimingsExport(listeners, parent, history).start();
++ }
++
++ static long getCost() {
++ // Benchmark the users System.nanotime() for cost basis
++ int passes = 100;
++ TimingHandler SAMPLER1 = Timings.ofSafe("Timings Sampler 1");
++ TimingHandler SAMPLER2 = Timings.ofSafe("Timings Sampler 2");
++ TimingHandler SAMPLER3 = Timings.ofSafe("Timings Sampler 3");
++ TimingHandler SAMPLER4 = Timings.ofSafe("Timings Sampler 4");
++ TimingHandler SAMPLER5 = Timings.ofSafe("Timings Sampler 5");
++ TimingHandler SAMPLER6 = Timings.ofSafe("Timings Sampler 6");
++
++ long start = System.nanoTime();
++ for (int i = 0; i < passes; i++) {
++ SAMPLER1.startTiming();
++ SAMPLER2.startTiming();
++ SAMPLER3.startTiming();
++ SAMPLER3.stopTiming();
++ SAMPLER4.startTiming();
++ SAMPLER5.startTiming();
++ SAMPLER6.startTiming();
++ SAMPLER6.stopTiming();
++ SAMPLER5.stopTiming();
++ SAMPLER4.stopTiming();
++ SAMPLER2.stopTiming();
++ SAMPLER1.stopTiming();
++ }
++ long timingsCost = (System.nanoTime() - start) / passes / 6;
++ SAMPLER1.reset(true);
++ SAMPLER2.reset(true);
++ SAMPLER3.reset(true);
++ SAMPLER4.reset(true);
++ SAMPLER5.reset(true);
++ SAMPLER6.reset(true);
++ return timingsCost;
++ }
++
++ private static JSONObject mapAsJSON(ConfigurationSection config, String parentKey) {
++
++ JSONObject object = new JSONObject();
++ for (String key : config.getKeys(false)) {
++ String fullKey = (parentKey != null ? parentKey + "." + key : key);
++ if (fullKey.equals("database") || fullKey.equals("settings.bungeecord-addresses") || TimingsManager.hiddenConfigs.contains(fullKey) || key.startsWith("seed-") || key.equals("worldeditregentempworld")) {
++ continue;
++ }
++ final Object val = config.get(key);
++
++ object.put(key, valAsJSON(val, fullKey));
++ }
++ return object;
++ }
++
++ private static Object valAsJSON(Object val, final String parentKey) {
++ if (!(val instanceof MemorySection)) {
++ if (val instanceof List) {
++ Iterable<Object> v = (Iterable<Object>) val;
++ return toArrayMapper(v, input -> valAsJSON(input, parentKey));
++ } else {
++ return String.valueOf(val);
++ }
++ } else {
++ return mapAsJSON((ConfigurationSection) val, parentKey);
++ }
++ }
++
++ @Override
++ public void run() {
++ out.put("data", toArrayMapper(history, TimingHistory::export));
++
++
++ String response = null;
++ String timingsURL = null;
++ try {
++ HttpURLConnection con = (HttpURLConnection) new URL(TimingsManager.url + "post").openConnection();
++ con.setDoOutput(true);
++ String hostName = "BrokenHost";
++ try {
++ hostName = InetAddress.getLocalHost().getHostName();
++ } catch (Exception ignored) {}
++ con.setRequestProperty("User-Agent", "Paper/" + Bukkit.getUnsafe().getTimingsServerName() + "/" + hostName);
++ con.setRequestMethod("POST");
++ con.setInstanceFollowRedirects(false);
++
++ OutputStream request = new GZIPOutputStream(con.getOutputStream()) {{
++ this.def.setLevel(7);
++ }};
++
++ request.write(JSONValue.toJSONString(out).getBytes("UTF-8"));
++ request.close();
++
++ response = getResponse(con);
++
++ if (con.getResponseCode() != 302) {
++ listeners.sendMessage(text( "Upload Error: " + con.getResponseCode() + ": " + con.getResponseMessage(), NamedTextColor.RED));
++ listeners.sendMessage(text("Check your logs for more information", NamedTextColor.RED));
++ if (response != null) {
++ Bukkit.getLogger().log(Level.SEVERE, response);
++ }
++ return;
++ }
++
++ timingsURL = con.getHeaderField("Location");
++ listeners.sendMessage(text("View Timings Report: ", NamedTextColor.GREEN).append(text(timingsURL).clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, timingsURL))));
++
++ if (response != null && !response.isEmpty()) {
++ Bukkit.getLogger().log(Level.INFO, "Timing Response: " + response);
++ }
++ } catch (IOException ex) {
++ listeners.sendMessage(text("Error uploading timings, check your logs for more information", NamedTextColor.RED));
++ if (response != null) {
++ Bukkit.getLogger().log(Level.SEVERE, response);
++ }
++ Bukkit.getLogger().log(Level.SEVERE, "Could not paste timings", ex);
++ } finally {
++ this.listeners.done(timingsURL);
++ }
++ }
++
++ private String getResponse(HttpURLConnection con) throws IOException {
++ InputStream is = null;
++ try {
++ is = con.getInputStream();
++ ByteArrayOutputStream bos = new ByteArrayOutputStream();
++
++ byte[] b = new byte[1024];
++ int bytesRead;
++ while ((bytesRead = is.read(b)) != -1) {
++ bos.write(b, 0, bytesRead);
++ }
++ return bos.toString();
++
++ } catch (IOException ex) {
++ listeners.sendMessage(text("Error uploading timings, check your logs for more information", NamedTextColor.RED));
++ Bukkit.getLogger().log(Level.WARNING, con.getResponseMessage(), ex);
++ return null;
++ } finally {
++ if (is != null) {
++ is.close();
++ }
++ }
++ }
++}
+diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..2f0d9b953802dee821cfde82d22b0567cce8ee91
+--- /dev/null
++++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
+@@ -0,0 +1,120 @@
++package co.aikar.timings;
++
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.world.level.Level;
++import net.minecraft.world.level.storage.PrimaryLevelData;
++
++/**
++ * Set of timers per world, to track world specific timings.
++ */
++// TODO: Re-implement missing timers
++@Deprecated(forRemoval = true)
++public class WorldTimingsHandler {
++ public final Timing mobSpawn;
++ public final Timing doChunkUnload;
++ public final Timing doPortalForcer;
++ public final Timing scheduledBlocks;
++ public final Timing scheduledBlocksCleanup;
++ public final Timing scheduledBlocksTicking;
++ public final Timing chunkTicks;
++ public final Timing lightChunk;
++ public final Timing chunkTicksBlocks;
++ public final Timing doVillages;
++ public final Timing doChunkMap;
++ public final Timing doChunkMapUpdate;
++ public final Timing doChunkMapToUpdate;
++ public final Timing doChunkMapSortMissing;
++ public final Timing doChunkMapSortSendToPlayers;
++ public final Timing doChunkMapPlayersNeedingChunks;
++ public final Timing doChunkMapPendingSendToPlayers;
++ public final Timing doChunkMapUnloadChunks;
++ public final Timing doChunkGC;
++ public final Timing doSounds;
++ public final Timing entityRemoval;
++ public final Timing entityTick;
++ public final Timing tileEntityTick;
++ public final Timing tileEntityPending;
++ public final Timing tracker1;
++ public final Timing tracker2;
++ public final Timing doTick;
++ public final Timing tickEntities;
++ public final Timing chunks;
++ public final Timing newEntities;
++ public final Timing raids;
++ public final Timing chunkProviderTick;
++ public final Timing broadcastChunkUpdates;
++ public final Timing countNaturalMobs;
++
++ public final Timing chunkLoad;
++ public final Timing chunkLoadPopulate;
++ public final Timing syncChunkLoad;
++ public final Timing chunkLoadLevelTimer;
++ public final Timing chunkIO;
++ public final Timing chunkPostLoad;
++ public final Timing worldSave;
++ public final Timing worldSaveChunks;
++ public final Timing worldSaveLevel;
++ public final Timing chunkSaveData;
++
++
++ public final Timing miscMobSpawning;
++
++ public WorldTimingsHandler(Level server) {
++ String name = ((PrimaryLevelData) server.getLevelData()).getLevelName() + " - ";
++
++ mobSpawn = Timings.ofSafe(name + "mobSpawn");
++ doChunkUnload = Timings.ofSafe(name + "doChunkUnload");
++ scheduledBlocks = Timings.ofSafe(name + "Scheduled Blocks");
++ scheduledBlocksCleanup = Timings.ofSafe(name + "Scheduled Blocks - Cleanup");
++ scheduledBlocksTicking = Timings.ofSafe(name + "Scheduled Blocks - Ticking");
++ chunkTicks = Timings.ofSafe(name + "Chunk Ticks");
++ lightChunk = Timings.ofSafe(name + "Light Chunk");
++ chunkTicksBlocks = Timings.ofSafe(name + "Chunk Ticks - Blocks");
++ doVillages = Timings.ofSafe(name + "doVillages");
++ doChunkMap = Timings.ofSafe(name + "doChunkMap");
++ doChunkMapUpdate = Timings.ofSafe(name + "doChunkMap - Update");
++ doChunkMapToUpdate = Timings.ofSafe(name + "doChunkMap - To Update");
++ doChunkMapSortMissing = Timings.ofSafe(name + "doChunkMap - Sort Missing");
++ doChunkMapSortSendToPlayers = Timings.ofSafe(name + "doChunkMap - Sort Send To Players");
++ doChunkMapPlayersNeedingChunks = Timings.ofSafe(name + "doChunkMap - Players Needing Chunks");
++ doChunkMapPendingSendToPlayers = Timings.ofSafe(name + "doChunkMap - Pending Send To Players");
++ doChunkMapUnloadChunks = Timings.ofSafe(name + "doChunkMap - Unload Chunks");
++ doSounds = Timings.ofSafe(name + "doSounds");
++ doChunkGC = Timings.ofSafe(name + "doChunkGC");
++ doPortalForcer = Timings.ofSafe(name + "doPortalForcer");
++ entityTick = Timings.ofSafe(name + "entityTick");
++ entityRemoval = Timings.ofSafe(name + "entityRemoval");
++ tileEntityTick = Timings.ofSafe(name + "tileEntityTick");
++ tileEntityPending = Timings.ofSafe(name + "tileEntityPending");
++
++ chunkLoad = Timings.ofSafe(name + "Chunk Load");
++ chunkLoadPopulate = Timings.ofSafe(name + "Chunk Load - Populate");
++ syncChunkLoad = Timings.ofSafe(name + "Sync Chunk Load");
++ chunkLoadLevelTimer = Timings.ofSafe(name + "Chunk Load - Load Level");
++ chunkIO = Timings.ofSafe(name + "Chunk Load - DiskIO");
++ chunkPostLoad = Timings.ofSafe(name + "Chunk Load - Post Load");
++ worldSave = Timings.ofSafe(name + "World Save");
++ worldSaveLevel = Timings.ofSafe(name + "World Save - Level");
++ worldSaveChunks = Timings.ofSafe(name + "World Save - Chunks");
++ chunkSaveData = Timings.ofSafe(name + "Chunk Save - Data");
++
++ tracker1 = Timings.ofSafe(name + "tracker stage 1");
++ tracker2 = Timings.ofSafe(name + "tracker stage 2");
++ doTick = Timings.ofSafe(name + "doTick");
++ tickEntities = Timings.ofSafe(name + "tickEntities");
++
++ chunks = Timings.ofSafe(name + "Chunks");
++ newEntities = Timings.ofSafe(name + "New entity registration");
++ raids = Timings.ofSafe(name + "Raids");
++ chunkProviderTick = Timings.ofSafe(name + "Chunk provider tick");
++ broadcastChunkUpdates = Timings.ofSafe(name + "Broadcast chunk updates");
++ countNaturalMobs = Timings.ofSafe(name + "Count natural mobs");
++
++
++ miscMobSpawning = Timings.ofSafe(name + "Mob spawning - Misc");
++ }
++
++ public static Timing getTickList(ServerLevel worldserver, String timingsType) {
++ return Timings.ofSafe(((PrimaryLevelData) worldserver.getLevelData()).getLevelName() + " - Scheduled " + timingsType);
++ }
++}
+diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java
+index daedf825e68655492f5ab776bc206a5eb87c0170..7de24c39b460e43d27839b3821e67213508ece81 100644
+--- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java
++++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java
+@@ -29,7 +29,8 @@ public class PacketUtils {
+ engine.executeIfPossible(() -> {
+ if (MinecraftServer.getServer().hasStopped() || (listener instanceof ServerCommonPacketListenerImpl && ((ServerCommonPacketListenerImpl) listener).processedDisconnect)) return; // CraftBukkit, MC-142590
+ 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 exception) {
+ label25:
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index 1eb0809addfd77303b94bb594701ee7f38483909..337a1e89ba63471ea6b413cdec3e68d343a0b46b 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -186,7 +186,7 @@ import org.bukkit.craftbukkit.Main;
+ import org.bukkit.event.server.ServerLoadEvent;
+ // CraftBukkit end
+
+-import org.bukkit.craftbukkit.SpigotTimings; // Spigot
++import co.aikar.timings.MinecraftTimings; // Paper
+
+ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTask> implements ServerInfo, CommandSource, AutoCloseable {
+
+@@ -891,6 +891,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ }
+
+ MinecraftServer.LOGGER.info("Stopping server");
++ MinecraftTimings.stopServer(); // Paper
+ // CraftBukkit start
+ if (this.server != null) {
+ this.server.disablePlugins();
+@@ -1144,9 +1145,21 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+
+ private boolean haveTime() {
+ // CraftBukkit start
++ if (isOversleep) return canOversleep();// Paper - because of our changes, this logic is broken
+ return this.forceTicks || this.runningTask() || Util.getNanos() < (this.mayHaveDelayedTasks ? this.delayedTasksMaxNextTickTimeNanos : this.nextTickTimeNanos);
+ }
+
++ // Paper start
++ boolean isOversleep = false;
++ private boolean canOversleep() {
++ return this.mayHaveDelayedTasks && Util.getMillis() < this.delayedTasksMaxNextTickTime;
++ }
++
++ private boolean canSleepForTickNoOversleep() {
++ return this.forceTicks || this.runningTask() || Util.getMillis() < this.nextTickTime;
++ }
++ // Paper end
++
+ private void executeModerately() {
+ this.runAllTasks();
+ java.util.concurrent.locks.LockSupport.parkNanos("executing tasks", 1000L);
+@@ -1154,9 +1167,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ }
+
+ protected void waitUntilNextTick() {
+- this.runAllTasks();
++ //this.executeAll(); // Paper - move this into the tick method for timings
+ this.managedBlock(() -> {
+- return !this.haveTime();
++ return !this.canSleepForTickNoOversleep(); // Paper - move oversleep into full server tick
+ });
+ }
+
+@@ -1245,9 +1258,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ }
+
+ public void tickServer(BooleanSupplier shouldKeepTicking) {
+- SpigotTimings.serverTickTimer.startTiming(); // Spigot
++ co.aikar.timings.TimingsManager.FULL_SERVER_TICK.startTiming(); // Paper
+ long i = Util.getNanos();
+
++ // Paper start - move oversleep into full server tick
++ isOversleep = true;MinecraftTimings.serverOversleep.startTiming();
++ this.managedBlock(() -> {
++ return !this.canOversleep();
++ });
++ isOversleep = false;MinecraftTimings.serverOversleep.stopTiming();
++ // Paper end
++
+ ++this.tickCount;
+ this.tickRateManager.tick();
+ this.tickChildren(shouldKeepTicking);
+@@ -1261,15 +1282,18 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ if (this.autosavePeriod > 0 && this.ticksUntilAutosave <= 0) {
+ this.ticksUntilAutosave = this.autosavePeriod;
+ // CraftBukkit end
+- SpigotTimings.worldSaveTimer.startTiming(); // Spigot
+ MinecraftServer.LOGGER.debug("Autosave started");
+ this.profiler.push("save");
+ this.saveEverything(true, false, false);
+ this.profiler.pop();
+ MinecraftServer.LOGGER.debug("Autosave finished");
+- SpigotTimings.worldSaveTimer.stopTiming(); // Spigot
+ }
+ io.papermc.paper.util.CachedLists.reset(); // Paper
++ // Paper start - move executeAll() into full server tick timing
++ try (co.aikar.timings.Timing ignored = MinecraftTimings.processTasksTimer.startTiming()) {
++ this.runAllTasks();
++ }
++ // Paper end
+ this.profiler.push("tallying");
+ long j = Util.getNanos() - i;
+ int k = this.tickCount % 100;
+@@ -1283,8 +1307,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ this.logTickTime(l - i);
+ this.profiler.pop();
+ org.spigotmc.WatchdogThread.tick(); // Spigot
+- SpigotTimings.serverTickTimer.stopTiming(); // Spigot
+- org.spigotmc.CustomTimingsHandler.tick(); // Spigot
++ co.aikar.timings.TimingsManager.FULL_SERVER_TICK.stopTiming(); // Paper
+ }
+
+ private int computeNextAutosaveInterval() {
+@@ -1346,26 +1369,26 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ this.getPlayerList().getPlayers().forEach((entityplayer) -> {
+ entityplayer.connection.suspendFlushing();
+ });
+- SpigotTimings.schedulerTimer.startTiming(); // Spigot
++ MinecraftTimings.bukkitSchedulerTimer.startTiming(); // Spigot // Paper
+ this.server.getScheduler().mainThreadHeartbeat(this.tickCount); // CraftBukkit
+- SpigotTimings.schedulerTimer.stopTiming(); // Spigot
++ MinecraftTimings.bukkitSchedulerTimer.stopTiming(); // Spigot // Paper
+ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper
+ this.profiler.push("commandFunctions");
+- SpigotTimings.commandFunctionsTimer.startTiming(); // Spigot
++ MinecraftTimings.commandFunctionsTimer.startTiming(); // Spigot // Paper
+ this.getFunctions().tick();
+- SpigotTimings.commandFunctionsTimer.stopTiming(); // Spigot
++ MinecraftTimings.commandFunctionsTimer.stopTiming(); // Spigot // Paper
+ this.profiler.popPush("levels");
+ Iterator iterator = this.getAllLevels().iterator();
+
+ // CraftBukkit start
+ // Run tasks that are waiting on processing
+- SpigotTimings.processQueueTimer.startTiming(); // Spigot
++ MinecraftTimings.processQueueTimer.startTiming(); // Spigot
+ while (!this.processQueue.isEmpty()) {
+ this.processQueue.remove().run();
+ }
+- SpigotTimings.processQueueTimer.stopTiming(); // Spigot
++ MinecraftTimings.processQueueTimer.stopTiming(); // Spigot
+
+- SpigotTimings.timeUpdateTimer.startTiming(); // Spigot
++ MinecraftTimings.timeUpdateTimer.startTiming(); // Spigot // Paper
+ // Send time updates to everyone, it will get the right time from the world the player is in.
+ if (this.tickCount % 20 == 0) {
+ for (int i = 0; i < this.getPlayerList().players.size(); ++i) {
+@@ -1373,7 +1396,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ entityplayer.connection.send(new ClientboundSetTimePacket(entityplayer.level().getGameTime(), entityplayer.getPlayerTime(), entityplayer.level().getGameRules().getBoolean(GameRules.RULE_DAYLIGHT))); // Add support for per player time
+ }
+ }
+- SpigotTimings.timeUpdateTimer.stopTiming(); // Spigot
++ MinecraftTimings.timeUpdateTimer.stopTiming(); // Spigot // Paper
+
+ while (iterator.hasNext()) {
+ ServerLevel worldserver = (ServerLevel) iterator.next();
+@@ -1419,24 +1442,24 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ }
+
+ this.profiler.popPush("connection");
+- SpigotTimings.connectionTimer.startTiming(); // Spigot
++ MinecraftTimings.connectionTimer.startTiming(); // Spigot // Paper
+ this.getConnection().tick();
+- SpigotTimings.connectionTimer.stopTiming(); // Spigot
++ MinecraftTimings.connectionTimer.stopTiming(); // Spigot // Paper
+ this.profiler.popPush("players");
+- SpigotTimings.playerListTimer.startTiming(); // Spigot
++ MinecraftTimings.playerListTimer.startTiming(); // Spigot // Paper
+ this.playerList.tick();
+- SpigotTimings.playerListTimer.stopTiming(); // Spigot
++ MinecraftTimings.playerListTimer.stopTiming(); // Spigot // Paper
+ if (SharedConstants.IS_RUNNING_IN_IDE && this.tickRateManager.runsNormally()) {
+ GameTestTicker.SINGLETON.tick();
+ }
+
+ this.profiler.popPush("server gui refresh");
+
+- SpigotTimings.tickablesTimer.startTiming(); // Spigot
++ MinecraftTimings.tickablesTimer.startTiming(); // Spigot // Paper
+ for (int i = 0; i < this.tickables.size(); ++i) {
+ ((Runnable) this.tickables.get(i)).run();
+ }
+- SpigotTimings.tickablesTimer.stopTiming(); // Spigot
++ MinecraftTimings.tickablesTimer.stopTiming(); // Spigot // Paper
+
+ this.profiler.popPush("send chunks");
+ iterator = this.playerList.getPlayers().iterator();
+diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+index 4b303d8acc663cfd3b86e37f4a3110d5335d07ad..cb95818bfb5f0a9274b4e0f2530000bfca7ffc87 100644
+--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+@@ -58,8 +58,9 @@ import org.apache.logging.log4j.Level;
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.io.IoBuilder;
+ import org.bukkit.command.CommandSender;
+-import org.bukkit.craftbukkit.SpigotTimings; // Spigot
++import co.aikar.timings.MinecraftTimings; // Paper
+ import org.bukkit.event.server.ServerCommandEvent;
++import org.bukkit.craftbukkit.util.Waitable; // Paper
+ import org.bukkit.event.server.RemoteServerCommandEvent;
+ // CraftBukkit end
+
+@@ -403,7 +404,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+ }
+
+ public void handleConsoleInputs() {
+- SpigotTimings.serverCommandTimer.startTiming(); // Spigot
++ MinecraftTimings.serverCommandTimer.startTiming(); // Spigot
+ while (!this.consoleInput.isEmpty()) {
+ ConsoleInput servercommand = (ConsoleInput) this.consoleInput.remove(0);
+
+@@ -418,7 +419,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+ // CraftBukkit end
+ }
+
+- SpigotTimings.serverCommandTimer.stopTiming(); // Spigot
++ MinecraftTimings.serverCommandTimer.stopTiming(); // Spigot
+ }
+
+ @Override
+@@ -676,7 +677,9 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+ }
+
+ public String runCommand(RconConsoleSource rconConsoleSource, String s) {
++ Waitable[] waitableArray = new Waitable[1]; // Paper
+ rconConsoleSource.prepareForCommand();
++ final java.util.concurrent.atomic.AtomicReference<String> command = new java.util.concurrent.atomic.AtomicReference<>(s); // Paper
+ this.executeBlocking(() -> {
+ CommandSourceStack wrapper = rconConsoleSource.createCommandSourceStack();
+ RemoteServerCommandEvent event = new RemoteServerCommandEvent(rconConsoleSource.getBukkitSender(wrapper), s);
+@@ -684,9 +687,39 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+ if (event.isCancelled()) {
+ return;
+ }
++ // Paper start
++ command.set(event.getCommand());
++ if (event.getCommand().toLowerCase().startsWith("timings") && event.getCommand().toLowerCase().matches("timings (report|paste|get|merged|seperate)")) {
++ org.bukkit.command.BufferedCommandSender sender = new org.bukkit.command.BufferedCommandSender();
++ Waitable<String> waitable = new Waitable<>() {
++ @Override
++ protected String evaluate() {
++ return sender.getBuffer();
++ }
++ };
++ waitableArray[0] = waitable;
++ co.aikar.timings.Timings.generateReport(new co.aikar.timings.TimingsReportListener(sender, waitable));
++ } else {
++ // Paper end
+ ConsoleInput serverCommand = new ConsoleInput(event.getCommand(), wrapper);
+ this.server.dispatchServerCommand(event.getSender(), serverCommand);
++ } // Paper
+ });
++ // Paper start
++ if (waitableArray[0] != null) {
++ //noinspection unchecked
++ Waitable<String> waitable = waitableArray[0];
++ try {
++ return waitable.get();
++ } catch (java.util.concurrent.ExecutionException e) {
++ throw new RuntimeException("Exception processing rcon command " + command.get(), e.getCause());
++ } catch (InterruptedException e) {
++ Thread.currentThread().interrupt(); // Maintain interrupted state
++ throw new RuntimeException("Interrupted processing rcon command " + command.get(), e);
++ }
++
++ }
++ // Paper end
+ return rconConsoleSource.getCommandResponse();
+ // CraftBukkit end
+ }
+diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
+index 770f6a08c613093577172385549df8040ded627b..c1bb511cc25ca97282ee02d2b207e2940279c190 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
+@@ -1,8 +1,10 @@
+ package net.minecraft.server.level;
+
++import co.aikar.timings.Timing; // Paper
+ import com.google.common.collect.ImmutableList;
+ import com.google.common.collect.ImmutableList.Builder;
+ import com.google.common.collect.Iterables;
++import com.google.common.collect.ComparisonChain; // Paper
+ import com.google.common.collect.Lists;
+ import com.google.common.collect.Queues;
+ import com.google.common.collect.Sets;
+@@ -896,6 +898,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ ChunkStatus chunkstatus = ChunkLevel.generationStatus(chunkHolder.getTicketLevel());
+
+ return !chunkstatus.isOrAfter(ChunkStatus.FULL) ? ChunkHolder.UNLOADED_CHUNK : either.mapLeft((ichunkaccess) -> {
++ try (Timing ignored = level.timings.chunkPostLoad.startTimingIfSync()) { // Paper
+ ChunkPos chunkcoordintpair = chunkHolder.getPos();
+ ProtoChunk protochunk = (ProtoChunk) ichunkaccess;
+ LevelChunk chunk;
+@@ -920,6 +923,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ }
+
+ return chunk;
++ } // Paper
+ });
+ }, (runnable) -> {
+ ProcessorHandle mailbox = this.mainThreadMailbox;
+@@ -1471,6 +1475,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ List<ServerPlayer> list = Lists.newArrayList();
+ List<ServerPlayer> list1 = this.level.players();
+ ObjectIterator objectiterator = this.entityMap.values().iterator();
++ level.timings.tracker1.startTiming(); // Paper
+
+ ChunkMap.TrackedEntity playerchunkmap_entitytracker;
+
+@@ -1495,14 +1500,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ playerchunkmap_entitytracker.serverEntity.sendChanges();
+ }
+ }
++ level.timings.tracker1.stopTiming(); // Paper
+
+ if (!list.isEmpty()) {
+ objectiterator = this.entityMap.values().iterator();
+
++ level.timings.tracker2.startTiming(); // Paper
+ while (objectiterator.hasNext()) {
+ playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next();
+ playerchunkmap_entitytracker.updatePlayers(list);
+ }
++ level.timings.tracker2.stopTiming(); // Paper
+ }
+
+ }
+diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+index 7cacfceed5ef9276a19123a8a9079579423d03ac..d62f6ef628f591e4362c4aa7db9d38fa2304af4c 100644
+--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+@@ -413,13 +413,15 @@ public class ServerChunkCache extends ChunkSource {
+ }
+
+ gameprofilerfiller.incrementCounter("getChunkCacheMiss");
+- this.level.timings.syncChunkLoadTimer.startTiming(); // Spigot
+ CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create);
+ ServerChunkCache.MainThreadExecutor chunkproviderserver_b = this.mainThreadProcessor;
+
+ Objects.requireNonNull(completablefuture);
++ if (!completablefuture.isDone()) { // Paper
++ this.level.timings.syncChunkLoad.startTiming(); // Paper
+ chunkproviderserver_b.managedBlock(completablefuture::isDone);
+- this.level.timings.syncChunkLoadTimer.stopTiming(); // Spigot
++ this.level.timings.syncChunkLoad.stopTiming(); // Paper
++ } // Paper
+ ichunkaccess = (ChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> {
+ return ichunkaccess1;
+ }, (playerchunk_failure) -> {
+@@ -618,7 +620,9 @@ public class ServerChunkCache extends ChunkSource {
+
+ public void save(boolean flush) {
+ this.runDistanceManagerUpdates();
++ try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings
+ this.chunkMap.saveAllChunks(flush);
++ } // Paper - Timings
+ }
+
+ @Override
+@@ -657,10 +661,10 @@ public class ServerChunkCache extends ChunkSource {
+ this.level.timings.doChunkMap.stopTiming(); // Spigot
+ this.level.getProfiler().popPush("chunks");
+ if (tickChunks) {
++ this.level.timings.chunks.startTiming(); // Paper - timings
+ this.tickChunks();
+- this.level.timings.tracker.startTiming(); // Spigot
++ this.level.timings.chunks.stopTiming(); // Paper - timings
+ this.chunkMap.tick();
+- this.level.timings.tracker.stopTiming(); // Spigot
+ }
+
+ this.level.timings.doChunkUnload.startTiming(); // Spigot
+@@ -683,6 +687,7 @@ public class ServerChunkCache extends ChunkSource {
+ gameprofilerfiller.push("filteringLoadedChunks");
+ List<ServerChunkCache.ChunkAndHolder> list = Lists.newArrayListWithCapacity(this.chunkMap.size());
+ Iterator iterator = this.chunkMap.getChunks().iterator();
++ this.level.timings.chunkTicks.startTiming(); // Paper
+
+ while (iterator.hasNext()) {
+ ChunkHolder playerchunk = (ChunkHolder) iterator.next();
+@@ -695,8 +700,10 @@ public class ServerChunkCache extends ChunkSource {
+
+ if (this.level.getServer().tickRateManager().runsNormally()) {
+ gameprofilerfiller.popPush("naturalSpawnCount");
++ this.level.timings.countNaturalMobs.startTiming(); // Paper - timings
+ int k = this.distanceManager.getNaturalSpawnChunkCount();
+ NaturalSpawner.SpawnState spawnercreature_d = NaturalSpawner.createState(k, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap));
++ this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings
+
+ this.lastSpawnState = spawnercreature_d;
+ gameprofilerfiller.popPush("spawnAndTick");
+@@ -728,13 +735,17 @@ public class ServerChunkCache extends ChunkSource {
+
+ gameprofilerfiller.popPush("customSpawners");
+ if (flag) {
++ try (co.aikar.timings.Timing ignored = this.level.timings.miscMobSpawning.startTiming()) { // Paper - timings
+ this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies);
++ } // Paper - timings
+ }
+ }
+
+ gameprofilerfiller.popPush("broadcast");
+ list.forEach((chunkproviderserver_a1) -> {
++ this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing
+ chunkproviderserver_a1.holder.broadcastChanges(chunkproviderserver_a1.chunk);
++ this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
+ });
+ gameprofilerfiller.pop();
+ gameprofilerfiller.pop();
+diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+index 0995b63953d62198c117fdc7a8c4703a986d2db2..b698cdc4ef9e2bf824b9e5a4f985b6832c15f8f6 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -1,6 +1,8 @@
+ package net.minecraft.server.level;
+
+ import com.google.common.annotations.VisibleForTesting;
++import co.aikar.timings.TimingHistory; // Paper
++import co.aikar.timings.Timings; // Paper
+ import com.google.common.collect.Lists;
+ import com.mojang.datafixers.DataFixer;
+ import com.mojang.datafixers.util.Pair;
+@@ -167,7 +169,6 @@ import org.slf4j.Logger;
+ import org.bukkit.Bukkit;
+ import org.bukkit.Location;
+ import org.bukkit.WeatherType;
+-import org.bukkit.craftbukkit.SpigotTimings; // Spigot
+ import org.bukkit.craftbukkit.event.CraftEventFactory;
+ import org.bukkit.craftbukkit.generator.CustomWorldChunkManager;
+ import org.bukkit.craftbukkit.util.CraftNamespacedKey;
+@@ -472,7 +473,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
+ }
+
+ gameprofilerfiller.popPush("tickPending");
+- this.timings.doTickPending.startTiming(); // Spigot
++ this.timings.scheduledBlocks.startTiming(); // Paper
+ if (!this.isDebug() && flag) {
+ j = this.getGameTime();
+ gameprofilerfiller.push("blockTicks");
+@@ -481,15 +482,19 @@ public class ServerLevel extends Level implements WorldGenLevel {
+ this.fluidTicks.tick(j, 65536, this::tickFluid);
+ gameprofilerfiller.pop();
+ }
+- this.timings.doTickPending.stopTiming(); // Spigot
++ this.timings.scheduledBlocks.stopTiming(); // Paper
+
+ gameprofilerfiller.popPush("raid");
+ if (flag) {
++ this.timings.raids.startTiming(); // Paper - timings
+ this.raids.tick();
++ this.timings.raids.stopTiming(); // Paper - timings
+ }
+
+ gameprofilerfiller.popPush("chunkSource");
++ this.timings.chunkProviderTick.startTiming(); // Paper - timings
+ this.getChunkSource().tick(shouldKeepTicking, true);
++ this.timings.chunkProviderTick.stopTiming(); // Paper - timings
+ gameprofilerfiller.popPush("blockEvents");
+ if (flag) {
+ this.timings.doSounds.startTiming(); // Spigot
+@@ -642,6 +647,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
+ }
+
+ gameprofilerfiller.popPush("tickBlocks");
++ timings.chunkTicksBlocks.startTiming(); // Paper
+ if (randomTickSpeed > 0) {
+ LevelChunkSection[] achunksection = chunk.getSections();
+
+@@ -674,6 +680,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
+ }
+ }
+
++ timings.chunkTicksBlocks.stopTiming(); // Paper
+ gameprofilerfiller.pop();
+ }
+
+@@ -950,14 +957,22 @@ public class ServerLevel extends Level implements WorldGenLevel {
+ }
+
+ public void tickNonPassenger(Entity entity) {
++ ++TimingHistory.entityTicks; // Paper - timings
+ // Spigot start
++ co.aikar.timings.Timing timer; // Paper
+ if (!org.spigotmc.ActivationRange.checkIfActive(entity)) {
+ entity.tickCount++;
++ timer = entity.getType().inactiveTickTimer.startTiming(); try { // Paper - timings
+ entity.inactiveTick();
++ } finally { timer.stopTiming(); } // Paper
+ return;
+ }
+ // Spigot end
+- entity.tickTimer.startTiming(); // Spigot
++ // Paper start- timings
++ TimingHistory.activatedEntityTicks++;
++ timer = entity.getVehicle() != null ? entity.getType().passengerTickTimer.startTiming() : entity.getType().tickTimer.startTiming();
++ try {
++ // Paper end - timings
+ entity.setOldPosAndRot();
+ ProfilerFiller gameprofilerfiller = this.getProfiler();
+
+@@ -976,7 +991,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
+
+ this.tickPassenger(entity, entity1);
+ }
+- entity.tickTimer.stopTiming(); // Spigot
++ } finally { timer.stopTiming(); } // Paper - timings
+
+ }
+
+@@ -1018,6 +1033,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
+
+ if (!savingDisabled) {
+ org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(this.getWorld())); // CraftBukkit
++ try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) { // Paper
+ if (progressListener != null) {
+ progressListener.progressStartNoAbort(Component.translatable("menu.savingLevel"));
+ }
+@@ -1027,7 +1043,10 @@ public class ServerLevel extends Level implements WorldGenLevel {
+ progressListener.progressStage(Component.translatable("menu.savingChunks"));
+ }
+
++ timings.worldSaveChunks.startTiming(); // Paper
+ chunkproviderserver.save(flush);
++ timings.worldSaveChunks.stopTiming(); // Paper
++ }// Paper
+ if (flush) {
+ this.entityManager.saveAll();
+ } else {
+diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index bc9ca925db912b637def658fd123105ac1c75412..d6b9fee57d22da0eaf3dcc4abfd3995d69abef95 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -323,7 +323,6 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+
+ @Override
+ public void tick() {
+- org.bukkit.craftbukkit.SpigotTimings.playerConnectionTimer.startTiming(); // Spigot
+ if (this.ackBlockChangesUpTo > -1) {
+ this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo));
+ this.ackBlockChangesUpTo = -1;
+@@ -390,7 +389,6 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854
+ this.disconnect(Component.translatable("multiplayer.disconnect.idling"));
+ }
+- org.bukkit.craftbukkit.SpigotTimings.playerConnectionTimer.stopTiming(); // Spigot
+
+ }
+
+@@ -2014,7 +2012,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ }
+
+ private void handleCommand(String s) {
+- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.startTiming(); // Spigot
++ co.aikar.timings.MinecraftTimings.playerCommandTimer.startTiming(); // Paper
+ if ( org.spigotmc.SpigotConfig.logCommands ) // Spigot
+ this.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + s);
+
+@@ -2024,7 +2022,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ this.cserver.getPluginManager().callEvent(event);
+
+ if (event.isCancelled()) {
+- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot
++ co.aikar.timings.MinecraftTimings.playerCommandTimer.stopTiming(); // Paper
+ return;
+ }
+
+@@ -2037,7 +2035,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ java.util.logging.Logger.getLogger(ServerGamePacketListenerImpl.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
+ return;
+ } finally {
+- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot
++ co.aikar.timings.MinecraftTimings.playerCommandTimer.stopTiming(); // Paper
+ }
+ }
+ // CraftBukkit end
+diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
+index 6e9f5a404511f3703298def67402b87eca2f28a0..f5a4191977e8675952fc689744c8a39e86f62a07 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -1,5 +1,6 @@
+ package net.minecraft.server.players;
+
++import co.aikar.timings.MinecraftTimings;
+ import com.google.common.collect.Lists;
+ import com.google.common.collect.Maps;
+ import com.google.common.collect.Sets;
+@@ -1039,10 +1040,11 @@ public abstract class PlayerList {
+ }
+
+ public void saveAll() {
++ MinecraftTimings.savePlayers.startTiming(); // Paper
+ for (int i = 0; i < this.players.size(); ++i) {
+ this.save((ServerPlayer) this.players.get(i));
+ }
+-
++ MinecraftTimings.savePlayers.stopTiming(); // Paper
+ }
+
+ public UserWhiteList getWhiteList() {
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index d0f1cd32aa71f275c9975d1cedc8895fb2e8a174..a1a744657f8802852c161258382c5891858ebfa6 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -135,7 +135,6 @@ import org.bukkit.craftbukkit.event.CraftPortalEvent;
+ import org.bukkit.entity.Hanging;
+ import org.bukkit.entity.LivingEntity;
+ import org.bukkit.entity.Vehicle;
+-import org.spigotmc.CustomTimingsHandler; // Spigot
+ import org.bukkit.event.entity.EntityCombustByEntityEvent;
+ import org.bukkit.event.hanging.HangingBreakByEntityEvent;
+ import org.bukkit.event.vehicle.VehicleBlockCollisionEvent;
+@@ -312,7 +311,6 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S
+ // Marks an entity, that it was removed by a plugin via Entity#remove
+ // Main use case currently is for SPIGOT-7487, preventing dropping of leash when leash is removed
+ public boolean pluginRemoved = false;
+- public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getEntityTimings(this); // Spigot
+ // Spigot start
+ public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this);
+ public final boolean defaultActivationState;
+@@ -809,7 +807,6 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S
+ }
+
+ public void move(MoverType movementType, Vec3 movement) {
+- org.bukkit.craftbukkit.SpigotTimings.entityMoveTimer.startTiming(); // Spigot
+ if (this.noPhysics) {
+ this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z);
+ } else {
+@@ -970,7 +967,6 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S
+ this.level().getProfiler().pop();
+ }
+ }
+- org.bukkit.craftbukkit.SpigotTimings.entityMoveTimer.stopTiming(); // Spigot
+ }
+
+ private boolean isStateClimbable(BlockState state) {
+diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java
+index c4bc491eed487c0a7e30538b0fb46fde91cd7b31..382b55167dede435b034866bd394455f0f6f2a00 100644
+--- a/src/main/java/net/minecraft/world/entity/EntityType.java
++++ b/src/main/java/net/minecraft/world/entity/EntityType.java
+@@ -327,6 +327,15 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
+ }
+
+ public EntityType(EntityType.EntityFactory<T> factory, MobCategory spawnGroup, boolean saveable, boolean summonable, boolean fireImmune, boolean spawnableFarFromPlayer, ImmutableSet<Block> canSpawnInside, EntityDimensions dimensions, int maxTrackDistance, int trackTickInterval, FeatureFlagSet requiredFeatures) {
++ // Paper start
++ this(factory, spawnGroup, saveable, summonable, fireImmune, spawnableFarFromPlayer, canSpawnInside, dimensions, maxTrackDistance, trackTickInterval, requiredFeatures, "custom");
++ }
++ public EntityType(EntityType.EntityFactory<T> factory, MobCategory spawnGroup, boolean saveable, boolean summonable, boolean fireImmune, boolean spawnableFarFromPlayer, ImmutableSet<Block> canSpawnInside, EntityDimensions dimensions, int maxTrackDistance, int trackTickInterval, FeatureFlagSet requiredFeatures, String id) {
++ this.tickTimer = co.aikar.timings.MinecraftTimings.getEntityTimings(id, "tick");
++ this.inactiveTickTimer = co.aikar.timings.MinecraftTimings.getEntityTimings(id, "inactiveTick");
++ this.passengerTickTimer = co.aikar.timings.MinecraftTimings.getEntityTimings(id, "passengerTick");
++ this.passengerInactiveTickTimer = co.aikar.timings.MinecraftTimings.getEntityTimings(id, "passengerInactiveTick");
++ // Paper end
+ this.builtInRegistryHolder = BuiltInRegistries.ENTITY_TYPE.createIntrusiveHolder(this);
+ this.factory = factory;
+ this.category = spawnGroup;
+@@ -648,6 +657,12 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
+ return this.updateInterval;
+ }
+
++ // Paper start - timings
++ public final co.aikar.timings.Timing tickTimer;
++ public final co.aikar.timings.Timing inactiveTickTimer;
++ public final co.aikar.timings.Timing passengerTickTimer;
++ public final co.aikar.timings.Timing passengerInactiveTickTimer;
++ // Paper end
+ public boolean trackDeltas() {
+ return this != EntityType.PLAYER && this != EntityType.LLAMA_SPIT && this != EntityType.WITHER && this != EntityType.BAT && this != EntityType.ITEM_FRAME && this != EntityType.GLOW_ITEM_FRAME && this != EntityType.LEASH_KNOT && this != EntityType.PAINTING && this != EntityType.END_CRYSTAL && this != EntityType.EVOKER_FANGS;
+ }
+@@ -757,7 +772,7 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
+ Util.fetchChoiceType(References.ENTITY_TREE, id);
+ }
+
+- return new EntityType<>(this.factory, this.category, this.serialize, this.summon, this.fireImmune, this.canSpawnFarFromPlayer, this.immuneTo, this.dimensions, this.clientTrackingRange, this.updateInterval, this.requiredFeatures);
++ return new EntityType<>(this.factory, this.category, this.serialize, this.summon, this.fireImmune, this.canSpawnFarFromPlayer, this.immuneTo, this.dimensions, this.clientTrackingRange, this.updateInterval, this.requiredFeatures, id); // Paper - add id
+ }
+ }
+
+diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+index c2c3fa92235c365f4e4a8bdffa3295e32ebd8d16..4afd257f30ab063c4805e8a3144f51d643c49db3 100644
+--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+@@ -140,7 +140,7 @@ import org.bukkit.event.entity.EntityTeleportEvent;
+ import org.bukkit.event.player.PlayerItemConsumeEvent;
+ // CraftBukkit end
+
+-import org.bukkit.craftbukkit.SpigotTimings; // Spigot
++import co.aikar.timings.MinecraftTimings; // Paper
+
+ public abstract class LivingEntity extends Entity implements Attackable {
+
+@@ -2866,7 +2866,6 @@ public abstract class LivingEntity extends Entity implements Attackable {
+
+ @Override
+ public void tick() {
+- SpigotTimings.timerEntityBaseTick.startTiming(); // Spigot
+ super.tick();
+ this.updatingUsingItem();
+ this.updateSwimAmount();
+@@ -2908,9 +2907,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
+ }
+
+ if (!this.isRemoved()) {
+- SpigotTimings.timerEntityBaseTick.stopTiming(); // Spigot
+ this.aiStep();
+- SpigotTimings.timerEntityTickRest.startTiming(); // Spigot
+ }
+
+ double d0 = this.getX() - this.xo;
+@@ -2994,7 +2991,6 @@ public abstract class LivingEntity extends Entity implements Attackable {
+ }
+
+ this.refreshDirtyAttributes();
+- SpigotTimings.timerEntityTickRest.stopTiming(); // Spigot
+ }
+
+ public void detectEquipmentUpdatesPublic() { // CraftBukkit
+@@ -3169,7 +3165,6 @@ public abstract class LivingEntity extends Entity implements Attackable {
+
+ this.setDeltaMovement(d0, d1, d2);
+ this.level().getProfiler().push("ai");
+- SpigotTimings.timerEntityAI.startTiming(); // Spigot
+ if (this.isImmobile()) {
+ this.jumping = false;
+ this.xxa = 0.0F;
+@@ -3179,7 +3174,6 @@ public abstract class LivingEntity extends Entity implements Attackable {
+ this.serverAiStep();
+ this.level().getProfiler().pop();
+ }
+- SpigotTimings.timerEntityAI.stopTiming(); // Spigot
+
+ this.level().getProfiler().pop();
+ this.level().getProfiler().push("jump");
+@@ -3219,7 +3213,6 @@ public abstract class LivingEntity extends Entity implements Attackable {
+ this.resetFallDistance();
+ }
+
+- SpigotTimings.timerEntityAIMove.startTiming(); // Spigot
+ label104:
+ {
+ LivingEntity entityliving = this.getControllingPassenger();
+@@ -3235,7 +3228,6 @@ public abstract class LivingEntity extends Entity implements Attackable {
+
+ this.travel(vec3d1);
+ }
+- SpigotTimings.timerEntityAIMove.stopTiming(); // Spigot
+
+ this.level().getProfiler().pop();
+ this.level().getProfiler().push("freezing");
+@@ -3262,9 +3254,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
+ this.checkAutoSpinAttack(axisalignedbb, this.getBoundingBox());
+ }
+
+- SpigotTimings.timerEntityAICollision.startTiming(); // Spigot
+ this.pushEntities();
+- SpigotTimings.timerEntityAICollision.stopTiming(); // Spigot
+ this.level().getProfiler().pop();
+ if (!this.level().isClientSide && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) {
+ this.hurt(this.damageSources().drown(), 1.0F);
+diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
+index 915c1dfae28ccac02d5ebb97d5cf797c66d2c8e4..26fe1603b3899f5b69980dc64d46d26d9c944fde 100644
+--- a/src/main/java/net/minecraft/world/level/Level.java
++++ b/src/main/java/net/minecraft/world/level/Level.java
+@@ -91,7 +91,6 @@ import org.bukkit.Bukkit;
+ import org.bukkit.Location;
+ import org.bukkit.craftbukkit.CraftServer;
+ import org.bukkit.craftbukkit.CraftWorld;
+-import org.bukkit.craftbukkit.SpigotTimings; // Spigot
+ import org.bukkit.craftbukkit.block.CapturedBlockState;
+ import org.bukkit.craftbukkit.block.CraftBlockState;
+ import org.bukkit.craftbukkit.block.data.CraftBlockData;
+@@ -166,7 +165,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+ }
+ // Paper end
+
+- public final SpigotTimings.WorldTimingsHandler timings; // Spigot
++ public final co.aikar.timings.WorldTimingsHandler timings; // Paper
+ public static BlockPos lastPhysicsProblem; // Spigot
+ private org.spigotmc.TickLimiter entityLimiter;
+ private org.spigotmc.TickLimiter tileLimiter;
+@@ -264,7 +263,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+ public void onBorderSetDamageSafeZOne(WorldBorder border, double safeZoneRadius) {}
+ });
+ // CraftBukkit end
+- this.timings = new SpigotTimings.WorldTimingsHandler(this); // Spigot - code below can generate new world and access timings
++ this.timings = new co.aikar.timings.WorldTimingsHandler(this); // Paper - code below can generate new world and access timings
+ this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime);
+ this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime);
+ }
+@@ -725,15 +724,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+
+ this.timings.tileEntityTick.stopTiming(); // Spigot
+ this.tickingBlockEntities = false;
++ co.aikar.timings.TimingHistory.tileEntityTicks += this.blockEntityTickers.size(); // Paper
+ gameprofilerfiller.pop();
+ this.spigotConfig.currentPrimedTnt = 0; // Spigot
+ }
+
+ public <T extends Entity> void guardEntityTick(Consumer<T> tickConsumer, T entity) {
+ try {
+- SpigotTimings.tickEntityTimer.startTiming(); // Spigot
+ tickConsumer.accept(entity);
+- SpigotTimings.tickEntityTimer.stopTiming(); // Spigot
+ } catch (Throwable throwable) {
+ CrashReport crashreport = CrashReport.forThrowable(throwable, "Ticking entity");
+ CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being ticked");
+diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java
+index 74f380afac8a91cd1d2bbd3cd679f0eaee53fdee..756a8ae14ffc46d6ebe0a858a03fb2e89b8e118a 100644
+--- a/src/main/java/net/minecraft/world/level/block/Block.java
++++ b/src/main/java/net/minecraft/world/level/block/Block.java
+@@ -89,6 +89,15 @@ public class Block extends BlockBehaviour implements ItemLike {
+ public static final int UPDATE_LIMIT = 512;
+ protected final StateDefinition<Block, BlockState> stateDefinition;
+ private BlockState defaultBlockState;
++ // Paper start
++ public co.aikar.timings.Timing timing;
++ public co.aikar.timings.Timing getTiming() {
++ if (timing == null) {
++ timing = co.aikar.timings.MinecraftTimings.getBlockTiming(this);
++ }
++ return timing;
++ }
++ // Paper end
+ @Nullable
+ private String descriptionId;
+ @Nullable
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
+index c02fa35cefc9194d1838abbe4f2dc2b226a41e41..b300d12e9e00519028b53aca9c3fb01f589eaa91 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
+@@ -22,10 +22,12 @@ import org.bukkit.inventory.InventoryHolder;
+ // CraftBukkit end
+
+ import org.spigotmc.CustomTimingsHandler; // Spigot
++import co.aikar.timings.MinecraftTimings; // Paper
++import co.aikar.timings.Timing; // Paper
+
+ public abstract class BlockEntity {
+
+- public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getTileEntityTimings(this); // Spigot
++ public Timing tickTimer = MinecraftTimings.getTileEntityTimings(this); // Paper
+ // CraftBukkit start - data containers
+ private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry();
+ public CraftPersistentDataContainer persistentDataContainer;
+diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+index 17d36b53ec8efbc60b0648764f7195003e40fdcc..93348550f1632f7fc567eb5b42cd03d78532e383 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+@@ -682,6 +682,7 @@ public class LevelChunk extends ChunkAccess {
+ server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration));
+
+ if (this.needsDecoration) {
++ try (co.aikar.timings.Timing ignored = this.level.timings.chunkLoadPopulate.startTiming()) { // Paper
+ this.needsDecoration = false;
+ java.util.Random random = new java.util.Random();
+ random.setSeed(this.level.getSeed());
+@@ -701,6 +702,7 @@ public class LevelChunk extends ChunkAccess {
+ }
+ }
+ server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk));
++ } // Paper
+ }
+ }
+ }
+diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+index 0eb09ce5c850d85ffd7229d27cf06b3e0edda11b..cc1d7626a82881c4410d65c6a33dadae7ab07172 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+@@ -483,13 +483,10 @@ public class ChunkSerializer {
+ ListTag nbttaglist1 = ChunkSerializer.getListOfCompoundsOrNull(nbt, "block_entities");
+
+ return nbttaglist == null && nbttaglist1 == null ? null : (chunk) -> {
+- world.timings.syncChunkLoadEntitiesTimer.startTiming(); // Spigot
+ if (nbttaglist != null) {
+ world.addLegacyChunkEntities(EntityType.loadEntitiesRecursive(nbttaglist, world));
+ }
+- world.timings.syncChunkLoadEntitiesTimer.stopTiming(); // Spigot
+
+- world.timings.syncChunkLoadTileEntitiesTimer.startTiming(); // Spigot
+ if (nbttaglist1 != null) {
+ for (int i = 0; i < nbttaglist1.size(); ++i) {
+ CompoundTag nbttagcompound1 = nbttaglist1.getCompound(i);
+@@ -507,7 +504,6 @@ public class ChunkSerializer {
+ }
+ }
+ }
+- world.timings.syncChunkLoadTileEntitiesTimer.stopTiming(); // Spigot
+
+ };
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index 6a8b3ad8848119e3c448ff976c60bfc6eda22e59..0f3659d2d60426275869dec76412aecb8e407442 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -372,7 +372,7 @@ public final class CraftServer implements Server {
+ this.saveCommandsConfig();
+ this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*");
+ this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions");
+- this.pluginManager.useTimings(this.configuration.getBoolean("settings.plugin-profiling"));
++ //this.pluginManager.useTimings(this.configuration.getBoolean("settings.plugin-profiling")); // Paper - we already moved this
+ this.overrideSpawnLimits();
+ console.autosavePeriod = this.configuration.getInt("ticks-per.autosave");
+ this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose"));
+@@ -2547,12 +2547,31 @@ public final class CraftServer implements Server {
+ private final org.bukkit.Server.Spigot spigot = new org.bukkit.Server.Spigot()
+ {
+
++ @Deprecated
+ @Override
+ public YamlConfiguration getConfig()
+ {
+ return org.spigotmc.SpigotConfig.config;
+ }
+
++ @Override
++ public YamlConfiguration getBukkitConfig()
++ {
++ return configuration;
++ }
++
++ @Override
++ public YamlConfiguration getSpigotConfig()
++ {
++ return org.spigotmc.SpigotConfig.config;
++ }
++
++ @Override
++ public YamlConfiguration getPaperConfig()
++ {
++ return CraftServer.this.console.paperConfigurations.createLegacyObject(CraftServer.this.console);
++ }
++
+ @Override
+ public void restart() {
+ org.spigotmc.RestartCommand.restart();
+diff --git a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java b/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java
+deleted file mode 100644
+index b0ffa23faf62629043dfd613315eaf9c5fcc2cfe..0000000000000000000000000000000000000000
+--- a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java
++++ /dev/null
+@@ -1,163 +0,0 @@
+-package org.bukkit.craftbukkit;
+-
+-import java.util.HashMap;
+-import net.minecraft.world.entity.Entity;
+-import net.minecraft.world.level.Level;
+-import net.minecraft.world.level.block.entity.BlockEntity;
+-import net.minecraft.world.level.storage.PrimaryLevelData;
+-import org.bukkit.craftbukkit.scheduler.CraftTask;
+-import org.bukkit.plugin.java.JavaPluginLoader;
+-import org.bukkit.scheduler.BukkitTask;
+-import org.spigotmc.CustomTimingsHandler;
+-
+-public class SpigotTimings {
+-
+- public static final CustomTimingsHandler serverTickTimer = new CustomTimingsHandler("** Full Server Tick");
+- public static final CustomTimingsHandler playerListTimer = new CustomTimingsHandler("Player List");
+- public static final CustomTimingsHandler commandFunctionsTimer = new CustomTimingsHandler("Command Functions");
+- public static final CustomTimingsHandler connectionTimer = new CustomTimingsHandler("Connection Handler");
+- public static final CustomTimingsHandler playerConnectionTimer = new CustomTimingsHandler("** PlayerConnection");
+- public static final CustomTimingsHandler tickablesTimer = new CustomTimingsHandler("Tickables");
+- public static final CustomTimingsHandler schedulerTimer = new CustomTimingsHandler("Scheduler");
+- public static final CustomTimingsHandler timeUpdateTimer = new CustomTimingsHandler("Time Update");
+- public static final CustomTimingsHandler serverCommandTimer = new CustomTimingsHandler("Server Command");
+- public static final CustomTimingsHandler worldSaveTimer = new CustomTimingsHandler("World Save");
+-
+- public static final CustomTimingsHandler entityMoveTimer = new CustomTimingsHandler("** entityMove");
+- public static final CustomTimingsHandler tickEntityTimer = new CustomTimingsHandler("** tickEntity");
+- public static final CustomTimingsHandler activatedEntityTimer = new CustomTimingsHandler("** activatedTickEntity");
+- public static final CustomTimingsHandler tickTileEntityTimer = new CustomTimingsHandler("** tickTileEntity");
+-
+- public static final CustomTimingsHandler timerEntityBaseTick = new CustomTimingsHandler("** livingEntityBaseTick");
+- public static final CustomTimingsHandler timerEntityAI = new CustomTimingsHandler("** livingEntityAI");
+- public static final CustomTimingsHandler timerEntityAICollision = new CustomTimingsHandler("** livingEntityAICollision");
+- public static final CustomTimingsHandler timerEntityAIMove = new CustomTimingsHandler("** livingEntityAIMove");
+- public static final CustomTimingsHandler timerEntityTickRest = new CustomTimingsHandler("** livingEntityTickRest");
+-
+- public static final CustomTimingsHandler processQueueTimer = new CustomTimingsHandler("processQueue");
+- public static final CustomTimingsHandler schedulerSyncTimer = new CustomTimingsHandler("** Scheduler - Sync Tasks", JavaPluginLoader.pluginParentTimer);
+-
+- public static final CustomTimingsHandler playerCommandTimer = new CustomTimingsHandler("** playerCommand");
+-
+- public static final CustomTimingsHandler entityActivationCheckTimer = new CustomTimingsHandler("entityActivationCheck");
+- public static final CustomTimingsHandler checkIfActiveTimer = new CustomTimingsHandler("** checkIfActive");
+-
+- public static final HashMap<String, CustomTimingsHandler> entityTypeTimingMap = new HashMap<String, CustomTimingsHandler>();
+- public static final HashMap<String, CustomTimingsHandler> tileEntityTypeTimingMap = new HashMap<String, CustomTimingsHandler>();
+- public static final HashMap<String, CustomTimingsHandler> pluginTaskTimingMap = new HashMap<String, CustomTimingsHandler>();
+-
+- /**
+- * Gets a timer associated with a plugins tasks.
+- * @param task
+- * @param period
+- * @return
+- */
+- public static CustomTimingsHandler getPluginTaskTimings(BukkitTask task, long period) {
+- if (!task.isSync()) {
+- return null;
+- }
+- String plugin;
+- final CraftTask ctask = (CraftTask) task;
+-
+- if (task.getOwner() != null) {
+- plugin = task.getOwner().getDescription().getFullName();
+- } else {
+- plugin = "Unknown";
+- }
+- String taskname = ctask.getTaskName();
+-
+- String name = "Task: " + plugin + " Runnable: " + taskname;
+- if (period > 0) {
+- name += "(interval:" + period + ")";
+- } else {
+- name += "(Single)";
+- }
+- CustomTimingsHandler result = SpigotTimings.pluginTaskTimingMap.get(name);
+- if (result == null) {
+- result = new CustomTimingsHandler(name, SpigotTimings.schedulerSyncTimer);
+- SpigotTimings.pluginTaskTimingMap.put(name, result);
+- }
+- return result;
+- }
+-
+- /**
+- * Get a named timer for the specified entity type to track type specific timings.
+- * @param entity
+- * @return
+- */
+- public static CustomTimingsHandler getEntityTimings(Entity entity) {
+- String entityType = entity.getClass().getName();
+- CustomTimingsHandler result = SpigotTimings.entityTypeTimingMap.get(entityType);
+- if (result == null) {
+- result = new CustomTimingsHandler("** tickEntity - " + entity.getClass().getSimpleName(), SpigotTimings.activatedEntityTimer);
+- SpigotTimings.entityTypeTimingMap.put(entityType, result);
+- }
+- return result;
+- }
+-
+- /**
+- * Get a named timer for the specified tile entity type to track type specific timings.
+- * @param entity
+- * @return
+- */
+- public static CustomTimingsHandler getTileEntityTimings(BlockEntity entity) {
+- String entityType = entity.getClass().getName();
+- CustomTimingsHandler result = SpigotTimings.tileEntityTypeTimingMap.get(entityType);
+- if (result == null) {
+- result = new CustomTimingsHandler("** tickTileEntity - " + entity.getClass().getSimpleName(), SpigotTimings.tickTileEntityTimer);
+- SpigotTimings.tileEntityTypeTimingMap.put(entityType, result);
+- }
+- return result;
+- }
+-
+- /**
+- * Set of timers per world, to track world specific timings.
+- */
+- public static class WorldTimingsHandler {
+- public final CustomTimingsHandler mobSpawn;
+- public final CustomTimingsHandler doChunkUnload;
+- public final CustomTimingsHandler doTickPending;
+- public final CustomTimingsHandler doTickTiles;
+- public final CustomTimingsHandler doChunkMap;
+- public final CustomTimingsHandler doSounds;
+- public final CustomTimingsHandler entityTick;
+- public final CustomTimingsHandler tileEntityTick;
+- public final CustomTimingsHandler tileEntityPending;
+- public final CustomTimingsHandler tracker;
+- public final CustomTimingsHandler doTick;
+- public final CustomTimingsHandler tickEntities;
+-
+- public final CustomTimingsHandler syncChunkLoadTimer;
+- public final CustomTimingsHandler syncChunkLoadStructuresTimer;
+- public final CustomTimingsHandler syncChunkLoadEntitiesTimer;
+- public final CustomTimingsHandler syncChunkLoadTileEntitiesTimer;
+- public final CustomTimingsHandler syncChunkLoadTileTicksTimer;
+- public final CustomTimingsHandler syncChunkLoadPostTimer;
+-
+- public WorldTimingsHandler(Level server) {
+- String name = ((PrimaryLevelData) server.levelData).getLevelName() + " - ";
+-
+- this.mobSpawn = new CustomTimingsHandler("** " + name + "mobSpawn");
+- this.doChunkUnload = new CustomTimingsHandler("** " + name + "doChunkUnload");
+- this.doTickPending = new CustomTimingsHandler("** " + name + "doTickPending");
+- this.doTickTiles = new CustomTimingsHandler("** " + name + "doTickTiles");
+- this.doChunkMap = new CustomTimingsHandler("** " + name + "doChunkMap");
+- this.doSounds = new CustomTimingsHandler("** " + name + "doSounds");
+- this.entityTick = new CustomTimingsHandler("** " + name + "entityTick");
+- this.tileEntityTick = new CustomTimingsHandler("** " + name + "tileEntityTick");
+- this.tileEntityPending = new CustomTimingsHandler("** " + name + "tileEntityPending");
+-
+- this.syncChunkLoadTimer = new CustomTimingsHandler("** " + name + "syncChunkLoad");
+- this.syncChunkLoadStructuresTimer = new CustomTimingsHandler("** " + name + "chunkLoad - Structures");
+- this.syncChunkLoadEntitiesTimer = new CustomTimingsHandler("** " + name + "chunkLoad - Entities");
+- this.syncChunkLoadTileEntitiesTimer = new CustomTimingsHandler("** " + name + "chunkLoad - TileEntities");
+- this.syncChunkLoadTileTicksTimer = new CustomTimingsHandler("** " + name + "chunkLoad - TileTicks");
+- this.syncChunkLoadPostTimer = new CustomTimingsHandler("** " + name + "chunkLoad - Post");
+-
+-
+- this.tracker = new CustomTimingsHandler(name + "tracker");
+- this.doTick = new CustomTimingsHandler(name + "doTick");
+- this.tickEntities = new CustomTimingsHandler(name + "tickEntities");
+- }
+- }
+-}
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+index 336045ecb9e6b3803fdf9531b07bb72ceff0ee73..c74428dccd9db8c4d2809bbd23f3268a16d7e282 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+@@ -2585,6 +2585,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
+
+ CraftPlayer.this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundSystemChatPacket(components, position == net.md_5.bungee.api.ChatMessageType.ACTION_BAR));
+ }
++
++ // Paper start
++ @Override
++ public int getPing()
++ {
++ return CraftPlayer.this.getPing();
++ }
++ // Paper end
+ };
+
+ public Player.Spigot spigot()
+diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
+index 2b80ddb42c8e5fd32b37f89e894353167c8a698e..bd1057681d0c7470c497b873ff18abf03a0a6a66 100644
+--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
+@@ -1,5 +1,6 @@
+ package org.bukkit.craftbukkit.scheduler;
+
++import co.aikar.timings.MinecraftTimings; // Paper
+ import com.google.common.base.Preconditions;
+ import com.google.common.util.concurrent.ThreadFactoryBuilder;
+ import java.util.ArrayList;
+@@ -194,7 +195,8 @@ public class CraftScheduler implements BukkitScheduler {
+ }
+
+ public BukkitTask scheduleInternalTask(Runnable run, int delay, String taskName) {
+- final CraftTask task = new CraftTask(run, nextId(), taskName);
++ final CraftTask task = new CraftTask(run, nextId(), "Internal - " + (taskName != null ? taskName : "Unknown"));
++ task.internal = true;
+ return handle(task, delay);
+ }
+
+@@ -275,7 +277,7 @@ public class CraftScheduler implements BukkitScheduler {
+ }
+ return false;
+ }
+- });
++ }){{this.timings=co.aikar.timings.MinecraftTimings.getCancelTasksTimer();}}; // Paper
+ this.handle(task, 0L);
+ for (CraftTask taskPending = this.head.getNext(); taskPending != null; taskPending = taskPending.getNext()) {
+ if (taskPending == task) {
+@@ -310,7 +312,7 @@ public class CraftScheduler implements BukkitScheduler {
+ }
+ }
+ }
+- });
++ }){{this.timings=co.aikar.timings.MinecraftTimings.getCancelTasksTimer(plugin);}}; // Paper
+ this.handle(task, 0L);
+ for (CraftTask taskPending = this.head.getNext(); taskPending != null; taskPending = taskPending.getNext()) {
+ if (taskPending == task) {
+@@ -417,9 +419,7 @@ public class CraftScheduler implements BukkitScheduler {
+ if (task.isSync()) {
+ this.currentTask = task;
+ try {
+- task.timings.startTiming(); // Spigot
+ task.run();
+- task.timings.stopTiming(); // Spigot
+ } catch (final Throwable throwable) {
+ // Paper start
+ String msg = String.format(
+@@ -453,8 +453,10 @@ public class CraftScheduler implements BukkitScheduler {
+ this.runners.remove(task.getTaskId());
+ }
+ }
++ MinecraftTimings.bukkitSchedulerFinishTimer.startTiming(); // Paper
+ this.pending.addAll(temp);
+ temp.clear();
++ MinecraftTimings.bukkitSchedulerFinishTimer.stopTiming(); // Paper
+ this.debugHead = this.debugHead.getNextHead(currentTick);
+ }
+
+@@ -491,6 +493,7 @@ public class CraftScheduler implements BukkitScheduler {
+ }
+
+ private void parsePending() {
++ MinecraftTimings.bukkitSchedulerPendingTimer.startTiming();
+ CraftTask head = this.head;
+ CraftTask task = head.getNext();
+ CraftTask lastTask = head;
+@@ -509,6 +512,7 @@ public class CraftScheduler implements BukkitScheduler {
+ task.setNext(null);
+ }
+ this.head = lastTask;
++ MinecraftTimings.bukkitSchedulerPendingTimer.stopTiming();
+ }
+
+ private boolean isReady(final int currentTick) {
+diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java
+index d56abf283f38548faa790c57045033f7ade6f958..ea26d9464644b5217879b8c21b4da28e57708dcb 100644
+--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java
++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java
+@@ -1,12 +1,15 @@
+ package org.bukkit.craftbukkit.scheduler;
+
+ import java.util.function.Consumer;
++
++import co.aikar.timings.NullTimingHandler;
+ import org.bukkit.Bukkit;
+ import org.bukkit.plugin.Plugin;
+ import org.bukkit.scheduler.BukkitTask;
+
+-import org.bukkit.craftbukkit.SpigotTimings; // Spigot
+ import org.spigotmc.CustomTimingsHandler; // Spigot
++import co.aikar.timings.MinecraftTimings; // Paper
++import co.aikar.timings.Timing; // Paper
+
+ public class CraftTask implements BukkitTask, Runnable { // Spigot
+
+@@ -26,13 +29,13 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot
+ */
+ private volatile long period;
+ private long nextRun;
+- private final Runnable rTask;
+- private final Consumer<BukkitTask> cTask;
++ public final Runnable rTask; // Paper
++ public final Consumer<BukkitTask> cTask; // Paper
++ public Timing timings; // Paper
+ private final Plugin plugin;
+ private final int id;
+ private final long createdAt = System.nanoTime();
+
+- final CustomTimingsHandler timings; // Spigot
+ CraftTask() {
+ this(null, null, CraftTask.NO_REPEATING, CraftTask.NO_REPEATING);
+ }
+@@ -52,7 +55,7 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot
+ this.id = id;
+ this.period = CraftTask.NO_REPEATING;
+ this.taskName = taskName;
+- this.timings = null; // Will be changed in later patch
++ this.timings = MinecraftTimings.getInternalTaskName(taskName);
+ }
+ // Paper end
+
+@@ -73,7 +76,7 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot
+ }
+ this.id = id;
+ this.period = period;
+- this.timings = this.isSync() ? SpigotTimings.getPluginTaskTimings(this, period) : null; // Spigot
++ timings = task != null ? MinecraftTimings.getPluginTaskTimings(this, period) : NullTimingHandler.NULL; // Paper
+ }
+
+ @Override
+@@ -93,11 +96,13 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot
+
+ @Override
+ public void run() {
++ try (Timing ignored = timings.startTiming()) { // Paper
+ if (this.rTask != null) {
+ this.rTask.run();
+ } else {
+ this.cTask.accept(this);
+ }
++ } // Paper
+ }
+
+ long getCreatedAt() {
+@@ -128,7 +133,7 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot
+ this.next = next;
+ }
+
+- Class<?> getTaskClass() {
++ public Class<?> getTaskClass() { // Paper
+ return (this.rTask != null) ? this.rTask.getClass() : ((this.cTask != null) ? this.cTask.getClass() : null);
+ }
+
+@@ -152,9 +157,4 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot
+ return true;
+ }
+
+- // Spigot start
+- public String getTaskName() {
+- return (this.getTaskClass() == null) ? "Unknown" : this.getTaskClass().getName();
+- }
+- // Spigot end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java b/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java
+index f97eccb6a17c7876e1e002d798eb67bbe80571a0..76effc345d362047e64d064eb64a5222612aec14 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java
+@@ -8,4 +8,11 @@ public class CraftIconCache implements CachedServerIcon {
+ public CraftIconCache(final byte[] value) {
+ this.value = value;
+ }
++
++ public String getData() {
++ if (value == null) {
++ return null;
++ }
++ return "data:image/png;base64," + new String(java.util.Base64.getEncoder().encode(value), java.nio.charset.StandardCharsets.UTF_8);
++ } // Paper
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+index 72c1b7f1468c47ad7053a7ff6b3248324b2bf604..677335c3888adc25fbf3c5ec4d5a6c5ecf58ea5d 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+@@ -209,6 +209,12 @@ public final class CraftMagicNumbers implements UnsafeValues {
+ }
+ // Paper end
+ // ========================================================================
++ // Paper start
++ @Override
++ public void reportTimings() {
++ co.aikar.timings.TimingsExport.reportTimings();
++ }
++ // Paper end
+
+ public static byte toLegacyData(BlockState data) {
+ return CraftLegacy.toLegacyData(data);
+@@ -442,6 +448,13 @@ public final class CraftMagicNumbers implements UnsafeValues {
+ return new CraftPotionType(namespacedKey, potionRegistry);
+ }
+
++ // Paper start
++ @Override
++ public String getTimingsServerName() {
++ return io.papermc.paper.configuration.GlobalConfiguration.get().timings.serverName;
++ }
++ // Paper end
++
+ /**
+ * This helper class represents the different NBT Tags.
+ * <p>
+diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
+index 0c7c97f27853843ec714e47f5b570f9d09bbba14..e01ef22189ce80429eb0054e416547577fd3cc5d 100644
+--- a/src/main/java/org/spigotmc/ActivationRange.java
++++ b/src/main/java/org/spigotmc/ActivationRange.java
+@@ -27,7 +27,7 @@ import net.minecraft.world.entity.projectile.ThrownTrident;
+ import net.minecraft.world.entity.raid.Raider;
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.phys.AABB;
+-import org.bukkit.craftbukkit.SpigotTimings;
++import co.aikar.timings.MinecraftTimings;
+
+ public class ActivationRange
+ {
+@@ -71,8 +71,8 @@ public class ActivationRange
+ /**
+ * These entities are excluded from Activation range checks.
+ *
+- * @param entity
+- * @param config
++ * @param entity Entity to initialize
++ * @param config Spigot config to determine ranges
+ * @return boolean If it should always tick.
+ */
+ public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config)
+@@ -107,7 +107,7 @@ public class ActivationRange
+ */
+ public static void activateEntities(Level world)
+ {
+- SpigotTimings.entityActivationCheckTimer.startTiming();
++ MinecraftTimings.entityActivationCheckTimer.startTiming();
+ final int miscActivationRange = world.spigotConfig.miscActivationRange;
+ final int raiderActivationRange = world.spigotConfig.raiderActivationRange;
+ final int animalActivationRange = world.spigotConfig.animalActivationRange;
+@@ -134,7 +134,7 @@ public class ActivationRange
+
+ world.getEntities().get(ActivationRange.maxBB, ActivationRange::activateEntity);
+ }
+- SpigotTimings.entityActivationCheckTimer.stopTiming();
++ MinecraftTimings.entityActivationCheckTimer.stopTiming();
+ }
+
+ /**
+@@ -229,10 +229,8 @@ public class ActivationRange
+ */
+ public static boolean checkIfActive(Entity entity)
+ {
+- SpigotTimings.checkIfActiveTimer.startTiming();
+ // Never safe to skip fireworks or entities not yet added to chunk
+ if ( entity instanceof FireworkRocketEntity ) {
+- SpigotTimings.checkIfActiveTimer.stopTiming();
+ return true;
+ }
+
+@@ -256,7 +254,6 @@ public class ActivationRange
+ {
+ isActive = false;
+ }
+- SpigotTimings.checkIfActiveTimer.stopTiming();
+ return isActive;
+ }
+ }