Resource Get Server TPS

Discussion in 'Spigot Plugin Development' started by ProJoosh, Jun 4, 2016.

  1. What is this:
    Hey, so alot of people have been asking on these forums "How do i get the server tps" or "How can i get the server tps" and there isnt really alot of anwsers to that question and many of the anwsers use nms classes which will then later require a reflection class. So im here to show you my way of getting the servers tps using nms classes with a reflection so you can get it with multiple versions. If im not mistaken this will only work if you are using the Spigot API. P.S If you can see any improvements to this code please post below and ill update this post, p.s im new to reflection classes. Another way this could be done though is creating your own timer and calculating the delay the task takes to complete calculating your own tps which may give a more accurate result as im not sure on the accuracy of this method.

    Step 1: Firstly when making a reflection for somthing you will want to get the server version mecause as you might know when you just import nms clases it will import things like net.minecraft.server.1.9_R1 so you want to get that version e.g. 1.9_R1 so you can compile it to your own class fetcher.
    Code (Text):

        private final String name = Bukkit.getServer().getClass().getPackage().getName();
        private final String version = name.substring(name.lastIndexOf('.') + 1);
     
    Step 2: Create a method to fetch the NMS class supplying it with a parameter of a string with the class name so you can return that class from teh compiled net.minecraft.server + version
    Code (Text):

        private Class<?> getNMSClass(String className) {
            try {
                return Class.forName("net.minecraft.server." + version + "." + className);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
     
    Step 3: In the top of your class create empty variables to store the server Instance and the tps response along with the decimal formatter.
    Code (Text):

        private final DecimalFormat format = new DecimalFormat("##.##");

        private Object serverInstance;
        private Field tpsField;
     
    Step 4: Next you need to assign these variables values so you can either use a method and call it onEnable or put it in your constructor:
    Code (Text):

            try {
                serverInstance = getNMSClass("MinecraftServer").getMethod("getServer").invoke(null);
                tpsField = serverInstance.getClass().getField("recentTps");
            } catch (NoSuchFieldException | SecurityException | IllegalAccessException | IllegalArgumentException
                    | InvocationTargetException | NoSuchMethodException e) {
                e.printStackTrace();
            }
     
    Step 5: Now you can create the method to get the tps, this is a simple method which will return a string and accepts one int parameter which will be the time you want to get it for, a value of 0 will get the tps for the last minute, value of 1 will be 5min and 2 would be 15min.
    Code (Text):

        public String getTPS(int time) {
            try {
                double[] tps = ((double[]) tpsField.get(serverInstance));
                return format.format(tps[time]);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
     
    Thanks :) If this helped please say so, if you have any improvements which i havent seen please post below and i will update the main post crediting you. Please no hate :) I just trying to help as i cant find a resource out there showing how to do this with reflection.
    Thanks to: @PickNChew @MaTaMoR_
     
    #1 ProJoosh, Jun 4, 2016
    Last edited: Jun 4, 2016
    • Useful Useful x 6
    • Informative Informative x 4
    • Like Like x 1
    • Funny Funny x 1
  2. Great tutorial mate, keep it up!
     
    • Friendly Friendly x 1
  3. Very usefull! Thanks!
     
    • Friendly Friendly x 1
  4. Awesome tutorial, great job! :D
     
    • Friendly Friendly x 1
  5. Thank you for your kind responses :D
     
  6. Good job
     
    • Friendly Friendly x 1
  7. Cache methods and fields. Reflection is expensive.
     
    • Agree Agree x 1
    • Useful Useful x 1
  8. Code (Java):
    recentTPS = decimalFormat.format(tps[0]);
    Rather try the average:
    Code (Java):
    recentTPS = (decimalFormat.format(tps[0])+decimalFormat.format(tps[1])+decimalFormat.format(tps[2]))/3;
    Code (Java):
    instance = minecraftClass.getMethod("getServer").invoke(null);
    Shouldn't that be invoked at Bukkit.getServer() ?

    Also, save the methods, classes and fields.
     
    • Useful Useful x 1
  9. Bukkit.getServer() dosnt have a .recentTps field. The recentTps is from MinecraftServer, they arent the same thing which is why this requires nms and reflection.
     
  10. This version works with Bukkit/Spigot
    Class Lag

    Code (Text):
    public class Lag
    implements Runnable
    {
    public static int TICK_COUNT= 0;
    public static long[] TICKS= new long[600];
    public static long LAST_TICK= 0L;

    public static double getTPS()
    {
      return getTPS(100);
    }

    public static double getTPS(int ticks)
    {
      if (TICK_COUNT< ticks) {
        return 20.0D;
      }
      int target = (TICK_COUNT- 1 - ticks) % TICKS.length;
      long elapsed = System.currentTimeMillis() - TICKS[target];

      return ticks / (elapsed / 1000.0D);
    }

    public static long getElapsed(int tickID)
    {
      if (TICK_COUNT- tickID >= TICKS.length)
      {
      }

      long time = TICKS[(tickID % TICKS.length)];
      return System.currentTimeMillis() - time;
    }

    public void run()
    {
      TICKS[(TICK_COUNT% TICKS.length)] = System.currentTimeMillis();

      TICK_COUNT+= 1;
    }
    }
    [CENTER]
    And in your main class

    Code (Text):
    getServer().getScheduler().scheduleSyncRepeatingTask(this, new Lag(), 100L, 1L);

    Which would work as
    Code (Text):
        double tps = Lag.getTPS();
            double lag = Math.round((1.0D - tps / 20.0D) * 100.0D);

    Example Code:
    Sender.sendMessage(ChatColor.YELLOW + "The current TPS is " + ChatColor.GREEN + tps + ChatColor.YELLOW + ".");

    This version only works in Paper Spigot:

    Code (Text):
    double tps = Spigot.getTPS();
            double lag = Math.round((1.0D - tps / 20.0D) * 100.0D);

    Example Code:
    Sender.sendMessage(ChatColor.YELLOW + "The current TPS is " + ChatColor.GREEN + tps + ChatColor.YELLOW + ".");
     
  11. Yes, i said this is another way it could be done with a timer calculating it yourself.
     
  12. Oh Okay, Well I was just pointing out more suggestions which are more user-friendly for people who uses other versions then Spigot E.G PaperSpigot, PaperClip, Bukkit
     
  13. Yeah i see, i dont really understand why people use PaperSpigot and bukkit though because spigot is already really good optimised and bukkit is just, eh
     
  14. I have no idea what this is but looks cool
     
  15. PaperSpigot is even more optimized and has more configurability along with more API features (like in the future Spigot.getTPS() - It's not supported yet).

    IMO the Repeating Task method should never be used. If every plugin used it, the server would spend most of it's tick time calculating how much time a tick is taking which increases the amount of time it takes to calculate a tick. Basically, it's better to use a reflection class to get the variable where it's already been calculated. :)
     
  16. Very Very helpful Tank you :)

    Edit:
    Here it is all in one Method
    Code (Text):
        public String getTPS() {
           
            String name1 = Bukkit.getServer().getClass().getPackage().getName();
            String version = name1.substring(name1.lastIndexOf('.') + 1);
           
            Class<?> mcsclass = null;
           
            DecimalFormat format = new DecimalFormat("##.##");
           
            Object si = null;
            Field tpsField = null;
           
            try {
                mcsclass = Class.forName("net.minecraft.server." + version + "." + "MinecraftServer");
               
                si = mcsclass.getMethod("getServer").invoke(null);
               
                tpsField = si.getClass().getField("recentTps");
               
            } catch (ClassNotFoundException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | NoSuchFieldException e) {
                e.printStackTrace();
            }
           
            double[] tps = null;
           
            try {
                tps = ((double[]) tpsField.get(si));
               
            } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace();}
           
            return format.format(tps[0]);
        }
     
    #16 GravelCZLP, Jun 25, 2016
    Last edited: Jun 25, 2016
  17. Yes, I 100% agree with you, I've already use a different method which already calculates the TPS without it wasting the servers resources.