diff --git a/sources/src/main/java/co/aikar/timings/TimingHandler.java b/sources/src/main/java/co/aikar/timings/TimingHandler.java new file mode 100644 index 000000000..f49471e1f --- /dev/null +++ b/sources/src/main/java/co/aikar/timings/TimingHandler.java @@ -0,0 +1,227 @@ +/* + * This file is licensed under the MIT License (MIT). + * + * Copyright (c) 2014 Daniel Ennis + * + * 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 co.aikar.util.LoadingIntMap; +import io.akarin.api.Akari; +import io.akarin.server.core.AkarinGlobalConfig; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import org.bukkit.Bukkit; + +import java.util.logging.Level; + +class TimingHandler implements Timing { + + private static int idPool = 1; + final int id = idPool++; + + final String name; + private final boolean verbose; + + private final Int2ObjectOpenHashMap children = new LoadingIntMap<>(TimingData::new); + + final TimingData record; + private final TimingHandler groupHandler; + + private volatile long start = 0; // Akarin - volatile + private volatile int timingDepth = 0; // Akarin - volatile + private boolean added; + private boolean timed; + private boolean enabled; + private TimingHandler parent; + + TimingHandler(TimingIdentifier id) { + if (id.name.startsWith("##")) { + verbose = true; + this.name = id.name.substring(3); + } else { + this.name = id.name; + verbose = false; + } + + this.record = new TimingData(this.id); + this.groupHandler = id.groupHandler; + + TimingIdentifier.getGroup(id.group).handlers.add(this); + checkEnabled(); + } + + final void checkEnabled() { + enabled = Timings.timingsEnabled && (!verbose || Timings.verboseEnabled); + } + + void processTick(boolean violated) { + if (timingDepth != 0 || record.getCurTickCount() == 0) { + timingDepth = 0; + start = 0; + return; + } + + record.processTick(violated); + for (TimingData handler : children.values()) { + handler.processTick(violated); + } + } + + @Override + public Timing startTimingIfSync() { + if (Bukkit.isPrimaryThread()) { + startTiming(); + } + return this; + } + + @Override + public void stopTimingIfSync() { + if (Bukkit.isPrimaryThread()) { + stopTiming(true); // Akarin - avoid twice thread check + } + } + + @Override + public Timing startTiming() { + if (enabled && ++timingDepth == 1) { + start = System.nanoTime(); + parent = TimingsManager.CURRENT; + TimingsManager.CURRENT = this; + } + return this; + } + + @Override + public void stopTiming() { + // Akarin start - avoid twice thread check + stopTiming(false); + } + + public void stopTiming(boolean sync) { + if (enabled && --timingDepth == 0 && start != 0) { + // Akarin start - silent async timing + if (Akari.silentTiming) { // It must be off-main thread now + start = 0; + return; + } else { + if (!sync && !Bukkit.isPrimaryThread()) { + if (AkarinGlobalConfig.silentAsyncTimings) { + Bukkit.getLogger().log(Level.SEVERE, "stopTiming called async for " + name); + new Throwable().printStackTrace(); + } + start = 0; + return; + } + } + // Akarin end + addDiff(System.nanoTime() - start); + start = 0; + } + } + + @Override + public void abort() { + if (enabled && timingDepth > 0) { + start = 0; + } + } + + void addDiff(long diff) { + if (TimingsManager.CURRENT == this) { + TimingsManager.CURRENT = parent; + if (parent != null) { + parent.children.get(id).add(diff); + } + } + record.add(diff); + if (!added) { + added = true; + timed = true; + TimingsManager.HANDLERS.add(this); + } + if (groupHandler != null) { + groupHandler.addDiff(diff); + groupHandler.children.get(id).add(diff); + } + } + + /** + * Reset this timer, setting all values to zero. + * + * @param full + */ + void reset(boolean full) { + record.reset(); + if (full) { + timed = false; + } + start = 0; + timingDepth = 0; + added = false; + children.clear(); + checkEnabled(); + } + + @Override + public TimingHandler getTimingHandler() { + return this; + } + + @Override + public boolean equals(Object o) { + return (this == o); + } + + @Override + public int hashCode() { + return id; + } + + /** + * This is simply for the Closeable interface so it can be used with + * try-with-resources () + */ + @Override + public void close() { + stopTimingIfSync(); + } + + public boolean isSpecial() { + return this == TimingsManager.FULL_SERVER_TICK || this == TimingsManager.TIMINGS_TICK; + } + + boolean isTimed() { + return timed; + } + + public boolean isEnabled() { + return enabled; + } + + TimingData[] cloneChildren() { + final TimingData[] clonedChildren = new TimingData[children.size()]; + int i = 0; + for (TimingData child : children.values()) { + clonedChildren[i++] = child.clone(); + } + return clonedChildren; + } +} diff --git a/sources/src/main/java/io/akarin/api/Akari.java b/sources/src/main/java/io/akarin/api/Akari.java index 42bbcfa2a..319adea0e 100644 --- a/sources/src/main/java/io/akarin/api/Akari.java +++ b/sources/src/main/java/io/akarin/api/Akari.java @@ -61,18 +61,16 @@ public abstract class Akari { /* * Timings */ - private static Timing callbackTiming; + public static Timing worldTiming = getWorldTiming(); - public static Timing callbackTiming() { - if (callbackTiming == null) { - try { - Method ofSafe = Timings.class.getDeclaredMethod("ofSafe", String.class); - ofSafe.setAccessible(true); - callbackTiming = (Timing) ofSafe.invoke(null, "Akarin - Callback"); - } catch (Throwable t) { - t.printStackTrace(); - } + private static Timing getWorldTiming() { + try { + Method ofSafe = Timings.class.getDeclaredMethod("ofSafe", String.class); + ofSafe.setAccessible(true); + return worldTiming = (Timing) ofSafe.invoke(null, "Akarin - World"); + } catch (Throwable t) { + t.printStackTrace(); + return null; } - return callbackTiming; } } diff --git a/sources/src/main/java/io/akarin/server/core/AkarinGlobalConfig.java b/sources/src/main/java/io/akarin/server/core/AkarinGlobalConfig.java index 06e2dc4f8..5a36f6755 100644 --- a/sources/src/main/java/io/akarin/server/core/AkarinGlobalConfig.java +++ b/sources/src/main/java/io/akarin/server/core/AkarinGlobalConfig.java @@ -145,12 +145,12 @@ public class AkarinGlobalConfig { /*========================================================================*/ public static List extraAddress; private static void extraAddress() { - extraAddress = getList("network.extra-local-address", Lists.newArrayList()); + extraAddress = getList("bootstrap.extra-local-address", Lists.newArrayList()); } public static boolean legacyVersioningCompat; private static void legacyVersioningCompat() { - legacyVersioningCompat = getBoolean("bonus.legacy-versioning-compat", false); + legacyVersioningCompat = getBoolean("alternative.legacy-versioning-compat", false); } public static int registryTerminationSeconds; @@ -160,6 +160,16 @@ public class AkarinGlobalConfig { public static int playersPerIOThread; private static void playersPerIOThread() { - playersPerIOThread = getInt("chunk.players-per-chunk-io-thread", 50); + playersPerIOThread = getInt("core.players-per-chunk-io-thread", 50); + } + + public static boolean silentAsyncTimings; + private static void silentAsyncTimings() { + silentAsyncTimings = getBoolean("core.silent-async-timing", false); + } + + public static boolean legacyWorldTimings; + private static void legacyWorldTimings() { + legacyWorldTimings = getBoolean("alternative.legacy-world-timings-required", false); } } diff --git a/sources/src/main/java/io/akarin/server/mixin/core/MixinMinecraftServer.java b/sources/src/main/java/io/akarin/server/mixin/core/MixinMinecraftServer.java index 071135aaf..eaff0900b 100644 --- a/sources/src/main/java/io/akarin/server/mixin/core/MixinMinecraftServer.java +++ b/sources/src/main/java/io/akarin/server/mixin/core/MixinMinecraftServer.java @@ -1,5 +1,6 @@ package io.akarin.server.mixin.core; +import java.io.File; import java.util.List; import java.util.Queue; import java.util.concurrent.FutureTask; @@ -11,9 +12,13 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mutable; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import co.aikar.timings.MinecraftTimings; import io.akarin.api.Akari; +import io.akarin.server.core.AkarinGlobalConfig; import net.minecraft.server.CrashReport; import net.minecraft.server.CustomFunctionData; import net.minecraft.server.EntityPlayer; @@ -26,6 +31,7 @@ import net.minecraft.server.ReportedException; import net.minecraft.server.ServerConnection; import net.minecraft.server.SystemUtils; import net.minecraft.server.TileEntityHopper; +import net.minecraft.server.World; import net.minecraft.server.WorldServer; @Mixin(value = MinecraftServer.class, remap = false) @@ -44,6 +50,14 @@ public class MixinMinecraftServer { @Overwrite public void b(MojangStatisticsGenerator generator) {} + @Inject(method = "run()V", at = @At("HEAD")) + private void prerun(CallbackInfo info) { + for (int i = 0; i < worlds.size(); ++i) { + WorldServer world = worlds.get(i); + TileEntityHopper.skipHopperEvents = world.paperConfig.disableHopperMoveEvents || InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; + } + } + @Shadow public CraftServer server; @Shadow @Mutable protected Queue> j; @Shadow public Queue processQueue; @@ -71,6 +85,21 @@ public class MixinMinecraftServer { } } + private void tickWorld(WorldServer world) { + try { + world.doTick(); + } catch (Throwable throwable) { + CrashReport crashreport; + try { + crashreport = CrashReport.a(throwable, "Exception ticking world"); + } catch (Throwable t){ + throw new RuntimeException("Error generating crash report", t); + } + world.a(crashreport); + throw new ReportedException(crashreport); + } + } + @Overwrite public void D() throws InterruptedException { MinecraftTimings.bukkitSchedulerTimer.startTiming(); @@ -104,36 +133,40 @@ public class MixinMinecraftServer { } MinecraftTimings.timeUpdateTimer.stopTiming(); - for (int i = 0; i < worlds.size(); ++i) { - WorldServer mainWorld = worlds.get(i); - WorldServer entityWorld = worlds.get(i + 1 < worlds.size() ? i + 1 : 0); - TileEntityHopper.skipHopperEvents = entityWorld.paperConfig.disableHopperMoveEvents || InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; - - Akari.silentTiming = true; - Akari.STAGE_TICK.submit(() -> tickEntities(entityWorld), null); - - try { - mainWorld.timings.doTick.startTiming(); - mainWorld.doTick(); - mainWorld.timings.doTick.stopTiming(); - } catch (Throwable throwable) { - CrashReport crashreport; - try { - crashreport = CrashReport.a(throwable, "Exception ticking world"); - } catch (Throwable t){ - throw new RuntimeException("Error generating crash report", t); - } - mainWorld.a(crashreport); - throw new ReportedException(crashreport); + Akari.worldTiming.startTiming(); + if (AkarinGlobalConfig.legacyWorldTimings) { + for (int i = 0; i < worlds.size(); ++i) { + worlds.get(i).timings.tickEntities.startTiming(); + worlds.get(i).timings.doTick.startTiming(); } - - entityWorld.timings.tickEntities.startTiming(); - Akari.STAGE_TICK.take(); - entityWorld.timings.tickEntities.stopTiming(); - - entityWorld.getTracker().updatePlayers(); - Akari.silentTiming = false; - mainWorld.explosionDensityCache.clear(); // Paper - Optimize explosions + } + Akari.silentTiming = true; // Disable timings + Akari.STAGE_TICK.submit(() -> { + for (int i = 0; i < worlds.size(); ++i) { + WorldServer world = worlds.get(i); + tickEntities(world); + } + }, null); + + for (int i = 0; i < worlds.size(); ++i) { + WorldServer world = worlds.get(i); + tickWorld(world); + } + + Akari.STAGE_TICK.take(); + Akari.silentTiming = false; // Enable timings + Akari.worldTiming.stopTiming(); + if (AkarinGlobalConfig.legacyWorldTimings) { + for (int i = 0; i < worlds.size(); ++i) { + worlds.get(i).timings.tickEntities.stopTiming(); + worlds.get(i).timings.doTick.startTiming(); + } + } + + for (int i = 0; i < worlds.size(); ++i) { + WorldServer world = worlds.get(i); + world.getTracker().updatePlayers(); + world.explosionDensityCache.clear(); // Paper - Optimize explosions } MinecraftTimings.connectionTimer.startTiming(); diff --git a/sources/src/main/java/io/akarin/server/mixin/core/MixinTimingHandler.java b/sources/src/main/java/io/akarin/server/mixin/core/MixinTimingHandler.java deleted file mode 100644 index 6eea9a3ff..000000000 --- a/sources/src/main/java/io/akarin/server/mixin/core/MixinTimingHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.akarin.server.mixin.core; - -import java.util.logging.Level; - -import org.bukkit.Bukkit; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Overwrite; -import org.spongepowered.asm.mixin.Shadow; - -import io.akarin.api.Akari; - -@Mixin(targets = "co.aikar.timings.TimingHandler", remap = false) -public class MixinTimingHandler { - @Shadow @Final String name; - - @Shadow private long start = 0; - @Shadow private int timingDepth = 0; - @Shadow private boolean enabled; - - @Shadow void addDiff(long diff) {} - - @Overwrite - public void stopTiming() { - if (enabled && --timingDepth == 0 && start != 0) { - // Thread.currentThread() is an expensive operation, trying to avoid it - if (Akari.silentTiming) { // It must be off-main thread now - start = 0; - return; - } else { - if (!Bukkit.isPrimaryThread()) { - Bukkit.getLogger().log(Level.SEVERE, "stopTiming called async for " + name); - new Throwable().printStackTrace(); - start = 0; - return; - } - } - addDiff(System.nanoTime() - start); - start = 0; - } - } -} diff --git a/sources/src/main/resources/mixins.akarin.core.json b/sources/src/main/resources/mixins.akarin.core.json index a9e68d25d..df8dcbd90 100644 --- a/sources/src/main/resources/mixins.akarin.core.json +++ b/sources/src/main/resources/mixins.akarin.core.json @@ -16,7 +16,6 @@ "MixinMCUtil", "MixinMetrics", "MixinCraftServer", - "MixinTimingHandler", "MixinVersionCommand", "MixinMinecraftServer", "MixinChunkIOExecutor",