From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <Blake.Galbreath@GMail.com>
Date: Sun, 5 Apr 2020 22:23:14 -0500
Subject: [PATCH] Add tick times API and /mspt command


diff --git a/src/main/java/io/papermc/paper/command/MSPTCommand.java b/src/main/java/io/papermc/paper/command/MSPTCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..8b5293b0c696ef21d0101493ffa41b60bf0bc86b
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/MSPTCommand.java
@@ -0,0 +1,102 @@
+package io.papermc.paper.command;
+
+import net.kyori.adventure.text.Component;
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.Location;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.GOLD;
+import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
+import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
+import static net.kyori.adventure.text.format.NamedTextColor.YELLOW;
+
+@DefaultQualifier(NonNull.class)
+public final class MSPTCommand extends Command {
+    private static final DecimalFormat DF = new DecimalFormat("########0.0");
+    private static final Component SLASH = text("/");
+
+    public MSPTCommand(final String name) {
+        super(name);
+        this.description = "View server tick times";
+        this.usageMessage = "/mspt";
+        this.setPermission("bukkit.command.mspt");
+    }
+
+    @Override
+    public List<String> tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public boolean execute(CommandSender sender, String commandLabel, String[] args) {
+        if (!testPermission(sender)) return true;
+
+        MinecraftServer server = MinecraftServer.getServer();
+
+        List<Component> times = new ArrayList<>();
+        times.addAll(eval(server.tickTimes5s.getTimes()));
+        times.addAll(eval(server.tickTimes10s.getTimes()));
+        times.addAll(eval(server.tickTimes60s.getTimes()));
+
+        sender.sendMessage(text().content("Server tick times ").color(GOLD)
+            .append(text().color(YELLOW)
+                .append(
+                    text("("),
+                    text("avg", GRAY),
+                    text("/"),
+                    text("min", GRAY),
+                    text("/"),
+                    text("max", GRAY),
+                    text(")")
+                )
+            ).append(
+                text(" from last 5s"),
+                text(",", GRAY),
+                text(" 10s"),
+                text(",", GRAY),
+                text(" 1m"),
+                text(":", YELLOW)
+            )
+        );
+        sender.sendMessage(text().content("◴ ").color(GOLD)
+            .append(text().color(GRAY)
+                .append(
+                    times.get(0), SLASH, times.get(1), SLASH, times.get(2), text(", ", YELLOW),
+                    times.get(3), SLASH, times.get(4), SLASH, times.get(5), text(", ", YELLOW),
+                    times.get(6), SLASH, times.get(7), SLASH, times.get(8)
+                )
+            )
+        );
+        return true;
+    }
+
+    private static List<Component> eval(long[] times) {
+        long min = Integer.MAX_VALUE;
+        long max = 0L;
+        long total = 0L;
+        for (long value : times) {
+            if (value > 0L && value < min) min = value;
+            if (value > max) max = value;
+            total += value;
+        }
+        double avgD = ((double) total / (double) times.length) * 1.0E-6D;
+        double minD = ((double) min) * 1.0E-6D;
+        double maxD = ((double) max) * 1.0E-6D;
+        return Arrays.asList(getColor(avgD), getColor(minD), getColor(maxD));
+    }
+
+    private static Component getColor(double avg) {
+        return text(DF.format(avg), avg >= 50 ? RED : avg >= 40 ? YELLOW : GREEN);
+    }
+}
diff --git a/src/main/java/io/papermc/paper/command/PaperCommands.java b/src/main/java/io/papermc/paper/command/PaperCommands.java
index 72f2e81b9905a0d57ed8e2a88578f62d5235c456..7b58b2d6297800c2dcdbf7539e5ab8e7703f39f1 100644
--- a/src/main/java/io/papermc/paper/command/PaperCommands.java
+++ b/src/main/java/io/papermc/paper/command/PaperCommands.java
@@ -18,6 +18,7 @@ public final class PaperCommands {
     static {
         COMMANDS.put("paper", new PaperCommand("paper"));
         COMMANDS.put("callback", new CallbackCommand("callback"));
+        COMMANDS.put("mspt", new MSPTCommand("mspt"));
     }
 
     public static void registerCommands(final MinecraftServer server) {
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index a7eeb4f3098a4bea05592890b5fecbfbac3090e4..e1a15f3721e3c661be0185d65073a39f293f0589 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -263,6 +263,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
     private int playerIdleTimeout;
     private final long[] tickTimesNanos;
     private long aggregatedTickTimesNanos;
+    // Paper start - Add tick times API and /mspt command
+    public final TickTimes tickTimes5s = new TickTimes(100);
+    public final TickTimes tickTimes10s = new TickTimes(200);
+    public final TickTimes tickTimes60s = new TickTimes(1200);
+    // Paper end - Add tick times API and /mspt command
     @Nullable
     private KeyPair keyPair;
     @Nullable
@@ -1511,6 +1516,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
         this.aggregatedTickTimesNanos += k;
         this.tickTimesNanos[l] = k;
         this.smoothedTickTimeMillis = this.smoothedTickTimeMillis * 0.8F + (float) k / (float) TimeUtil.NANOSECONDS_PER_MILLISECOND * 0.19999999F;
+        // Paper start - Add tick times API and /mspt command
+        this.tickTimes5s.add(this.tickCount, k);
+        this.tickTimes10s.add(this.tickCount, k);
+        this.tickTimes60s.add(this.tickCount, k);
+        // Paper end - Add tick times API and /mspt command
         this.logTickMethodTime(i);
         gameprofilerfiller.pop();
     }
@@ -2901,4 +2911,30 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
     public static record ServerResourcePackInfo(UUID id, String url, String hash, boolean isRequired, @Nullable Component prompt) {
 
     }
+
+    // Paper start - Add tick times API and /mspt command
+    public static class TickTimes {
+        private final long[] times;
+
+        public TickTimes(int length) {
+            times = new long[length];
+        }
+
+        void add(int index, long time) {
+            times[index % times.length] = time;
+        }
+
+        public long[] getTimes() {
+            return times.clone();
+        }
+
+        public double getAverage() {
+            long total = 0L;
+            for (long value : times) {
+                total += value;
+            }
+            return ((double) total / (double) times.length) * 1.0E-6D;
+        }
+    }
+    // Paper end - Add tick times API and /mspt command
 }
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 6558fc8c8fc85b3f1e0d77fdc7a2b3937bf3d885..1b80d12515141fb6a6b33475a086d85d437e60a5 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -2714,6 +2714,18 @@ public final class CraftServer implements Server {
         return CraftMagicNumbers.INSTANCE;
     }
 
+    // Paper start
+    @Override
+    public long[] getTickTimes() {
+        return this.getServer().tickTimes5s.getTimes();
+    }
+
+    @Override
+    public double getAverageTickTime() {
+        return this.getServer().tickTimes5s.getAverage();
+    }
+    // Paper end
+
     // Spigot start
     private final org.bukkit.Server.Spigot spigot = new org.bukkit.Server.Spigot()
     {