From a58b89b6527bd2689494bb2f5e77820c3ba42256 Mon Sep 17 00:00:00 2001 From: IzzelAliz Date: Sun, 17 May 2020 09:47:06 +0800 Subject: [PATCH] Calculate tps --- .../core/server/MinecraftServerMixin.java | 150 +++++++++++++++++- .../arclight/mod/ArclightMixinPlugin.java | 4 +- 2 files changed, 150 insertions(+), 4 deletions(-) diff --git a/arclight-coremod/src/main/java/io/izzel/arclight/mixin/core/server/MinecraftServerMixin.java b/arclight-coremod/src/main/java/io/izzel/arclight/mixin/core/server/MinecraftServerMixin.java index 92db9b1d..ece189a4 100644 --- a/arclight-coremod/src/main/java/io/izzel/arclight/mixin/core/server/MinecraftServerMixin.java +++ b/arclight-coremod/src/main/java/io/izzel/arclight/mixin/core/server/MinecraftServerMixin.java @@ -9,14 +9,22 @@ import com.mojang.datafixers.DataFixer; import io.izzel.arclight.bridge.command.ICommandSourceBridge; import io.izzel.arclight.bridge.server.MinecraftServerBridge; import io.izzel.arclight.bridge.world.WorldBridge; +import io.izzel.arclight.mod.ArclightConstants; import io.izzel.arclight.mod.server.BukkitRegistry; +import io.izzel.arclight.mod.util.BukkitOptionParser; import joptsimple.OptionParser; import joptsimple.OptionSet; import net.minecraft.command.CommandSource; import net.minecraft.command.Commands; +import net.minecraft.crash.CrashReport; +import net.minecraft.crash.ReportedException; +import net.minecraft.network.ServerStatusResponse; +import net.minecraft.profiler.DebugProfiler; import net.minecraft.server.MinecraftServer; import net.minecraft.server.management.PlayerProfileCache; import net.minecraft.util.SharedConstants; +import net.minecraft.util.Util; +import net.minecraft.util.text.StringTextComponent; import net.minecraft.world.WorldSettings; import net.minecraft.world.WorldType; import net.minecraft.world.chunk.listener.IChunkStatusListener; @@ -24,6 +32,9 @@ import net.minecraft.world.chunk.listener.IChunkStatusListenerFactory; import net.minecraft.world.server.ServerWorld; import net.minecraft.world.storage.SaveHandler; import net.minecraft.world.storage.WorldInfo; +import net.minecraftforge.fml.StartupQuery; +import net.minecraftforge.fml.server.ServerLifecycleHooks; +import org.apache.logging.log4j.Logger; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; @@ -33,27 +44,53 @@ import org.bukkit.craftbukkit.v1_14_R1.scoreboard.CraftScoreboardManager; import org.bukkit.event.server.ServerLoadEvent; import org.bukkit.event.world.WorldInitEvent; import org.bukkit.plugin.PluginLoadOrder; +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 org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; -import io.izzel.arclight.mod.ArclightConstants; -import io.izzel.arclight.mod.util.BukkitOptionParser; +import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.lang.management.ManagementFactory; import java.net.Proxy; import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; import java.util.function.BooleanSupplier; @Mixin(MinecraftServer.class) -public class MinecraftServerMixin implements MinecraftServerBridge, ICommandSourceBridge { +public abstract class MinecraftServerMixin implements MinecraftServerBridge, ICommandSourceBridge { // @formatter:off @Shadow private int tickCounter; + @Shadow protected abstract boolean init() throws IOException; + @Shadow protected long serverTime; + @Shadow @Final private ServerStatusResponse statusResponse; + @Shadow @Nullable private String motd; + @Shadow public abstract void applyServerIconToResponse(ServerStatusResponse response); + @Shadow private volatile boolean serverRunning; + @Shadow private long timeOfLastWarning; + @Shadow @Final private static Logger LOGGER; + @Shadow private boolean startProfiling; + @Shadow @Final private DebugProfiler profiler; + @Shadow protected abstract void tick(BooleanSupplier hasTimeLeft); + @Shadow protected abstract boolean isAheadOfTime(); + @Shadow private boolean isRunningScheduledTasks; + @Shadow private long runTasksUntil; + @Shadow protected abstract void runScheduledTasks(); + @Shadow private volatile boolean serverIsRunning; + @Shadow protected abstract void finalTick(CrashReport report); + @Shadow public abstract CrashReport addServerInfoToCrashReport(CrashReport report); + @Shadow public abstract File getDataDirectory(); + @Shadow private boolean serverStopped; + @Shadow protected abstract void stopServer(); + @Shadow protected abstract void systemExitNow(); // @formatter:on public CraftServer server; @@ -66,6 +103,12 @@ public class MinecraftServerMixin implements MinecraftServerBridge, ICommandSour private boolean hasStopped = false; private final Object stopLock = new Object(); + private static final int TPS = 20; + private static final int TICK_TIME = 1000000000 / TPS; + private static final int SAMPLE_INTERVAL = 100; + private static int currentTick = (int) (System.currentTimeMillis() / 50); + public final double[] recentTps = new double[3]; + public boolean hasStopped() { synchronized (stopLock) { return hasStopped; @@ -83,6 +126,107 @@ public class MinecraftServerMixin implements MinecraftServerBridge, ICommandSour } } + /** + * @author IzzelAliz + * @reason + */ + @Overwrite + public void run() { + try { + if (this.init()) { + ServerLifecycleHooks.handleServerStarted((MinecraftServer) (Object) this); + this.serverTime = Util.milliTime(); + this.statusResponse.setServerDescription(new StringTextComponent(this.motd)); + this.statusResponse.setVersion(new ServerStatusResponse.Version(SharedConstants.getVersion().getName(), SharedConstants.getVersion().getProtocolVersion())); + this.applyServerIconToResponse(this.statusResponse); + + Arrays.fill(recentTps, 0); + long curTime, tickSection = Util.milliTime(), tickCount = 1; + + while (this.serverRunning) { + long i = (curTime = Util.milliTime()) - this.serverTime; + if (i > 5000L && this.serverTime - this.timeOfLastWarning >= 30000L) { + long j = i / 50L; + + if (server.getWarnOnOverload()) { + LOGGER.warn("Can't keep up! Is the server overloaded? Running {}ms or {} ticks behind", i, j); + } + + this.serverTime += j * 50L; + this.timeOfLastWarning = this.serverTime; + } + + if (tickCount++ % SAMPLE_INTERVAL == 0) { + double currentTps = 1E3 / (curTime - tickSection) * SAMPLE_INTERVAL; + recentTps[0] = calcTps(recentTps[0], 0.92, currentTps); // 1/exp(5sec/1min) + recentTps[1] = calcTps(recentTps[1], 0.9835, currentTps); // 1/exp(5sec/5min) + recentTps[2] = calcTps(recentTps[2], 0.9945, currentTps); // 1/exp(5sec/15min) + tickSection = curTime; + } + + currentTick = (int) (System.currentTimeMillis() / 50); + + this.serverTime += 50L; + if (this.startProfiling) { + this.startProfiling = false; + this.profiler.func_219899_d().func_219939_d(); + } + + this.profiler.startTick(); + this.profiler.startSection("tick"); + this.tick(this::isAheadOfTime); + this.profiler.endStartSection("nextTickWait"); + this.isRunningScheduledTasks = true; + this.runTasksUntil = Math.max(Util.milliTime() + 50L, this.serverTime); + this.runScheduledTasks(); + this.profiler.endSection(); + this.profiler.endTick(); + this.serverIsRunning = true; + } + ServerLifecycleHooks.handleServerStopping((MinecraftServer) (Object) this); + ServerLifecycleHooks.expectServerStopped(); // has to come before finalTick to avoid race conditions + } else { + ServerLifecycleHooks.expectServerStopped(); // has to come before finalTick to avoid race conditions + this.finalTick((CrashReport) null); + } + } catch (StartupQuery.AbortedException e) { + // ignore silently + ServerLifecycleHooks.expectServerStopped(); // has to come before finalTick to avoid race conditions + } catch (Throwable throwable1) { + LOGGER.error("Encountered an unexpected exception", throwable1); + CrashReport crashreport; + if (throwable1 instanceof ReportedException) { + crashreport = this.addServerInfoToCrashReport(((ReportedException) throwable1).getCrashReport()); + } else { + crashreport = this.addServerInfoToCrashReport(new CrashReport("Exception in server tick loop", throwable1)); + } + + File file1 = new File(new File(this.getDataDirectory(), "crash-reports"), "crash-" + (new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss")).format(new Date()) + "-server.txt"); + if (crashreport.saveToFile(file1)) { + LOGGER.error("This crash report has been saved to: {}", (Object) file1.getAbsolutePath()); + } else { + LOGGER.error("We were unable to save this crash report to disk."); + } + + ServerLifecycleHooks.expectServerStopped(); // has to come before finalTick to avoid race conditions + this.finalTick(crashreport); + } finally { + try { + this.serverStopped = true; + this.stopServer(); + } catch (Throwable throwable) { + LOGGER.error("Exception stopping the server", throwable); + } finally { + ServerLifecycleHooks.handleServerStopped((MinecraftServer) (Object) this); + this.systemExitNow(); + } + } + } + + private static double calcTps(double avg, double exp, double tps) { + return (avg * exp) + (tps * (1 - exp)); + } + @Inject(method = "stopServer", cancellable = true, at = @At("HEAD")) public void arclight$setStopped(CallbackInfo ci) { synchronized (stopLock) { diff --git a/arclight-coremod/src/main/java/io/izzel/arclight/mod/ArclightMixinPlugin.java b/arclight-coremod/src/main/java/io/izzel/arclight/mod/ArclightMixinPlugin.java index 8340c1b2..8029732f 100644 --- a/arclight-coremod/src/main/java/io/izzel/arclight/mod/ArclightMixinPlugin.java +++ b/arclight-coremod/src/main/java/io/izzel/arclight/mod/ArclightMixinPlugin.java @@ -29,7 +29,9 @@ public class ArclightMixinPlugin implements IMixinConfigPlugin { ImmutableMap., List>>builder() .put("net.minecraft.server.MinecraftServer", Maps.immutableEntry( - ImmutableList.of(), + ImmutableList.of( + new FieldNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "currentTick", "I", null, null) + ), ImmutableList.of( new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "getServer", "()Lnet/minecraft/server/MinecraftServer;", null, null) )