diff --git a/Spigot-API-Patches/0014-Require-Java-8.patch b/Spigot-API-Patches/0014-Require-Java-8.patch index fea9b5acd0..4187ee84e6 100644 --- a/Spigot-API-Patches/0014-Require-Java-8.patch +++ b/Spigot-API-Patches/0014-Require-Java-8.patch @@ -1,11 +1,11 @@ -From 9bdea46a67a9a7779a451416f32af354373289dc Mon Sep 17 00:00:00 2001 +From 1075d256ef7221d811991952a51e5a16b3773ca4 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Fri, 8 Jan 2016 23:50:25 -0600 Subject: [PATCH] Require Java 8 diff --git a/pom.xml b/pom.xml -index 3ccfabb..7dd0adb 100644 +index bc6964d..2c43d25 100644 --- a/pom.xml +++ b/pom.xml @@ -19,8 +19,9 @@ @@ -20,7 +20,7 @@ index 3ccfabb..7dd0adb 100644 UTF-8 -@@ -136,26 +137,6 @@ +@@ -129,26 +130,6 @@ diff --git a/Spigot-API-Patches/0015-Timings-v2.patch b/Spigot-API-Patches/0015-Timings-v2.patch new file mode 100644 index 0000000000..b75ca5f30a --- /dev/null +++ b/Spigot-API-Patches/0015-Timings-v2.patch @@ -0,0 +1,3427 @@ +From 3957d5f9f79ab7212bfcbc2204e4edd89c3c7600 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 8 Jan 2016 23:12:28 -0600 +Subject: [PATCH] Timings v2 + + +diff --git a/pom.xml b/pom.xml +index 2c43d25..7dd0adb 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -47,6 +47,13 @@ + + + ++ net.sf.trove4j ++ trove4j ++ 3.0.3 ++ ++ provided ++ ++ + commons-lang + commons-lang + 2.6 +diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java +index 584fe11..a4396e8 100644 +--- a/src/main/java/org/bukkit/Bukkit.java ++++ b/src/main/java/org/bukkit/Bukkit.java +@@ -533,7 +533,6 @@ public final class Bukkit { + */ + public static void reload() { + server.reload(); +- org.spigotmc.CustomTimingsHandler.reload(); // Spigot + } + + /** +diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java +index 26acdda..ae75bd4 100644 +--- a/src/main/java/org/bukkit/Server.java ++++ b/src/main/java/org/bukkit/Server.java +@@ -926,12 +926,27 @@ public interface Server extends PluginMessageRecipient { + + public class Spigot + { +- ++ @Deprecated + public org.bukkit.configuration.file.YamlConfiguration getConfig() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + ++ public org.bukkit.configuration.file.YamlConfiguration getBukkitConfig() ++ { ++ throw new UnsupportedOperationException( "Not supported yet." ); ++ } ++ ++ public org.bukkit.configuration.file.YamlConfiguration getSpigotConfig() ++ { ++ throw new UnsupportedOperationException("Not supported yet."); ++ } ++ ++ public org.bukkit.configuration.file.YamlConfiguration getPaperSpigotConfig() ++ { ++ throw new UnsupportedOperationException("Not supported yet."); ++ } ++ + /** + * Sends the component to the player + * +diff --git a/src/main/java/org/bukkit/command/Command.java b/src/main/java/org/bukkit/command/Command.java +index 0ba9b1c..535cdd6 100644 +--- a/src/main/java/org/bukkit/command/Command.java ++++ b/src/main/java/org/bukkit/command/Command.java +@@ -31,7 +31,8 @@ public abstract class Command { + protected String usageMessage; + private String permission; + private String permissionMessage; +- public org.spigotmc.CustomTimingsHandler timings; // Spigot ++ public org.spigotmc.timings.Timing timings; // Spigot ++ public String getTimingName() {return getName();} // Spigot + + protected Command(String name) { + this(name, "", "/" + name, new ArrayList()); +@@ -45,7 +46,6 @@ public abstract class Command { + this.usageMessage = usageMessage; + this.aliases = aliases; + this.activeAliases = new ArrayList(aliases); +- this.timings = new org.spigotmc.CustomTimingsHandler("** Command: " + name); // Spigot + } + + /** +@@ -229,7 +229,6 @@ public abstract class Command { + public boolean setLabel(String name) { + this.nextLabel = name; + if (!isRegistered()) { +- this.timings = new org.spigotmc.CustomTimingsHandler("** Command: " + name); // Spigot + this.label = name; + return true; + } +diff --git a/src/main/java/org/bukkit/command/FormattedCommandAlias.java b/src/main/java/org/bukkit/command/FormattedCommandAlias.java +index 3f07d7f..c901a24 100644 +--- a/src/main/java/org/bukkit/command/FormattedCommandAlias.java ++++ b/src/main/java/org/bukkit/command/FormattedCommandAlias.java +@@ -14,6 +14,7 @@ public class FormattedCommandAlias extends Command { + + public FormattedCommandAlias(String alias, String[] formatStrings) { + super(alias); ++ timings = org.spigotmc.timings.TimingsManager.getCommandTiming("minecraft", this); // Spigot + this.formatStrings = formatStrings; + } + +@@ -118,6 +119,9 @@ public class FormattedCommandAlias extends Command { + return formatString; + } + ++ @Override // Spigot ++ public String getTimingName() {return "Command Forwarder - " + super.getTimingName();} // Spigot ++ + private static boolean inRange(int i, int j, int k) { + return i >= j && i <= k; + } +diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java +index a08a49d..d11cbc2 100644 +--- a/src/main/java/org/bukkit/command/SimpleCommandMap.java ++++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java +@@ -31,7 +31,7 @@ public class SimpleCommandMap implements CommandMap { + register("bukkit", new VersionCommand("version")); + register("bukkit", new ReloadCommand("reload")); + register("bukkit", new PluginsCommand("plugins")); +- register("bukkit", new TimingsCommand("timings")); ++ register("bukkit", new org.spigotmc.timings.TimingsCommand("timings")); // Spigot + } + + public void setFallbackCommands() { +@@ -60,6 +60,7 @@ public class SimpleCommandMap implements CommandMap { + * {@inheritDoc} + */ + public boolean register(String label, String fallbackPrefix, Command command) { ++ command.timings = org.spigotmc.timings.TimingsManager.getCommandTiming(fallbackPrefix, command); // Spigot + label = label.toLowerCase().trim(); + fallbackPrefix = fallbackPrefix.toLowerCase().trim(); + boolean registered = register(label, command, false, fallbackPrefix); +diff --git a/src/main/java/org/bukkit/command/defaults/TimingsCommand.java b/src/main/java/org/bukkit/command/defaults/TimingsCommand.java +index 9782a3b..80e0b0f 100644 +--- a/src/main/java/org/bukkit/command/defaults/TimingsCommand.java ++++ b/src/main/java/org/bukkit/command/defaults/TimingsCommand.java +@@ -33,87 +33,22 @@ import org.spigotmc.CustomTimingsHandler; + // Spigot end + + public class TimingsCommand extends BukkitCommand { +- private static final List TIMINGS_SUBCOMMANDS = ImmutableList.of("report", "reset", "on", "off", "paste"); // Spigot +- public static long timingStart = 0; // Spigot ++ public static final List TIMINGS_SUBCOMMANDS = ImmutableList.of("merged", "reset", "separate"); + + public TimingsCommand(String name) { + super(name); +- this.description = "Manages Spigot Timings data to see performance of the server."; // Spigot +- this.usageMessage = "/timings "; // Spigot ++ this.description = "Records timings for all plugin events"; ++ this.usageMessage = "/timings "; + this.setPermission("bukkit.command.timings"); + } + +- // Spigot start - redesigned Timings Command +- public void executeSpigotTimings(CommandSender sender, String[] args) { +- if ( "on".equals( args[0] ) ) +- { +- ( (SimplePluginManager) Bukkit.getPluginManager() ).useTimings( true ); +- CustomTimingsHandler.reload(); +- sender.sendMessage( "Enabled Timings & Reset" ); +- return; +- } else if ( "off".equals( args[0] ) ) +- { +- ( (SimplePluginManager) Bukkit.getPluginManager() ).useTimings( false ); +- sender.sendMessage( "Disabled Timings" ); +- return; +- } +- +- if ( !Bukkit.getPluginManager().useTimings() ) +- { +- sender.sendMessage( "Please enable timings by typing /timings on" ); +- return; +- } +- +- boolean paste = "paste".equals( args[0] ); +- if ("reset".equals(args[0])) { +- CustomTimingsHandler.reload(); +- sender.sendMessage("Timings reset"); +- } else if ("merged".equals(args[0]) || "report".equals(args[0]) || paste) { +- long sampleTime = System.nanoTime() - timingStart; +- int index = 0; +- File timingFolder = new File("timings"); +- timingFolder.mkdirs(); +- File timings = new File(timingFolder, "timings.txt"); +- ByteArrayOutputStream bout = ( paste ) ? new ByteArrayOutputStream() : null; +- while (timings.exists()) timings = new File(timingFolder, "timings" + (++index) + ".txt"); +- PrintStream fileTimings = null; +- try { +- fileTimings = ( paste ) ? new PrintStream( bout ) : new PrintStream( timings ); +- +- CustomTimingsHandler.printTimings(fileTimings); +- fileTimings.println( "Sample time " + sampleTime + " (" + sampleTime / 1E9 + "s)" ); +- +- fileTimings.println( "" ); +- fileTimings.println( Bukkit.spigot().getConfig().saveToString() ); +- fileTimings.println( "" ); +- +- if ( paste ) +- { +- new PasteThread( sender, bout ).start(); +- return; +- } +- +- sender.sendMessage("Timings written to " + timings.getPath()); +- sender.sendMessage( "Paste contents of file into form at http://aikar.co/timings.php to read results." ); +- +- } catch (IOException e) { +- } finally { +- if (fileTimings != null) { +- fileTimings.close(); +- } +- } +- } +- } +- // Spigot end +- + @Override + public boolean execute(CommandSender sender, String currentAlias, String[] args) { + if (!testPermission(sender)) return true; +- if (args.length < 1) { // Spigot ++ if (args.length != 1) { + sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); + return false; + } +- if (true) { executeSpigotTimings(sender, args); return true; } // Spigot + if (!sender.getServer().getPluginManager().useTimings()) { + sender.sendMessage("Please enable timings by setting \"settings.plugin-profiling\" to true in bukkit.yml"); + return true; +@@ -199,55 +134,4 @@ public class TimingsCommand extends BukkitCommand { + } + return ImmutableList.of(); + } +- +- // Spigot start +- private static class PasteThread extends Thread +- { +- +- private final CommandSender sender; +- private final ByteArrayOutputStream bout; +- +- public PasteThread(CommandSender sender, ByteArrayOutputStream bout) +- { +- super( "Timings paste thread" ); +- this.sender = sender; +- this.bout = bout; +- } +- +- @Override +- public synchronized void start() { +- if (sender instanceof RemoteConsoleCommandSender) { +- run(); +- } else { +- super.start(); +- } +- } +- +- @Override +- public void run() +- { +- try +- { +- HttpURLConnection con = (HttpURLConnection) new URL( "http://paste.ubuntu.com/" ).openConnection(); +- con.setDoOutput( true ); +- con.setRequestMethod( "POST" ); +- con.setInstanceFollowRedirects( false ); +- +- OutputStream out = con.getOutputStream(); +- out.write( "poster=Spigot&syntax=text&content=".getBytes( "UTF-8" ) ); +- out.write( URLEncoder.encode( bout.toString( "UTF-8" ), "UTF-8" ).getBytes( "UTF-8" ) ); +- out.close(); +- con.getInputStream().close(); +- +- String location = con.getHeaderField( "Location" ); +- String pasteID = location.substring( "http://paste.ubuntu.com/".length(), location.length() - 1 ); +- sender.sendMessage( ChatColor.GREEN + "Timings results can be viewed at http://aikar.co/timings.php?url=" + pasteID ); +- } catch ( IOException ex ) +- { +- sender.sendMessage( ChatColor.RED + "Error pasting timings, check your console for more information" ); +- Bukkit.getServer().getLogger().log( Level.WARNING, "Could not paste timings", ex ); +- } +- } +- } +- // Spigot end + } +diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java +index 7522d45..c0ebe65 100644 +--- a/src/main/java/org/bukkit/entity/Player.java ++++ b/src/main/java/org/bukkit/entity/Player.java +@@ -1203,6 +1203,11 @@ public interface Player extends HumanEntity, Conversable, CommandSender, Offline + { + throw new UnsupportedOperationException( "Not supported yet" ); + } ++ ++ public int getPing() ++ { ++ throw new UnsupportedOperationException( "Not supported yet." ); ++ } + } + + Spigot spigot(); +diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +index c9d23d6..eb654c2 100644 +--- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java ++++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +@@ -295,7 +295,6 @@ public final class SimplePluginManager implements PluginManager { + } + } + +- org.bukkit.command.defaults.TimingsCommand.timingStart = System.nanoTime(); // Spigot + return result.toArray(new Plugin[result.size()]); + } + +@@ -332,7 +331,7 @@ public final class SimplePluginManager implements PluginManager { + + if (result != null) { + plugins.add(result); +- lookupNames.put(result.getDescription().getName(), result); ++ lookupNames.put(result.getDescription().getName().toLowerCase(), result); // Spigot + } + + return result; +@@ -358,7 +357,7 @@ public final class SimplePluginManager implements PluginManager { + * @return Plugin if it exists, otherwise null + */ + public synchronized Plugin getPlugin(String name) { +- return lookupNames.get(name.replace(' ', '_')); ++ return lookupNames.get(name.replace(' ', '_').toLowerCase()); // Spigot + } + + public synchronized Plugin[] getPlugins() { +@@ -556,7 +555,8 @@ public final class SimplePluginManager implements PluginManager { + throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled"); + } + +- if (useTimings) { ++ executor = new org.spigotmc.timings.TimedEventExecutor(executor, plugin, null, event); // Spigot ++ if (false) { // Spigot - RL handles useTimings check now + getEventListeners(event).register(new TimedRegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); + } else { + getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); +@@ -717,7 +717,7 @@ public final class SimplePluginManager implements PluginManager { + } + + public boolean useTimings() { +- return useTimings; ++ return org.spigotmc.timings.Timings.isTimingsEnabled(); // Spigot + } + + /** +@@ -726,6 +726,6 @@ public final class SimplePluginManager implements PluginManager { + * @param use True if per event timing code should be used + */ + public void useTimings(boolean use) { +- useTimings = use; ++ org.spigotmc.timings.Timings.setTimingsEnabled(use); // Spigot + } + } +diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java +index 7bf2fa6..35cf754 100644 +--- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java ++++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java +@@ -39,7 +39,6 @@ import org.bukkit.plugin.PluginLoader; + import org.bukkit.plugin.RegisteredListener; + import org.bukkit.plugin.TimedRegisteredListener; + import org.bukkit.plugin.UnknownDependencyException; +-import org.spigotmc.CustomTimingsHandler; // Spigot + import org.yaml.snakeyaml.error.YAMLException; + + /** +@@ -50,7 +49,6 @@ public final class JavaPluginLoader implements PluginLoader { + private final Pattern[] fileFilters = new Pattern[] { Pattern.compile("\\.jar$"), }; + private final Map> classes = new java.util.concurrent.ConcurrentHashMap>(); // Spigot + private final Map loaders = new LinkedHashMap(); +- public static final CustomTimingsHandler pluginParentTimer = new CustomTimingsHandler("** Plugins"); // Spigot + + /** + * This class was not meant to be constructed explicitly +@@ -293,26 +291,20 @@ public final class JavaPluginLoader implements PluginLoader { + } + } + +- final CustomTimingsHandler timings = new CustomTimingsHandler("Plugin: " + plugin.getDescription().getFullName() + " Event: " + listener.getClass().getName() + "::" + method.getName()+"("+eventClass.getSimpleName()+")", pluginParentTimer); // Spigot +- EventExecutor executor = new EventExecutor() { ++ EventExecutor executor = new org.spigotmc.timings.TimedEventExecutor(new EventExecutor() { // Spigot + public void execute(Listener listener, Event event) throws EventException { + try { + if (!eventClass.isAssignableFrom(event.getClass())) { + return; + } +- // Spigot start +- boolean isAsync = event.isAsynchronous(); +- if (!isAsync) timings.startTiming(); + method.invoke(listener, event); +- if (!isAsync) timings.stopTiming(); +- // Spigot end + } catch (InvocationTargetException ex) { + throw new EventException(ex.getCause()); + } catch (Throwable t) { + throw new EventException(t); + } + } +- }; ++ }, plugin, method, eventClass); // Spigot + if (false) { // Spigot - RL handles useTimings check now + eventSet.add(new TimedRegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled())); + } else { +diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java +index 4cffa13..b2cbf9e 100644 +--- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java ++++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java +@@ -15,7 +15,8 @@ import org.bukkit.plugin.PluginDescriptionFile; + /** + * A ClassLoader for plugins, to allow shared classes across multiple plugins + */ +-final class PluginClassLoader extends URLClassLoader { ++public final class PluginClassLoader extends URLClassLoader { // Spigot ++ public JavaPlugin getPlugin() { return plugin; } // Spigot + private final JavaPluginLoader loader; + private final Map> classes = new java.util.concurrent.ConcurrentHashMap>(); // Spigot + private final PluginDescriptionFile description; +diff --git a/src/main/java/org/bukkit/util/CachedServerIcon.java b/src/main/java/org/bukkit/util/CachedServerIcon.java +index 5ca863b..0480470 100644 +--- a/src/main/java/org/bukkit/util/CachedServerIcon.java ++++ b/src/main/java/org/bukkit/util/CachedServerIcon.java +@@ -12,4 +12,6 @@ import org.bukkit.event.server.ServerListPingEvent; + * @see Server#loadServerIcon(java.io.File) + * @see ServerListPingEvent#setServerIcon(CachedServerIcon) + */ +-public interface CachedServerIcon {} ++public interface CachedServerIcon { ++ public String getData(); // Spigot ++} +diff --git a/src/main/java/org/spigotmc/CustomTimingsHandler.java b/src/main/java/org/spigotmc/CustomTimingsHandler.java +index 8d98297..ed89acc 100644 +--- a/src/main/java/org/spigotmc/CustomTimingsHandler.java ++++ b/src/main/java/org/spigotmc/CustomTimingsHandler.java +@@ -1,165 +1,76 @@ ++/* ++ * 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 org.spigotmc; + +-import org.bukkit.command.defaults.TimingsCommand; +-import org.bukkit.event.HandlerList; ++import org.bukkit.Bukkit; ++import org.bukkit.plugin.AuthorNagException; + import org.bukkit.plugin.Plugin; +-import org.bukkit.plugin.RegisteredListener; +-import org.bukkit.plugin.TimedRegisteredListener; +-import java.io.PrintStream; +-import java.util.Collection; +-import java.util.HashSet; +-import java.util.List; +-import java.util.Queue; +-import java.util.concurrent.ConcurrentLinkedQueue; ++import org.spigotmc.timings.NullTimingHandler; ++import org.spigotmc.timings.Timing; ++import org.spigotmc.timings.Timings; ++import org.spigotmc.timings.TimingsManager; ++import sun.reflect.Reflection; + +-import org.bukkit.Bukkit; +-import org.bukkit.World; ++import java.lang.reflect.Method; ++import java.util.logging.Level; + + /** +- * Provides custom timing sections for /timings merged. ++ * This is here for legacy purposes incase any plugin used it. ++ * ++ * If you use this, migrate ASAP as this will be removed in the future! ++ * ++ * @deprecated ++ * @see org.spigotmc.timings.Timings#of + */ +-public class CustomTimingsHandler +-{ ++@Deprecated ++public final class CustomTimingsHandler { ++ private final Timing handler; + +- private static Queue HANDLERS = new ConcurrentLinkedQueue(); +- /*========================================================================*/ +- private final String name; +- private final CustomTimingsHandler parent; +- private long count = 0; +- private long start = 0; +- private long timingDepth = 0; +- private long totalTime = 0; +- private long curTickTotal = 0; +- private long violations = 0; ++ public CustomTimingsHandler(String name) { ++ Timing timing; + +- public CustomTimingsHandler(String name) +- { +- this( name, null ); +- } ++ Plugin plugin = null; ++ try { ++ plugin = TimingsManager.getPluginByClassloader(Reflection.getCallerClass(2)); ++ } catch (Exception ignored) {} + +- public CustomTimingsHandler(String name, CustomTimingsHandler parent) +- { +- this.name = name; +- this.parent = parent; +- HANDLERS.add( this ); +- } +- +- /** +- * Prints the timings and extra data to the given stream. +- * +- * @param printStream +- */ +- public static void printTimings(PrintStream printStream) +- { +- printStream.println( "Minecraft" ); +- for ( CustomTimingsHandler timings : HANDLERS ) +- { +- long time = timings.totalTime; +- long count = timings.count; +- if ( count == 0 ) +- { +- continue; ++ new AuthorNagException("Deprecated use of CustomTimingsHandler. Please Switch to Timings.of ASAP").printStackTrace(); ++ if (plugin != null) { ++ timing = Timings.of(plugin, "(Deprecated API) " + name); ++ } else { ++ try { ++ final Method ofSafe = TimingsManager.class.getMethod("getHandler", String.class, String.class, Timing.class, boolean.class); ++ timing = (Timing) ofSafe.invoke("Minecraft", "(Deprecated API) " + name, null, true); ++ } catch (Exception e) { ++ Bukkit.getLogger().log(Level.SEVERE, "This handler could not be registered"); ++ timing = Timings.NULL_HANDLER; + } +- long avg = time / count; +- +- printStream.println( " " + timings.name + " Time: " + time + " Count: " + count + " Avg: " + avg + " Violations: " + timings.violations ); + } +- printStream.println( "# Version " + Bukkit.getVersion() ); +- int entities = 0; +- int livingEntities = 0; +- for ( World world : Bukkit.getWorlds() ) +- { +- entities += world.getEntities().size(); +- livingEntities += world.getLivingEntities().size(); +- } +- printStream.println( "# Entities " + entities ); +- printStream.println( "# LivingEntities " + livingEntities ); ++ handler = timing; + } + +- /** +- * Resets all timings. +- */ +- public static void reload() +- { +- if ( Bukkit.getPluginManager().useTimings() ) +- { +- for ( CustomTimingsHandler timings : HANDLERS ) +- { +- timings.reset(); +- } +- } +- TimingsCommand.timingStart = System.nanoTime(); +- } ++ public void startTiming() { handler.startTiming(); } ++ public void stopTiming() { handler.stopTiming(); } + +- /** +- * Ticked every tick by CraftBukkit to count the number of times a timer +- * caused TPS loss. +- */ +- public static void tick() +- { +- if ( Bukkit.getPluginManager().useTimings() ) +- { +- for ( CustomTimingsHandler timings : HANDLERS ) +- { +- if ( timings.curTickTotal > 50000000 ) +- { +- timings.violations += Math.ceil( timings.curTickTotal / 50000000 ); +- } +- timings.curTickTotal = 0; +- timings.timingDepth = 0; // incase reset messes this up +- } +- } +- } +- +- /** +- * Starts timing to track a section of code. +- */ +- public void startTiming() +- { +- // If second condtion fails we are already timing +- if ( Bukkit.getPluginManager().useTimings() && ++timingDepth == 1 ) +- { +- start = System.nanoTime(); +- if ( parent != null && ++parent.timingDepth == 1 ) +- { +- parent.start = start; +- } +- } +- } +- +- /** +- * Stops timing a section of code. +- */ +- public void stopTiming() +- { +- if ( Bukkit.getPluginManager().useTimings() ) +- { +- if ( --timingDepth != 0 || start == 0 ) +- { +- return; +- } +- long diff = System.nanoTime() - start; +- totalTime += diff; +- curTickTotal += diff; +- count++; +- start = 0; +- if ( parent != null ) +- { +- parent.stopTiming(); +- } +- } +- } +- +- /** +- * Reset this timer, setting all values to zero. +- */ +- public void reset() +- { +- count = 0; +- violations = 0; +- curTickTotal = 0; +- totalTime = 0; +- start = 0; +- timingDepth = 0; +- } + } +diff --git a/src/main/java/org/spigotmc/timings/FullServerTickHandler.java b/src/main/java/org/spigotmc/timings/FullServerTickHandler.java +new file mode 100644 +index 0000000..1d3926f +--- /dev/null ++++ b/src/main/java/org/spigotmc/timings/FullServerTickHandler.java +@@ -0,0 +1,79 @@ ++package org.spigotmc.timings; ++ ++import static org.spigotmc.timings.TimingsManager.*; ++ ++public class FullServerTickHandler extends TimingHandler { ++ static final TimingIdentifier IDENTITY = new TimingIdentifier("Minecraft", "Full Server Tick", null, false); ++ final TimingData minuteData; ++ double avgFreeMemory = -1D; ++ double avgUsedMemory = -1D; ++ FullServerTickHandler() { ++ super(IDENTITY); ++ minuteData = new TimingData(id); ++ ++ TIMING_MAP.put(IDENTITY, this); ++ } ++ ++ @Override ++ public void startTiming() { ++ if (TimingsManager.needsFullReset) { ++ TimingsManager.resetTimings(); ++ } else if (TimingsManager.needsRecheckEnabled) { ++ TimingsManager.recheckEnabled(); ++ } ++ super.startTiming(); ++ } ++ ++ @Override ++ public void stopTiming() { ++ super.stopTiming(); ++ if (!enabled) { ++ return; ++ } ++ if (TimingHistory.timedTicks % 20 == 0) { ++ final Runtime runtime = Runtime.getRuntime(); ++ double usedMemory = runtime.totalMemory() - runtime.freeMemory(); ++ double freeMemory = runtime.maxMemory() - usedMemory; ++ if (this.avgFreeMemory == -1) { ++ this.avgFreeMemory = freeMemory; ++ } else { ++ this.avgFreeMemory = (this.avgFreeMemory * (59 / 60D)) + (freeMemory * (1 / 60D)); ++ } ++ ++ if (this.avgUsedMemory == -1) { ++ this.avgUsedMemory = usedMemory; ++ } else { ++ this.avgUsedMemory = (this.avgUsedMemory * (59 / 60D)) + (usedMemory * (1 / 60D)); ++ } ++ } ++ ++ long start = System.nanoTime(); ++ TimingsManager.tick(); ++ long diff = System.nanoTime() - start; ++ CURRENT = TIMINGS_TICK; ++ TIMINGS_TICK.addDiff(diff); ++ // addDiff for TIMINGS_TICK incremented this, bring it back down to 1 per tick. ++ record.curTickCount--; ++ minuteData.curTickTotal = record.curTickTotal; ++ minuteData.curTickCount = 1; ++ boolean violated = isViolated(); ++ minuteData.processTick(violated); ++ TIMINGS_TICK.processTick(violated); ++ processTick(violated); ++ ++ ++ if (TimingHistory.timedTicks % 1200 == 0) { ++ MINUTE_REPORTS.add(new TimingHistory.MinuteReport()); ++ TimingHistory.resetTicks(false); ++ minuteData.reset(); ++ } ++ if (TimingHistory.timedTicks % Timings.getHistoryInterval() == 0) { ++ TimingsManager.HISTORY.add(new TimingHistory()); ++ TimingsManager.resetTimings(); ++ } ++ } ++ ++ boolean isViolated() { ++ return record.curTickTotal > 50000000; ++ } ++} +diff --git a/src/main/java/org/spigotmc/timings/NullTimingHandler.java b/src/main/java/org/spigotmc/timings/NullTimingHandler.java +new file mode 100644 +index 0000000..2993971 +--- /dev/null ++++ b/src/main/java/org/spigotmc/timings/NullTimingHandler.java +@@ -0,0 +1,61 @@ ++/* ++ * 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 org.spigotmc.timings; ++ ++public final class NullTimingHandler implements Timing { ++ @Override ++ public void startTiming() { ++ ++ } ++ ++ @Override ++ public void stopTiming() { ++ ++ } ++ ++ @Override ++ public void startTimingIfSync() { ++ ++ } ++ ++ @Override ++ public void stopTimingIfSync() { ++ ++ } ++ ++ @Override ++ public void abort() { ++ ++ } ++ ++ @Override ++ public TimingHandler getTimingHandler() { ++ return null; ++ } ++ ++ @Override ++ public void close() { ++ ++ } ++} +diff --git a/src/main/java/org/spigotmc/timings/TimedEventExecutor.java b/src/main/java/org/spigotmc/timings/TimedEventExecutor.java +new file mode 100644 +index 0000000..a704015 +--- /dev/null ++++ b/src/main/java/org/spigotmc/timings/TimedEventExecutor.java +@@ -0,0 +1,73 @@ ++/* ++ * 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 org.spigotmc.timings; ++ ++import org.bukkit.Bukkit; ++import org.bukkit.event.Event; ++import org.bukkit.event.EventException; ++import org.bukkit.event.Listener; ++import org.bukkit.plugin.EventExecutor; ++import org.bukkit.plugin.Plugin; ++ ++import java.lang.reflect.Method; ++ ++public class TimedEventExecutor implements EventExecutor { ++ ++ private final EventExecutor executor; ++ private final Timing timings; ++ ++ /** ++ * Wraps an event executor and associates a timing handler to it. ++ * ++ * @param executor ++ * @param plugin ++ * @param method ++ * @param eventClass ++ */ ++ public TimedEventExecutor(EventExecutor executor, Plugin plugin, Method method, Class eventClass) { ++ this.executor = executor; ++ String id; ++ ++ if (method == null) { ++ method = executor.getClass().getEnclosingMethod(); ++ } ++ id = method.getDeclaringClass().getName(); ++ ++ final String eventName = eventClass.getSimpleName(); ++ boolean verbose = "BlockPhysicsEvent".equals(eventName) || "Drain".equals(eventName) || "Fill".equals(eventName); ++ this.timings = Timings.ofSafe(plugin.getName(), (verbose ? "## " : "") + ++ "Event: " + id + " (" + eventName + ")", null); ++ } ++ ++ @Override ++ public void execute(Listener listener, Event event) throws EventException { ++ if (event.isAsynchronous() || !Timings.timingsEnabled || !Bukkit.isPrimaryThread()) { ++ executor.execute(listener, event); ++ return; ++ } ++ timings.startTiming(); ++ executor.execute(listener, event); ++ timings.stopTiming(); ++ } ++} +diff --git a/src/main/java/org/spigotmc/timings/Timing.java b/src/main/java/org/spigotmc/timings/Timing.java +new file mode 100644 +index 0000000..817aca8 +--- /dev/null ++++ b/src/main/java/org/spigotmc/timings/Timing.java +@@ -0,0 +1,72 @@ ++/* ++ * 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 org.spigotmc.timings; ++ ++/** ++ * Provides an ability to time sections of code within the Minecraft Server ++ */ ++public interface Timing extends AutoCloseable { ++ /** ++ * Starts timing the execution until {@link #stopTiming()} is called. ++ */ ++ public void startTiming(); ++ ++ /** ++ * Stops timing and records the data. Propagates the data up to group handlers. ++ *

++ * Will automatically be called when this Timing is used with try-with-resources ++ */ ++ public void stopTiming(); ++ ++ /** ++ * Starts timing the execution until {@link #stopTiming()} is called. ++ * ++ * But only if we are on the primary thread. ++ */ ++ public void startTimingIfSync(); ++ ++ /** ++ * Stops timing and records the data. Propagates the data up to group handlers. ++ *

++ * Will automatically be called when this Timing is used with try-with-resources ++ * ++ * But only if we are on the primary thread. ++ */ ++ public void stopTimingIfSync(); ++ ++ /** ++ * Stops timing and disregards current timing data. ++ */ ++ public void abort(); ++ ++ /** ++ * Used internally to get the actual backing Handler in the case of delegated Handlers ++ * ++ * @return TimingHandler ++ */ ++ TimingHandler getTimingHandler(); ++ ++ @Override ++ void close(); ++} +diff --git a/src/main/java/org/spigotmc/timings/TimingData.java b/src/main/java/org/spigotmc/timings/TimingData.java +new file mode 100644 +index 0000000..677b597 +--- /dev/null ++++ b/src/main/java/org/spigotmc/timings/TimingData.java +@@ -0,0 +1,105 @@ ++/* ++ * 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 org.spigotmc.timings; ++ ++import com.google.common.base.Function; ++ ++import java.util.List; ++ ++import static org.spigotmc.util.JSONUtil.toArray; ++ ++/** ++ * Lightweight object for tracking timing data ++ *

++ * This is broken out to reduce memory usage ++ */ ++class TimingData { ++ static Function LOADER = new Function() { ++ @Override ++ public TimingData apply(Integer input) { ++ return new TimingData(input); ++ } ++ }; ++ int id; ++ int count = 0; ++ int lagCount = 0; ++ long totalTime = 0; ++ long lagTotalTime = 0; ++ ++ int curTickCount = 0; ++ int curTickTotal = 0; ++ ++ TimingData(int id) { ++ this.id = id; ++ } ++ ++ TimingData(TimingData data) { ++ this.id = data.id; ++ this.totalTime = data.totalTime; ++ this.lagTotalTime = data.lagTotalTime; ++ this.count = data.count; ++ this.lagCount = data.lagCount; ++ } ++ ++ void add(long diff) { ++ ++curTickCount; ++ curTickTotal += diff; ++ } ++ ++ void processTick(boolean violated) { ++ totalTime += curTickTotal; ++ count += curTickCount; ++ if (violated) { ++ lagTotalTime += curTickTotal; ++ lagCount += curTickCount; ++ } ++ curTickTotal = 0; ++ curTickCount = 0; ++ } ++ ++ void reset() { ++ count = 0; ++ lagCount = 0; ++ curTickTotal = 0; ++ curTickCount = 0; ++ totalTime = 0; ++ lagTotalTime = 0; ++ } ++ ++ protected TimingData clone() { ++ return new TimingData(this); ++ } ++ ++ public List export() { ++ List list = toArray( ++ id, ++ count, ++ totalTime); ++ if (lagCount > 0) { ++ list.add(lagCount); ++ list.add(lagTotalTime); ++ } ++ return list; ++ } ++} +diff --git a/src/main/java/org/spigotmc/timings/TimingHandler.java b/src/main/java/org/spigotmc/timings/TimingHandler.java +new file mode 100644 +index 0000000..c956a90 +--- /dev/null ++++ b/src/main/java/org/spigotmc/timings/TimingHandler.java +@@ -0,0 +1,193 @@ ++/* ++ * 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 org.spigotmc.timings; ++ ++import gnu.trove.map.hash.TIntObjectHashMap; ++import org.bukkit.Bukkit; ++import org.spigotmc.util.LoadingIntMap; ++import org.spigotmc.util.LoadingMap; ++import org.spigotmc.util.MRUMapCache; ++ ++import java.util.Map; ++import java.util.logging.Level; ++ ++class TimingHandler implements Timing { ++ ++ private static int idPool = 1; ++ final int id = idPool++; ++ ++ final String name; ++ final boolean verbose; ++ ++ final TIntObjectHashMap children = new LoadingIntMap(TimingData.LOADER); ++ ++ final TimingData record; ++ final TimingHandler groupHandler; ++ ++ long start = 0; ++ int timingDepth = 0; ++ boolean added; ++ boolean timed; ++ boolean enabled; ++ 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.curTickCount == 0) { ++ timingDepth = 0; ++ start = 0; ++ return; ++ } ++ ++ record.processTick(violated); ++ for (TimingData handler : children.valueCollection()) { ++ handler.processTick(violated); ++ } ++ } ++ ++ @Override ++ public void startTimingIfSync() { ++ if (Bukkit.isPrimaryThread()) { ++ startTiming(); ++ } ++ } ++ ++ @Override ++ public void stopTimingIfSync() { ++ if (Bukkit.isPrimaryThread()) { ++ stopTiming(); ++ } ++ } ++ ++ public void startTiming() { ++ if (enabled && ++timingDepth == 1) { ++ start = System.nanoTime(); ++ parent = TimingsManager.CURRENT; ++ TimingsManager.CURRENT = this; ++ } ++ } ++ ++ public void stopTiming() { ++ if (enabled && --timingDepth == 0 && start != 0) { ++ 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; ++ } ++ } ++ ++ @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; ++ } ++} +diff --git a/src/main/java/org/spigotmc/timings/TimingHistory.java b/src/main/java/org/spigotmc/timings/TimingHistory.java +new file mode 100644 +index 0000000..b99f73c +--- /dev/null ++++ b/src/main/java/org/spigotmc/timings/TimingHistory.java +@@ -0,0 +1,276 @@ ++/* ++ * 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 org.spigotmc.timings; ++ ++import com.google.common.base.Function; ++import com.google.common.collect.Sets; ++import org.bukkit.Bukkit; ++import org.bukkit.Chunk; ++import org.bukkit.Material; ++import org.bukkit.World; ++import org.bukkit.block.BlockState; ++import org.bukkit.entity.Entity; ++import org.bukkit.entity.EntityType; ++import org.bukkit.entity.Player; ++import org.spigotmc.util.LoadingMap; ++import org.spigotmc.util.MRUMapCache; ++ ++import java.lang.management.ManagementFactory; ++import java.util.Collection; ++import java.util.EnumMap; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++ ++import static org.spigotmc.timings.TimingsManager.FULL_SERVER_TICK; ++import static org.spigotmc.timings.TimingsManager.MINUTE_REPORTS; ++import static org.spigotmc.util.JSONUtil.*; ++ ++@SuppressWarnings({"deprecation", "SuppressionAnnotation"}) ++public class TimingHistory { ++ public static long lastMinuteTime; ++ public static long timedTicks; ++ public static long playerTicks; ++ public static long entityTicks; ++ public static long tileEntityTicks; ++ public static long activatedEntityTicks; ++ static int worldIdPool = 1; ++ static Map worldMap = LoadingMap.newHashMap(new Function() { ++ @Override ++ public Integer apply(String input) { ++ return worldIdPool++; ++ } ++ }); ++ final long endTime; ++ final long startTime; ++ final long totalTicks; ++ final long totalTime; // Represents all time spent running the server this history ++ final MinuteReport[] minuteReports; ++ ++ final TimingHistoryEntry[] entries; ++ final Set tileEntityTypeSet = Sets.newHashSet(); ++ final Set entityTypeSet = Sets.newHashSet(); ++ final Map worlds; ++ ++ TimingHistory() { ++ this.endTime = System.currentTimeMillis() / 1000; ++ this.startTime = TimingsManager.historyStart / 1000; ++ if (timedTicks % 1200 != 0 || MINUTE_REPORTS.isEmpty()) { ++ this.minuteReports = MINUTE_REPORTS.toArray(new MinuteReport[MINUTE_REPORTS.size() + 1]); ++ this.minuteReports[this.minuteReports.length - 1] = new MinuteReport(); ++ } else { ++ this.minuteReports = MINUTE_REPORTS.toArray(new MinuteReport[MINUTE_REPORTS.size()]); ++ } ++ long ticks = 0; ++ for (MinuteReport mp : this.minuteReports) { ++ ticks += mp.ticksRecord.timed; ++ } ++ this.totalTicks = ticks; ++ this.totalTime = FULL_SERVER_TICK.record.totalTime; ++ this.entries = new TimingHistoryEntry[TimingsManager.HANDLERS.size()]; ++ ++ int i = 0; ++ for (TimingHandler handler : TimingsManager.HANDLERS) { ++ entries[i++] = new TimingHistoryEntry(handler); ++ } ++ ++ final Map entityCounts = MRUMapCache.of(LoadingMap.of( ++ new EnumMap(EntityType.class), Counter.LOADER ++ )); ++ final Map tileEntityCounts = MRUMapCache.of(LoadingMap.of( ++ new EnumMap(Material.class), Counter.LOADER ++ )); ++ // Information about all loaded chunks/entities ++ this.worlds = toObjectMapper(Bukkit.getWorlds(), new Function() { ++ @Override ++ public JSONPair apply(World world) { ++ return pair( ++ worldMap.get(world.getName()), ++ toArrayMapper(world.getLoadedChunks(), new Function() { ++ @Override ++ public Object apply(Chunk chunk) { ++ entityCounts.clear(); ++ tileEntityCounts.clear(); ++ ++ for (Entity entity : chunk.getEntities()) { ++ entityCounts.get(entity.getType()).increment(); ++ } ++ ++ for (BlockState tileEntity : chunk.getTileEntities()) { ++ tileEntityCounts.get(tileEntity.getBlock().getType()).increment(); ++ } ++ ++ if (tileEntityCounts.isEmpty() && entityCounts.isEmpty()) { ++ return null; ++ } ++ return toArray( ++ chunk.getX(), ++ chunk.getZ(), ++ toObjectMapper(entityCounts.entrySet(), ++ new Function, JSONPair>() { ++ @Override ++ public JSONPair apply(Map.Entry entry) { ++ entityTypeSet.add(entry.getKey()); ++ return pair( ++ String.valueOf(entry.getKey().getTypeId()), ++ entry.getValue().count() ++ ); ++ } ++ } ++ ), ++ toObjectMapper(tileEntityCounts.entrySet(), ++ new Function, JSONPair>() { ++ @Override ++ public JSONPair apply(Map.Entry entry) { ++ tileEntityTypeSet.add(entry.getKey()); ++ return pair( ++ String.valueOf(entry.getKey().getId()), ++ entry.getValue().count() ++ ); ++ } ++ } ++ ) ++ ); ++ } ++ }) ++ ); ++ } ++ }); ++ } ++ ++ public static void resetTicks(boolean fullReset) { ++ if (fullReset) { ++ // Non full is simply for 1 minute reports ++ timedTicks = 0; ++ } ++ lastMinuteTime = System.nanoTime(); ++ playerTicks = 0; ++ tileEntityTicks = 0; ++ entityTicks = 0; ++ activatedEntityTicks = 0; ++ } ++ ++ Object export() { ++ return createObject( ++ pair("s", startTime), ++ pair("e", endTime), ++ pair("tk", totalTicks), ++ pair("tm", totalTime), ++ pair("w", worlds), ++ pair("h", toArrayMapper(entries, new Function() { ++ @Override ++ public Object apply(TimingHistoryEntry entry) { ++ TimingData record = entry.data; ++ if (record.count == 0) { ++ return null; ++ } ++ return entry.export(); ++ } ++ })), ++ pair("mp", toArrayMapper(minuteReports, new Function() { ++ @Override ++ public Object apply(MinuteReport input) { ++ return input.export(); ++ } ++ })) ++ ); ++ } ++ ++ static class MinuteReport { ++ final long time = System.currentTimeMillis() / 1000; ++ ++ final TicksRecord ticksRecord = new TicksRecord(); ++ final PingRecord pingRecord = new PingRecord(); ++ final TimingData fst = TimingsManager.FULL_SERVER_TICK.minuteData.clone(); ++ final double tps = 1E9 / ( System.nanoTime() - lastMinuteTime ) * ticksRecord.timed; ++ final double usedMemory = TimingsManager.FULL_SERVER_TICK.avgUsedMemory; ++ final double freeMemory = TimingsManager.FULL_SERVER_TICK.avgFreeMemory; ++ final double loadAvg = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage(); ++ ++ public List export() { ++ return toArray( ++ time, ++ Math.round(tps * 100D) / 100D, ++ Math.round(pingRecord.avg * 100D) / 100D, ++ fst.export(), ++ toArray(ticksRecord.timed, ++ ticksRecord.player, ++ ticksRecord.entity, ++ ticksRecord.activatedEntity, ++ ticksRecord.tileEntity ++ ), ++ usedMemory, ++ freeMemory, ++ loadAvg ++ ); ++ } ++ } ++ ++ static class TicksRecord { ++ final long timed; ++ final long player; ++ final long entity; ++ final long tileEntity; ++ final long activatedEntity; ++ ++ TicksRecord() { ++ timed = timedTicks - (TimingsManager.MINUTE_REPORTS.size() * 1200); ++ player = playerTicks; ++ entity = entityTicks; ++ tileEntity = tileEntityTicks; ++ activatedEntity = activatedEntityTicks; ++ } ++ ++ } ++ ++ static class PingRecord { ++ final double avg; ++ ++ PingRecord() { ++ final Collection onlinePlayers = Bukkit.getOnlinePlayers(); ++ int totalPing = 0; ++ for (Player player : onlinePlayers) { ++ totalPing += player.spigot().getPing(); ++ } ++ avg = onlinePlayers.isEmpty() ? 0 : totalPing / onlinePlayers.size(); ++ } ++ } ++ ++ static class Counter { ++ int count = 0; ++ @SuppressWarnings({"rawtypes", "SuppressionAnnotation"}) ++ static Function LOADER = new LoadingMap.Feeder() { ++ @Override ++ public Counter apply() { ++ return new Counter(); ++ } ++ }; ++ public int increment() { ++ return ++count; ++ } ++ public int count() { ++ return count; ++ } ++ } ++} +diff --git a/src/main/java/org/spigotmc/timings/TimingHistoryEntry.java b/src/main/java/org/spigotmc/timings/TimingHistoryEntry.java +new file mode 100644 +index 0000000..2b37cbe +--- /dev/null ++++ b/src/main/java/org/spigotmc/timings/TimingHistoryEntry.java +@@ -0,0 +1,59 @@ ++/* ++ * 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 org.spigotmc.timings; ++ ++import com.google.common.base.Function; ++ ++import java.util.List; ++ ++import static org.spigotmc.util.JSONUtil.toArrayMapper; ++ ++class TimingHistoryEntry { ++ final TimingData data; ++ final TimingData[] children; ++ ++ TimingHistoryEntry(TimingHandler handler) { ++ this.data = handler.record.clone(); ++ children = new TimingData[handler.children.size()]; ++ int i = 0; ++ for (TimingData child : handler.children.valueCollection()) { ++ children[i++] = child.clone(); ++ } ++ } ++ ++ List export() { ++ List result = data.export(); ++ if (children.length > 0) { ++ result.add( ++ toArrayMapper(children, new Function() { ++ @Override ++ public Object apply(TimingData child) { ++ return child.export(); ++ } ++ }) ++ ); ++ } ++ return result; ++ } ++} +diff --git a/src/main/java/org/spigotmc/timings/TimingIdentifier.java b/src/main/java/org/spigotmc/timings/TimingIdentifier.java +new file mode 100644 +index 0000000..69ed2b0 +--- /dev/null ++++ b/src/main/java/org/spigotmc/timings/TimingIdentifier.java +@@ -0,0 +1,102 @@ ++/* ++ * 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 org.spigotmc.timings; ++ ++import com.google.common.base.Function; ++import org.spigotmc.util.LoadingMap; ++import org.spigotmc.util.MRUMapCache; ++ ++import java.util.ArrayDeque; ++import java.util.Map; ++ ++/** ++ * Used as a basis for fast HashMap key comparisons for the Timing Map. ++ *

++ * This class uses interned strings giving us the ability to do an identity check instead of equals() on the strings ++ */ ++final class TimingIdentifier { ++ /** ++ * Holds all groups. Autoloads on request for a group by name. ++ */ ++ static final Map GROUP_MAP = MRUMapCache.of( ++ LoadingMap.newIdentityHashMap(new Function() { ++ @Override ++ public TimingGroup apply(String group) { ++ return new TimingGroup(group); ++ } ++ }, 64) ++ ); ++ static final TimingGroup DEFAULT_GROUP = getGroup("Minecraft"); ++ final String group; ++ final String name; ++ final TimingHandler groupHandler; ++ final boolean protect; ++ private final int hashCode; ++ ++ TimingIdentifier(String group, String name, Timing groupHandler, boolean protect) { ++ this.group = group != null ? group.intern() : DEFAULT_GROUP.name; ++ this.name = name.intern(); ++ this.groupHandler = groupHandler != null ? groupHandler.getTimingHandler() : null; ++ this.protect = protect; ++ this.hashCode = (31 * this.group.hashCode()) + this.name.hashCode(); ++ } ++ ++ static TimingGroup getGroup(String groupName) { ++ if (groupName == null) { ++ return DEFAULT_GROUP; ++ } ++ ++ return GROUP_MAP.get(groupName.intern()); ++ } ++ ++ // We are using .intern() on the strings so it is guaranteed to be an identity comparison. ++ @SuppressWarnings("StringEquality") ++ @Override ++ public boolean equals(Object o) { ++ if (o == null) { ++ return false; ++ } ++ ++ TimingIdentifier that = (TimingIdentifier) o; ++ return group == that.group && name == that.name; ++ } ++ ++ @Override ++ public int hashCode() { ++ return hashCode; ++ } ++ ++ static class TimingGroup { ++ ++ private static int idPool = 1; ++ final int id = idPool++; ++ ++ final String name; ++ ArrayDeque handlers = new ArrayDeque(64); ++ ++ private TimingGroup(String name) { ++ this.name = name; ++ } ++ } ++} +diff --git a/src/main/java/org/spigotmc/timings/Timings.java b/src/main/java/org/spigotmc/timings/Timings.java +new file mode 100644 +index 0000000..a7218aa +--- /dev/null ++++ b/src/main/java/org/spigotmc/timings/Timings.java +@@ -0,0 +1,273 @@ ++/* ++ * 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 org.spigotmc.timings; ++ ++import com.google.common.base.Preconditions; ++import com.google.common.collect.EvictingQueue; ++import org.bukkit.Bukkit; ++import org.bukkit.command.CommandSender; ++import org.bukkit.plugin.Plugin; ++ ++import java.util.Queue; ++import java.util.logging.Level; ++ ++@SuppressWarnings("UnusedDeclaration") ++public final class Timings { ++ ++ private static final int MAX_HISTORY_FRAMES = 12; ++ public static final Timing NULL_HANDLER = new NullTimingHandler(); ++ static boolean timingsEnabled = false; ++ static boolean verboseEnabled = false; ++ private static int historyInterval = -1; ++ private static int historyLength = -1; ++ ++ private Timings() {} ++ ++ /** ++ * Returns a Timing for a plugin corresponding to a name. ++ * ++ * @param plugin Plugin to own the Timing ++ * @param name Name of Timing ++ * @return Handler ++ */ ++ public static Timing of(Plugin plugin, String name) { ++ Timing pluginHandler = null; ++ if (plugin != null) { ++ pluginHandler = ofSafe(plugin.getName(), "Combined Total", TimingsManager.PLUGIN_GROUP_HANDLER); ++ } ++ return of(plugin, name, pluginHandler); ++ } ++ ++ /** ++ * Returns a handler that has a groupHandler timer handler. Parent timers should not have their ++ * start/stop methods called directly, as the children will call it for you. ++ *

++ * Parent Timers are used to group multiple subsections togethers and get a summary of them combined ++ * Parent Handler can not be changed after first call ++ * ++ * @param plugin Plugin to own the Timing ++ * @param name Name of Timing ++ * @param groupHandler Parent handler to mirror .start/stop calls to ++ * @return Timing Handler ++ */ ++ public static Timing of(Plugin plugin, String name, Timing groupHandler) { ++ Preconditions.checkNotNull(plugin, "Plugin can not be null"); ++ return TimingsManager.getHandler(plugin.getName(), name, groupHandler, true); ++ } ++ ++ /** ++ * Returns a Timing object after starting it, useful for Java7 try-with-resources. ++ * ++ * try (Timing ignored = Timings.ofStart(plugin, someName)) { ++ * // timed section ++ * } ++ * ++ * @param plugin Plugin to own the Timing ++ * @param name Name of Timing ++ * @return Timing Handler ++ */ ++ public static Timing ofStart(Plugin plugin, String name) { ++ return ofStart(plugin, name, null); ++ } ++ ++ /** ++ * Returns a Timing object after starting it, useful for Java7 try-with-resources. ++ * ++ * try (Timing ignored = Timings.ofStart(plugin, someName, groupHandler)) { ++ * // timed section ++ * } ++ * ++ * @param plugin Plugin to own the Timing ++ * @param name Name of Timing ++ * @param groupHandler Parent handler to mirror .start/stop calls to ++ * @return Timing Handler ++ */ ++ public static Timing ofStart(Plugin plugin, String name, Timing groupHandler) { ++ Timing timing = of(plugin, name, groupHandler); ++ timing.startTimingIfSync(); ++ return timing; ++ } ++ ++ /** ++ * Gets whether or not the Spigot Timings system is enabled ++ * ++ * @return Enabled or not ++ */ ++ public static boolean isTimingsEnabled() { ++ return timingsEnabled; ++ } ++ ++ /** ++ * Sets whether or not the Spigot Timings system should be enabled ++ *

++ * Calling this will reset timing data. ++ * ++ * @param enabled Should timings be reported ++ */ ++ public static void setTimingsEnabled(boolean enabled) { ++ timingsEnabled = enabled; ++ reset(); ++ } ++ ++ /** ++ * Gets whether or not the Verbose level of timings is enabled. ++ *

++ * When Verbose is disabled, high-frequency timings will not be available ++ * ++ * @return Enabled or not ++ */ ++ public static boolean isVerboseTimingsEnabled() { ++ return timingsEnabled; ++ } ++ ++ /** ++ * Sets whether or not the Timings should monitor at Verbose level. ++ *

++ * When Verbose is disabled, high-frequency timings will not be available. ++ * Calling this will reset timing data. ++ * ++ * @param enabled Should high-frequency timings be reported ++ */ ++ public static void setVerboseTimingsEnabled(boolean enabled) { ++ verboseEnabled = enabled; ++ TimingsManager.needsRecheckEnabled = true; ++ } ++ ++ /** ++ * Gets the interval between Timing History report generation. ++ *

++ * Defaults to 5 minutes (6000 ticks) ++ * ++ * @return Interval in ticks ++ */ ++ public static int getHistoryInterval() { ++ return historyInterval; ++ } ++ ++ /** ++ * Sets the interval between Timing History report generations. ++ *

++ * Defaults to 5 minutes (6000 ticks) ++ * ++ * This will recheck your history length, so lowering this value will lower your ++ * history length if you need more than 60 history windows. ++ * ++ * @param interval Interval in ticks ++ */ ++ public static void setHistoryInterval(int interval) { ++ historyInterval = Math.max(20*60, interval); ++ // Recheck the history length with the new Interval ++ if (historyLength != -1) { ++ setHistoryLength(historyLength); ++ } ++ } ++ ++ /** ++ * Gets how long in ticks Timings history is kept for the server. ++ * ++ * Defaults to 1 hour (72000 ticks) ++ * ++ * @return Duration in Ticks ++ */ ++ public static int getHistoryLength() { ++ return historyLength; ++ } ++ ++ /** ++ * Sets how long Timing History reports are kept for the server. ++ * ++ * Defaults to 1 hours(72000 ticks) ++ * ++ * This value is capped at a maximum of getHistoryInterval() * MAX_HISTORY_FRAMES (12) ++ * ++ * Will not reset Timing Data but may truncate old history if the new length is less than old length. ++ * ++ * @param length Duration in ticks ++ */ ++ public static void setHistoryLength(int length) { ++ // Cap at 12 History Frames, 1 hour at 5 minute frames. ++ int maxLength = historyInterval * MAX_HISTORY_FRAMES; ++ // For special cases of servers with special permission to bypass the max. ++ // This max helps keep data file sizes reasonable for processing on Aikar's Timing parser side. ++ // Setting this will not help you bypass the max unless Aikar has added an exception on the API side. ++ if (System.getProperty("timings.bypassMax") != null) { ++ maxLength = Integer.MAX_VALUE; ++ } ++ historyLength = Math.max(Math.min(maxLength, length), historyInterval); ++ Queue oldQueue = TimingsManager.HISTORY; ++ int frames = (getHistoryLength() / getHistoryInterval()); ++ if (length > maxLength) { ++ Bukkit.getLogger().log(Level.WARNING, "Timings Length too high. Requested " + length + ", max is " + maxLength + ". To get longer history, you must increase your interval. Set Interval to " + Math.ceil(length / MAX_HISTORY_FRAMES) + " to achieve this length."); ++ } ++ TimingsManager.HISTORY = EvictingQueue.create(frames); ++ TimingsManager.HISTORY.addAll(oldQueue); ++ } ++ ++ /** ++ * Resets all Timing Data ++ */ ++ public static void reset() { ++ TimingsManager.reset(); ++ } ++ ++ /** ++ * Generates a report and sends it to the specified command sender. ++ * ++ * If sender is null, ConsoleCommandSender will be used. ++ * @param sender ++ */ ++ public static void generateReport(CommandSender sender) { ++ if (sender == null) { ++ sender = Bukkit.getConsoleSender(); ++ } ++ TimingsExport.reportTimings(sender); ++ } ++ ++ /* ++ ================= ++ Protected API: These are for internal use only in Bukkit/CraftBukkit ++ These do not have isPrimaryThread() checks in the startTiming/stopTiming ++ ================= ++ */ ++ ++ static TimingHandler ofSafe(String name) { ++ return ofSafe(null, name, null); ++ } ++ ++ static Timing ofSafe(Plugin plugin, String name) { ++ Timing pluginHandler = null; ++ if (plugin != null) { ++ pluginHandler = ofSafe(plugin.getName(), "Combined Total", TimingsManager.PLUGIN_GROUP_HANDLER); ++ } ++ return ofSafe(plugin != null ? plugin.getName() : "Minecraft - Invalid Plugin", name, pluginHandler); ++ } ++ ++ static TimingHandler ofSafe(String name, Timing groupHandler) { ++ return ofSafe(null, name, groupHandler); ++ } ++ ++ static TimingHandler ofSafe(String groupName, String name, Timing groupHandler) { ++ return TimingsManager.getHandler(groupName, name, groupHandler, false); ++ } ++} +diff --git a/src/main/java/org/spigotmc/timings/TimingsCommand.java b/src/main/java/org/spigotmc/timings/TimingsCommand.java +new file mode 100644 +index 0000000..aa1867f +--- /dev/null ++++ b/src/main/java/org/spigotmc/timings/TimingsCommand.java +@@ -0,0 +1,110 @@ ++/* ++ * 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 org.spigotmc.timings; ++ ++import com.google.common.collect.ImmutableList; ++import org.apache.commons.lang.Validate; ++import org.bukkit.ChatColor; ++import org.bukkit.command.CommandSender; ++import org.bukkit.command.defaults.BukkitCommand; ++import org.bukkit.util.StringUtil; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++ ++public class TimingsCommand extends BukkitCommand { ++ public static final List TIMINGS_SUBCOMMANDS = ImmutableList.of("report", "reset", "on", "off", "paste", "verbon", "verboff"); ++ ++ public TimingsCommand(String name) { ++ super(name); ++ this.description = "Manages Spigot Timings data to see performance of the server."; ++ this.usageMessage = "/timings "; ++ this.setPermission("bukkit.command.timings"); ++ } ++ ++ @Override ++ public boolean execute(CommandSender sender, String currentAlias, String[] args) { ++ if (!testPermission(sender)) { ++ return true; ++ } ++ if (args.length < 1) { ++ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); ++ return true; ++ } ++ final String arg = args[0]; ++ if ("on".equalsIgnoreCase(arg)) { ++ Timings.setTimingsEnabled(true); ++ sender.sendMessage("Enabled Timings & Reset"); ++ return true; ++ } else if ("off".equalsIgnoreCase(arg)) { ++ Timings.setTimingsEnabled(false); ++ sender.sendMessage("Disabled Timings"); ++ return true; ++ } ++ ++ if (!Timings.isTimingsEnabled()) { ++ sender.sendMessage("Please enable timings by typing /timings on"); ++ return true; ++ } ++ if ("verbon".equalsIgnoreCase(arg)) { ++ Timings.setVerboseTimingsEnabled(true); ++ sender.sendMessage("Enabled Verbose Timings"); ++ return true; ++ } else if ("verboff".equalsIgnoreCase(arg)) { ++ Timings.setVerboseTimingsEnabled(false); ++ sender.sendMessage("Disabled Verbose Timings"); ++ return true; ++ } else if ("reset".equalsIgnoreCase(arg)) { ++ TimingsManager.reset(); ++ sender.sendMessage("Timings reset"); ++ } else if ("cost".equals(arg)) { ++ sender.sendMessage("Timings cost: " + TimingsExport.getCost()); ++ } else if ( ++ "paste".equalsIgnoreCase(arg) || ++ "report".equalsIgnoreCase(arg) || ++ "get".equalsIgnoreCase(arg) || ++ "merged".equalsIgnoreCase(arg) || ++ "separate".equalsIgnoreCase(arg) ++ ) { ++ TimingsExport.reportTimings(sender); ++ } else { ++ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); ++ } ++ return true; ++ } ++ ++ @Override ++ public List tabComplete(CommandSender sender, String alias, String[] args) { ++ Validate.notNull(sender, "Sender cannot be null"); ++ Validate.notNull(args, "Arguments cannot be null"); ++ Validate.notNull(alias, "Alias cannot be null"); ++ ++ if (args.length == 1) { ++ return StringUtil.copyPartialMatches(args[0], TIMINGS_SUBCOMMANDS, ++ new ArrayList(TIMINGS_SUBCOMMANDS.size())); ++ } ++ return ImmutableList.of(); ++ } ++} +diff --git a/src/main/java/org/spigotmc/timings/TimingsExport.java b/src/main/java/org/spigotmc/timings/TimingsExport.java +new file mode 100644 +index 0000000..9dac535 +--- /dev/null ++++ b/src/main/java/org/spigotmc/timings/TimingsExport.java +@@ -0,0 +1,373 @@ ++/* ++ * 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 org.spigotmc.timings; ++ ++import com.google.common.base.Function; ++import com.google.common.collect.Sets; ++import org.apache.commons.lang.StringUtils; ++import org.bukkit.Bukkit; ++import org.bukkit.ChatColor; ++import org.bukkit.Material; ++import org.bukkit.command.CommandSender; ++import org.bukkit.command.ConsoleCommandSender; ++import org.bukkit.command.RemoteConsoleCommandSender; ++import org.bukkit.configuration.ConfigurationSection; ++import org.bukkit.configuration.MemorySection; ++import org.bukkit.entity.EntityType; ++import org.bukkit.plugin.Plugin; ++import org.json.simple.JSONObject; ++import org.json.simple.JSONValue; ++ ++import java.io.ByteArrayOutputStream; ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.OutputStream; ++import java.lang.management.GarbageCollectorMXBean; ++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 org.spigotmc.timings.TimingsManager.HISTORY; ++import static org.spigotmc.util.JSONUtil.*; ++ ++@SuppressWarnings({"rawtypes", "SuppressionAnnotation"}) ++class TimingsExport extends Thread { ++ ++ private final CommandSender sender; ++ private final Map out; ++ private final TimingHistory[] history; ++ ++ TimingsExport(CommandSender sender, Map out, TimingHistory[] history) { ++ super("Timings paste thread"); ++ this.sender = sender; ++ this.out = out; ++ this.history = history; ++ } ++ ++ ++ /** ++ * Builds an XML report of the timings to be uploaded for parsing. ++ * ++ * @param sender Who to report to ++ */ ++ static void reportTimings(CommandSender sender) { ++ 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("sampletime", (System.currentTimeMillis() - TimingsManager.timingStart) / 1000) ++ ); ++ if (!TimingsManager.privacy) { ++ appendObjectData(parent, ++ pair("server", Bukkit.getServerName()), ++ pair("motd", Bukkit.getServer().getMotd()), ++ pair("online-mode", Bukkit.getServer().getOnlineMode()), ++ pair("icon", Bukkit.getServer().getServerIcon().getData()) ++ ); ++ } ++ ++ final Runtime runtime = Runtime.getRuntime(); ++ RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean(); ++ ++ parent.put("system", createObject( ++ pair("timingcost", getCost()), ++ pair("name", System.getProperty("os.name")), ++ pair("version", System.getProperty("os.version")), ++ pair("jvmversion", System.getProperty("java.version")), ++ pair("arch", System.getProperty("os.arch")), ++ pair("maxmem", runtime.maxMemory()), ++ pair("cpu", runtime.availableProcessors()), ++ pair("runtime", ManagementFactory.getRuntimeMXBean().getUptime()), ++ pair("flags", StringUtils.join(runtimeBean.getInputArguments(), " ")), ++ pair("gc", toObjectMapper(ManagementFactory.getGarbageCollectorMXBeans(), new Function() { ++ @Override ++ public JSONPair apply(GarbageCollectorMXBean input) { ++ return pair(input.getName(), toArray(input.getCollectionCount(), input.getCollectionTime())); ++ } ++ })) ++ ) ++ ); ++ ++ Set tileEntityTypeSet = Sets.newHashSet(); ++ Set 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(); ++ for (TimingIdentifier.TimingGroup group : TimingIdentifier.GROUP_MAP.values()) { ++ for (TimingHandler id : group.handlers) { ++ if (!id.timed && !id.isSpecial()) { ++ continue; ++ } ++ handlers.put(id.id, toArray( ++ group.id, ++ id.name ++ )); ++ } ++ } ++ ++ parent.put("idmap", createObject( ++ pair("groups", toObjectMapper( ++ TimingIdentifier.GROUP_MAP.values(), new Function() { ++ @Override ++ public JSONPair apply(TimingIdentifier.TimingGroup group) { ++ return pair(group.id, group.name); ++ } ++ })), ++ pair("handlers", handlers), ++ pair("worlds", toObjectMapper(TimingHistory.worldMap.entrySet(), new Function, JSONPair>() { ++ @Override ++ public JSONPair apply(Map.Entry input) { ++ return pair(input.getValue(), input.getKey()); ++ } ++ })), ++ pair("tileentity", ++ toObjectMapper(tileEntityTypeSet, new Function() { ++ @Override ++ public JSONPair apply(Material input) { ++ return pair(input.getId(), input.name()); ++ } ++ })), ++ pair("entity", ++ toObjectMapper(entityTypeSet, new Function() { ++ @Override ++ public JSONPair apply(EntityType input) { ++ return pair(input.getTypeId(), input.name()); ++ } ++ })) ++ )); ++ ++ // Information about loaded plugins ++ ++ parent.put("plugins", toObjectMapper(Bukkit.getPluginManager().getPlugins(), ++ new Function() { ++ @Override ++ public JSONPair apply(Plugin plugin) { ++ return 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("paperspigot", mapAsJSON(Bukkit.spigot().getPaperSpigotConfig(), null)) ++ )); ++ ++ new TimingsExport(sender, parent, history).start(); ++ } ++ ++ static long getCost() { ++ // Benchmark the users System.nanotime() for cost basis ++ int passes = 500000; ++ 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)) { ++ 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 v = (Iterable) val; ++ return toArrayMapper(v, new Function() { ++ @Override ++ public Object apply(Object input) { ++ return valAsJSON(input, parentKey); ++ } ++ }); ++ } else { ++ return val.toString(); ++ } ++ } else { ++ return mapAsJSON((ConfigurationSection) val, parentKey); ++ } ++ } ++ ++ @SuppressWarnings("CallToThreadRun") ++ @Override ++ public synchronized void start() { ++ if (sender instanceof RemoteConsoleCommandSender) { ++ sender.sendMessage(ChatColor.RED + "Warning: Timings report done over RCON will cause lag spikes."); ++ sender.sendMessage(ChatColor.RED + "You should use " + ChatColor.YELLOW + ++ "/timings report" + ChatColor.RED + " in game or console."); ++ run(); ++ } else { ++ super.start(); ++ } ++ } ++ ++ @Override ++ public void run() { ++ sender.sendMessage(ChatColor.GREEN + "Preparing Timings Report..."); ++ ++ ++ out.put("data", toArrayMapper(history, new Function() { ++ @Override ++ public Object apply(TimingHistory input) { ++ return input.export(); ++ } ++ })); ++ ++ ++ String response = null; ++ try { ++ HttpURLConnection con = (HttpURLConnection) new URL("http://timings.aikar.co/post").openConnection(); ++ con.setDoOutput(true); ++ con.setRequestProperty("User-Agent", "Spigot/" + Bukkit.getServerName() + "/" + InetAddress.getLocalHost().getHostName()); ++ 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) { ++ sender.sendMessage( ++ ChatColor.RED + "Upload Error: " + con.getResponseCode() + ": " + con.getResponseMessage()); ++ sender.sendMessage(ChatColor.RED + "Check your logs for more information"); ++ if (response != null) { ++ Bukkit.getLogger().log(Level.SEVERE, response); ++ } ++ return; ++ } ++ ++ String location = con.getHeaderField("Location"); ++ sender.sendMessage(ChatColor.GREEN + "View Timings Report: " + location); ++ if (!(sender instanceof ConsoleCommandSender)) { ++ Bukkit.getLogger().log(Level.INFO, "View Timings Report: " + location); ++ } ++ ++ if (response != null && !response.isEmpty()) { ++ Bukkit.getLogger().log(Level.INFO, "Timing Response: " + response); ++ } ++ } catch (IOException ex) { ++ sender.sendMessage(ChatColor.RED + "Error uploading timings, check your logs for more information"); ++ if (response != null) { ++ Bukkit.getLogger().log(Level.SEVERE, response); ++ } ++ Bukkit.getLogger().log(Level.SEVERE, "Could not paste timings", ex); ++ } ++ } ++ ++ 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) { ++ sender.sendMessage(ChatColor.RED + "Error uploading timings, check your logs for more information"); ++ Bukkit.getLogger().log(Level.WARNING, con.getResponseMessage(), ex); ++ return null; ++ } finally { ++ if (is != null) { ++ is.close(); ++ } ++ } ++ } ++} +diff --git a/src/main/java/org/spigotmc/timings/TimingsManager.java b/src/main/java/org/spigotmc/timings/TimingsManager.java +new file mode 100644 +index 0000000..f6b31cb +--- /dev/null ++++ b/src/main/java/org/spigotmc/timings/TimingsManager.java +@@ -0,0 +1,194 @@ ++/* ++ * 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 org.spigotmc.timings; ++ ++import com.google.common.base.Function; ++import com.google.common.collect.EvictingQueue; ++import org.bukkit.Bukkit; ++import org.bukkit.Server; ++import org.bukkit.command.Command; ++import org.bukkit.plugin.Plugin; ++import org.bukkit.plugin.java.PluginClassLoader; ++import org.spigotmc.util.LoadingMap; ++ ++import java.util.ArrayDeque; ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.List; ++import java.util.Map; ++import java.util.logging.Level; ++ ++public final class TimingsManager { ++ static final Map TIMING_MAP = ++ Collections.synchronizedMap(LoadingMap.newHashMap( ++ new Function() { ++ @Override ++ public TimingHandler apply(TimingIdentifier id) { ++ return (id.protect ? ++ new UnsafeTimingHandler(id) : ++ new TimingHandler(id) ++ ); ++ } ++ }, ++ 256, .5F ++ )); ++ public static final FullServerTickHandler FULL_SERVER_TICK = new FullServerTickHandler(); ++ public static final TimingHandler TIMINGS_TICK = Timings.ofSafe("Timings Tick", FULL_SERVER_TICK); ++ public static final Timing PLUGIN_GROUP_HANDLER = Timings.ofSafe("Plugins"); ++ public static List hiddenConfigs = new ArrayList(); ++ public static boolean privacy = false; ++ ++ static final Collection HANDLERS = new ArrayDeque(); ++ static final ArrayDeque MINUTE_REPORTS = new ArrayDeque(); ++ ++ static EvictingQueue HISTORY = EvictingQueue.create(12); ++ static TimingHandler CURRENT; ++ static long timingStart = 0; ++ static long historyStart = 0; ++ static boolean needsFullReset = false; ++ static boolean needsRecheckEnabled = false; ++ ++ private TimingsManager() {} ++ ++ /** ++ * Resets all timing data on the next tick ++ */ ++ static void reset() { ++ needsFullReset = true; ++ } ++ ++ /** ++ * Ticked every tick by CraftBukkit to count the number of times a timer ++ * caused TPS loss. ++ */ ++ static void tick() { ++ if (Timings.timingsEnabled) { ++ boolean violated = FULL_SERVER_TICK.isViolated(); ++ ++ for (TimingHandler handler : HANDLERS) { ++ if (handler.isSpecial()) { ++ // We manually call this ++ continue; ++ } ++ handler.processTick(violated); ++ } ++ ++ TimingHistory.playerTicks += Bukkit.getOnlinePlayers().size(); ++ TimingHistory.timedTicks++; ++ // Generate TPS/Ping/Tick reports every minute ++ } ++ } ++ static void stopServer() { ++ Timings.timingsEnabled = false; ++ recheckEnabled(); ++ } ++ static void recheckEnabled() { ++ synchronized (TIMING_MAP) { ++ for (TimingHandler timings : TIMING_MAP.values()) { ++ timings.checkEnabled(); ++ } ++ } ++ needsRecheckEnabled = false; ++ } ++ static void resetTimings() { ++ if (needsFullReset) { ++ // Full resets need to re-check every handlers enabled state ++ // Timing map can be modified from async so we must sync on it. ++ synchronized (TIMING_MAP) { ++ for (TimingHandler timings : TIMING_MAP.values()) { ++ timings.reset(true); ++ } ++ } ++ Bukkit.getLogger().log(Level.INFO, "Timings Reset"); ++ HISTORY.clear(); ++ needsFullReset = false; ++ needsRecheckEnabled = false; ++ timingStart = System.currentTimeMillis(); ++ } else { ++ // Soft resets only need to act on timings that have done something ++ // Handlers can only be modified on main thread. ++ for (TimingHandler timings : HANDLERS) { ++ timings.reset(false); ++ } ++ } ++ ++ HANDLERS.clear(); ++ MINUTE_REPORTS.clear(); ++ ++ TimingHistory.resetTicks(true); ++ historyStart = System.currentTimeMillis(); ++ } ++ ++ static TimingHandler getHandler(String group, String name, Timing parent, boolean protect) { ++ return TIMING_MAP.get(new TimingIdentifier(group, name, parent, protect)); ++ } ++ ++ ++ /** ++ * Due to access restrictions, we need a helper method to get a Command TimingHandler with String group ++ *

++ * Plugins should never call this ++ * ++ * @param pluginName Plugin this command is associated with ++ * @param command Command to get timings for ++ * @return TimingHandler ++ */ ++ public static Timing getCommandTiming(String pluginName, Command command) { ++ Plugin plugin = null; ++ final Server server = Bukkit.getServer(); ++ if (!("minecraft".equals(pluginName) || "bukkit".equals(pluginName) || "Spigot".equals(pluginName) || ++ server == null)) { ++ plugin = server.getPluginManager().getPlugin(pluginName); ++ if (plugin == null) { ++ // Plugin is passing custom fallback prefix, try to look up by class loader ++ plugin = getPluginByClassloader(command.getClass()); ++ } ++ } ++ if (plugin == null) { ++ return Timings.ofSafe("Command: " + pluginName + ":" + command.getTimingName()); ++ } ++ ++ return Timings.ofSafe(plugin, "Command: " + pluginName + ":" + command.getTimingName()); ++ } ++ ++ /** ++ * Looks up the class loader for the specified class, and if it is a PluginClassLoader, return the ++ * Plugin that created this class. ++ * ++ * @param clazz Class to check ++ * @return Plugin if created by a plugin ++ */ ++ public static Plugin getPluginByClassloader(Class clazz) { ++ if (clazz == null) { ++ return null; ++ } ++ final ClassLoader classLoader = clazz.getClassLoader(); ++ if (classLoader instanceof PluginClassLoader) { ++ PluginClassLoader pluginClassLoader = (PluginClassLoader) classLoader; ++ return pluginClassLoader.getPlugin(); ++ } ++ return null; ++ } ++} +diff --git a/src/main/java/org/spigotmc/timings/UnsafeTimingHandler.java b/src/main/java/org/spigotmc/timings/UnsafeTimingHandler.java +new file mode 100644 +index 0000000..971106f +--- /dev/null ++++ b/src/main/java/org/spigotmc/timings/UnsafeTimingHandler.java +@@ -0,0 +1,51 @@ ++/* ++ * 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 org.spigotmc.timings; ++ ++import org.bukkit.Bukkit; ++ ++class UnsafeTimingHandler extends TimingHandler { ++ ++ UnsafeTimingHandler(TimingIdentifier id) { ++ super(id); ++ } ++ ++ private static void checkThread() { ++ if (!Bukkit.isPrimaryThread()) { ++ throw new IllegalStateException("Calling Timings from Async Operation"); ++ } ++ } ++ ++ @Override ++ public void startTiming() { ++ checkThread(); ++ super.startTiming(); ++ } ++ ++ @Override ++ public void stopTiming() { ++ checkThread(); ++ super.stopTiming(); ++ } ++} +diff --git a/src/main/java/org/spigotmc/util/JSONUtil.java b/src/main/java/org/spigotmc/util/JSONUtil.java +new file mode 100644 +index 0000000..134fb2d +--- /dev/null ++++ b/src/main/java/org/spigotmc/util/JSONUtil.java +@@ -0,0 +1,123 @@ ++package org.spigotmc.util; ++ ++import com.google.common.base.Function; ++import com.google.common.collect.Lists; ++import com.google.common.collect.Maps; ++import org.json.simple.JSONArray; ++import org.json.simple.JSONObject; ++ ++import java.util.ArrayList; ++import java.util.LinkedHashMap; ++import java.util.List; ++import java.util.Map; ++ ++/** ++ * Provides Utility methods that assist with generating JSON Objects ++ */ ++@SuppressWarnings({"rawtypes", "SuppressionAnnotation"}) ++public final class JSONUtil { ++ private JSONUtil() {} ++ ++ /** ++ * Creates a key/value "JSONPair" object ++ * @param key ++ * @param obj ++ * @return ++ */ ++ public static JSONPair pair(String key, Object obj) { ++ return new JSONPair(key, obj); ++ } ++ ++ public static JSONPair pair(long key, Object obj) { ++ return new JSONPair(String.valueOf(key), obj); ++ } ++ ++ /** ++ * Creates a new JSON object from multiple JsonPair key/value pairs ++ * @param data ++ * @return ++ */ ++ public static Map createObject(JSONPair... data) { ++ return appendObjectData(new LinkedHashMap(), data); ++ } ++ ++ /** ++ * This appends multiple key/value Obj pairs into a JSON Object ++ * @param parent ++ * @param data ++ * @return ++ */ ++ public static Map appendObjectData(Map parent, JSONPair... data) { ++ for (JSONPair JSONPair : data) { ++ parent.put(JSONPair.key, JSONPair.val); ++ } ++ return parent; ++ } ++ ++ /** ++ * This builds a JSON array from a set of data ++ * @param data ++ * @return ++ */ ++ public static List toArray(Object... data) { ++ return Lists.newArrayList(data); ++ } ++ ++ /** ++ * These help build a single JSON array using a mapper function ++ * @param collection ++ * @param mapper ++ * @param ++ * @return ++ */ ++ public static List toArrayMapper(E[] collection, Function mapper) { ++ return toArrayMapper(Lists.newArrayList(collection), mapper); ++ } ++ ++ public static List toArrayMapper(Iterable collection, Function mapper) { ++ List array = Lists.newArrayList(); ++ for (E e : collection) { ++ Object object = mapper.apply(e); ++ if (object != null) { ++ array.add(object); ++ } ++ } ++ return array; ++ } ++ ++ /** ++ * These help build a single JSON Object from a collection, using a mapper function ++ * @param collection ++ * @param mapper ++ * @param ++ * @return ++ */ ++ public static Map toObjectMapper(E[] collection, Function mapper) { ++ return toObjectMapper(Lists.newArrayList(collection), mapper); ++ } ++ ++ public static Map toObjectMapper(Iterable collection, Function mapper) { ++ Map object = Maps.newLinkedHashMap(); ++ for (E e : collection) { ++ JSONPair JSONPair = mapper.apply(e); ++ if (JSONPair != null) { ++ object.put(JSONPair.key, JSONPair.val); ++ } ++ } ++ return object; ++ } ++ ++ /** ++ * Simply stores a key and a value, used internally by many methods below. ++ */ ++ @SuppressWarnings("PublicInnerClass") ++ public static class JSONPair { ++ final String key; ++ final Object val; ++ ++ JSONPair(String key, Object val) { ++ this.key = key; ++ this.val = val; ++ } ++ } ++} +diff --git a/src/main/java/org/spigotmc/util/LoadingIntMap.java b/src/main/java/org/spigotmc/util/LoadingIntMap.java +new file mode 100644 +index 0000000..e8087e8 +--- /dev/null ++++ b/src/main/java/org/spigotmc/util/LoadingIntMap.java +@@ -0,0 +1,68 @@ ++/* ++ * Copyright (c) 2015. Starlis LLC / dba Empire Minecraft ++ * ++ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval ++ * ++ */ ++package org.spigotmc.util; ++ ++ ++import com.google.common.base.Function; ++import gnu.trove.map.hash.TIntObjectHashMap; ++ ++import java.lang.reflect.Constructor; ++import java.util.*; ++ ++/** ++ * Allows you to pass a Loader function that when a key is accessed that doesn't exists, ++ * automatically loads the entry into the map by calling the loader Function. ++ * ++ * .get() Will only return null if the Loader can return null. ++ * ++ * You may pass any backing Map to use. ++ * ++ * This class is not thread safe and should be wrapped with Collections.synchronizedMap on the OUTSIDE of the LoadingMap if needed. ++ * ++ * Do not wrap the backing map with Collections.synchronizedMap. ++ * ++ * @param Key ++ * @param Value ++ */ ++public class LoadingIntMap extends TIntObjectHashMap { ++ private final Function loader; ++ ++ /** ++ * Initializes an auto loading map using specified loader and backing map ++ * @param backingMap ++ * @param loader ++ */ ++ public LoadingIntMap(Function loader) { ++ this.loader = loader; ++ } ++ ++ ++ @Override ++ public V get(int key) { ++ V res = super.get(key); ++ if (res == null) { ++ res = loader.apply(key); ++ if (res != null) { ++ put(key, res); ++ } ++ } ++ return res; ++ } ++ ++ /** ++ * Due to java stuff, you will need to cast it to (Function) for some cases ++ * @param ++ */ ++ public abstract static class Feeder implements Function { ++ @Override ++ public T apply(Object input) { ++ return apply(); ++ } ++ ++ public abstract T apply(); ++ } ++} +diff --git a/src/main/java/org/spigotmc/util/LoadingMap.java b/src/main/java/org/spigotmc/util/LoadingMap.java +new file mode 100644 +index 0000000..17aead4 +--- /dev/null ++++ b/src/main/java/org/spigotmc/util/LoadingMap.java +@@ -0,0 +1,332 @@ ++/* ++ * 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 org.spigotmc.util; ++ ++ ++import com.google.common.base.Function; ++import org.bukkit.Material; ++import org.spigotmc.timings.TimingHistory; ++import org.w3c.dom.css.Counter; ++ ++import java.lang.reflect.Constructor; ++import java.util.AbstractMap; ++import java.util.Collection; ++import java.util.EnumMap; ++import java.util.HashMap; ++import java.util.IdentityHashMap; ++import java.util.Map; ++import java.util.Set; ++ ++/** ++ * Allows you to pass a Loader function that when a key is accessed that doesn't exists, ++ * automatically loads the entry into the map by calling the loader Function. ++ * ++ * .get() Will only return null if the Loader can return null. ++ * ++ * You may pass any backing Map to use. ++ * ++ * This class is not thread safe and should be wrapped with Collections.synchronizedMap on the OUTSIDE of the LoadingMap if needed. ++ * ++ * Do not wrap the backing map with Collections.synchronizedMap. ++ * ++ * @param Key ++ * @param Value ++ */ ++public class LoadingMap extends AbstractMap { ++ private final Map backingMap; ++ private final Function loader; ++ ++ /** ++ * Initializes an auto loading map using specified loader and backing map ++ * @param backingMap ++ * @param loader ++ */ ++ public LoadingMap(Map backingMap, Function loader) { ++ this.backingMap = backingMap; ++ this.loader = loader; ++ } ++ ++ /** ++ * Creates a new LoadingMap with the specified map and loader ++ * @param backingMap ++ * @param loader ++ * @param ++ * @param ++ * @return ++ */ ++ public static Map of(Map backingMap, Function loader) { ++ return new LoadingMap(backingMap, loader); ++ } ++ ++ /** ++ * Creates a LoadingMap with an auto instantiating loader. ++ * ++ * Will auto construct class of of Value when not found ++ * ++ * Since this uses Reflection, It is more effecient to define your own static loader ++ * than using this helper, but if performance is not critical, this is easier. ++ * ++ * @param backingMap Actual map being used. ++ * @param keyClass Class used for the K generic ++ * @param valueClass Class used for the V generic ++ * @param Key Type of the Map ++ * @param Value Type of the Map ++ * @return Map that auto instantiates on .get() ++ */ ++ public static Map newAutoMap(Map backingMap, final Class keyClass, ++ final Class valueClass) { ++ return new LoadingMap(backingMap, new AutoInstantiatingLoader(keyClass, valueClass)); ++ } ++ /** ++ * Creates a LoadingMap with an auto instantiating loader. ++ * ++ * Will auto construct class of of Value when not found ++ * ++ * Since this uses Reflection, It is more effecient to define your own static loader ++ * than using this helper, but if performance is not critical, this is easier. ++ * ++ * @param backingMap Actual map being used. ++ * @param valueClass Class used for the V generic ++ * @param Key Type of the Map ++ * @param Value Type of the Map ++ * @return Map that auto instantiates on .get() ++ */ ++ public static Map newAutoMap(Map backingMap, ++ final Class valueClass) { ++ return newAutoMap(backingMap, null, valueClass); ++ } ++ ++ /** ++ * @see #newAutoMap ++ * ++ * new Auto initializing map using a HashMap. ++ * @param keyClass ++ * @param valueClass ++ * @param ++ * @param ++ * @return ++ */ ++ public static Map newHashAutoMap(final Class keyClass, final Class valueClass) { ++ return newAutoMap(new HashMap(), keyClass, valueClass); ++ } ++ ++ /** ++ * @see #newAutoMap ++ * ++ * new Auto initializing map using a HashMap. ++ * @param valueClass ++ * @param ++ * @param ++ * @return ++ */ ++ public static Map newHashAutoMap(final Class valueClass) { ++ return newHashAutoMap(null, valueClass); ++ } ++ ++ /** ++ * @see #newAutoMap ++ * ++ * new Auto initializing map using a HashMap. ++ * ++ * @param keyClass ++ * @param valueClass ++ * @param initialCapacity ++ * @param loadFactor ++ * @param ++ * @param ++ * @return ++ */ ++ public static Map newHashAutoMap(final Class keyClass, final Class valueClass, int initialCapacity, float loadFactor) { ++ return newAutoMap(new HashMap(initialCapacity, loadFactor), keyClass, valueClass); ++ } ++ ++ /** ++ * @see #newAutoMap ++ * ++ * new Auto initializing map using a HashMap. ++ * ++ * @param valueClass ++ * @param initialCapacity ++ * @param loadFactor ++ * @param ++ * @param ++ * @return ++ */ ++ public static Map newHashAutoMap(final Class valueClass, int initialCapacity, float loadFactor) { ++ return newHashAutoMap(null, valueClass, initialCapacity, loadFactor); ++ } ++ ++ /** ++ * Initializes an auto loading map using a HashMap ++ * @param loader ++ * @param ++ * @param ++ * @return ++ */ ++ public static Map newHashMap(Function loader) { ++ return new LoadingMap(new HashMap(), loader); ++ } ++ ++ /** ++ * Initializes an auto loading map using a HashMap ++ * @param loader ++ * @param initialCapacity ++ * @param loadFactor ++ * @param ++ * @param ++ * @return ++ */ ++ public static Map newHashMap(Function loader, int initialCapacity, float loadFactor) { ++ return new LoadingMap(new HashMap(initialCapacity, loadFactor), loader); ++ } ++ ++ /** ++ * Initializes an auto loading map using an Identity HashMap ++ * @param loader ++ * @param ++ * @param ++ * @return ++ */ ++ public static Map newIdentityHashMap(Function loader) { ++ return new LoadingMap(new IdentityHashMap(), loader); ++ } ++ ++ /** ++ * Initializes an auto loading map using an Identity HashMap ++ * @param loader ++ * @param initialCapacity ++ * @param ++ * @param ++ * @return ++ */ ++ public static Map newIdentityHashMap(Function loader, int initialCapacity) { ++ return new LoadingMap(new IdentityHashMap(initialCapacity), loader); ++ } ++ ++ @Override ++ public int size() {return backingMap.size();} ++ ++ @Override ++ public boolean isEmpty() {return backingMap.isEmpty();} ++ ++ @Override ++ public boolean containsKey(Object key) {return backingMap.containsKey(key);} ++ ++ @Override ++ public boolean containsValue(Object value) {return backingMap.containsValue(value);} ++ ++ @Override ++ public V get(Object key) { ++ V res = backingMap.get(key); ++ if (res == null && key != null) { ++ res = loader.apply((K) key); ++ if (res != null) { ++ backingMap.put((K) key, res); ++ } ++ } ++ return res; ++ } ++ ++ public V put(K key, V value) {return backingMap.put(key, value);} ++ ++ @Override ++ public V remove(Object key) {return backingMap.remove(key);} ++ ++ public void putAll(Map m) {backingMap.putAll(m);} ++ ++ @Override ++ public void clear() {backingMap.clear();} ++ ++ @Override ++ public Set keySet() {return backingMap.keySet();} ++ ++ @Override ++ public Collection values() {return backingMap.values();} ++ ++ @Override ++ public boolean equals(Object o) {return backingMap.equals(o);} ++ ++ @Override ++ public int hashCode() {return backingMap.hashCode();} ++ ++ @Override ++ public Set> entrySet() { ++ return backingMap.entrySet(); ++ } ++ ++ public LoadingMap clone() { ++ return new LoadingMap(backingMap, loader); ++ } ++ ++ private static class AutoInstantiatingLoader implements Function { ++ final Constructor constructor; ++ private final Class valueClass; ++ ++ AutoInstantiatingLoader(Class keyClass, Class valueClass) { ++ try { ++ this.valueClass = valueClass; ++ if (keyClass != null) { ++ constructor = valueClass.getConstructor(keyClass); ++ } else { ++ constructor = null; ++ } ++ } catch (NoSuchMethodException e) { ++ throw new IllegalStateException( ++ valueClass.getName() + " does not have a constructor for " + (keyClass != null ? keyClass.getName() : null)); ++ } ++ } ++ ++ @Override ++ public V apply(K input) { ++ try { ++ return (constructor != null ? constructor.newInstance(input) : valueClass.newInstance()); ++ } catch (Exception e) { ++ throw new ExceptionInInitializerError(e); ++ } ++ } ++ ++ @Override ++ public int hashCode() { ++ return super.hashCode(); ++ } ++ ++ @Override ++ public boolean equals(Object object) { ++ return false; ++ } ++ } ++ ++ /** ++ * Due to java stuff, you will need to cast it to (Function) for some cases ++ * @param ++ */ ++ public abstract static class Feeder implements Function { ++ @Override ++ public T apply(Object input) { ++ return apply(); ++ } ++ ++ public abstract T apply(); ++ } ++} +diff --git a/src/main/java/org/spigotmc/util/MRUMapCache.java b/src/main/java/org/spigotmc/util/MRUMapCache.java +new file mode 100644 +index 0000000..7b44039 +--- /dev/null ++++ b/src/main/java/org/spigotmc/util/MRUMapCache.java +@@ -0,0 +1,100 @@ ++/* ++ * 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 org.spigotmc.util; ++ ++import java.util.AbstractMap; ++import java.util.Collection; ++import java.util.Map; ++import java.util.Set; ++ ++/** ++ * Implements a Most Recently Used cache in front of a backing map, to quickly access the last accessed result. ++ * @param ++ * @param ++ */ ++public class MRUMapCache extends AbstractMap { ++ final Map backingMap; ++ Object cacheKey; ++ V cacheValue; ++ public MRUMapCache(final Map backingMap) { ++ this.backingMap = backingMap; ++ } ++ ++ public int size() {return backingMap.size();} ++ ++ public boolean isEmpty() {return backingMap.isEmpty();} ++ ++ public boolean containsKey(Object key) { ++ return key != null && key.equals(cacheKey) || backingMap.containsKey(key); ++ } ++ ++ public boolean containsValue(Object value) { ++ return value != null && value == cacheValue || backingMap.containsValue(value); ++ } ++ ++ public V get(Object key) { ++ if (cacheKey != null && cacheKey.equals(key)) { ++ return cacheValue; ++ } ++ cacheKey = key; ++ return cacheValue = backingMap.get(key); ++ } ++ ++ public V put(K key, V value) { ++ cacheKey = key; ++ return cacheValue = backingMap.put(key, value); ++ } ++ ++ public V remove(Object key) { ++ if (key != null && key.equals(cacheKey)) { ++ cacheKey = null; ++ } ++ return backingMap.remove(key); ++ } ++ ++ public void putAll(Map m) {backingMap.putAll(m);} ++ ++ public void clear() { ++ cacheKey = null; ++ cacheValue = null; ++ backingMap.clear(); ++ } ++ ++ public Set keySet() {return backingMap.keySet();} ++ ++ public Collection values() {return backingMap.values();} ++ ++ public Set> entrySet() {return backingMap.entrySet();} ++ ++ /** ++ * Wraps the specified map with a most recently used cache ++ * @param map ++ * @param ++ * @param ++ * @return ++ */ ++ public static Map of(Map map) { ++ return new MRUMapCache(map); ++ } ++} +-- +2.7.0 + diff --git a/Spigot-Server-Patches/0002-PaperSpigot-config-files.patch b/Spigot-Server-Patches/0002-PaperSpigot-config-files.patch index 876b9042eb..c94fd2d5f6 100644 --- a/Spigot-Server-Patches/0002-PaperSpigot-config-files.patch +++ b/Spigot-Server-Patches/0002-PaperSpigot-config-files.patch @@ -1,11 +1,11 @@ -From 173dc14f7675fadf49fe31bc7e790ec5369c046a Mon Sep 17 00:00:00 2001 +From d27df4714c775e383f7388373b4dac6aea181526 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Thu, 28 May 2015 00:08:15 -0500 Subject: [PATCH] PaperSpigot config files diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java -index 8dd4ef1..6dfb161 100644 +index c30867d..45cea14 100644 --- a/src/main/java/net/minecraft/server/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/DedicatedServer.java @@ -177,6 +177,10 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer @@ -20,7 +20,7 @@ index 8dd4ef1..6dfb161 100644 DedicatedServer.LOGGER.info("Generating keypair"); this.a(MinecraftEncryption.b()); diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java -index 0271b19..822ddeb 100644 +index eca86db8..bb46799 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -161,6 +161,8 @@ public abstract class World implements IBlockAccess { @@ -41,7 +41,7 @@ index 0271b19..822ddeb 100644 this.world = new CraftWorld((WorldServer) this, gen, env); this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); // CraftBukkit diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index f57c785..5ce0d50 100644 +index 2759425..019f7a9 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -688,6 +688,7 @@ public final class CraftServer implements Server { @@ -88,7 +88,7 @@ index 337aa29..c936219 100644 diff --git a/src/main/java/org/github/paperspigot/PaperSpigotConfig.java b/src/main/java/org/github/paperspigot/PaperSpigotConfig.java new file mode 100644 -index 0000000..006ca37 +index 0000000..a08688f --- /dev/null +++ b/src/main/java/org/github/paperspigot/PaperSpigotConfig.java @@ -0,0 +1,141 @@ @@ -123,7 +123,7 @@ index 0000000..006ca37 + + "\n" + + "IRC: #paperspigot @ irc.spi.gt ( http://irc.spi.gt/iris/?channels=PaperSpigot )\n"; + /*========================================================================*/ -+ static YamlConfiguration config; ++ public static YamlConfiguration config; + static int version; + static Map commands; + /*========================================================================*/ @@ -317,5 +317,5 @@ index 0000000..146324a + } +} -- -2.4.6.windows.1 +2.7.0 diff --git a/Spigot-Server-Patches/0081-Timings-v2.patch b/Spigot-Server-Patches/0081-Timings-v2.patch new file mode 100644 index 0000000000..239a6177f2 --- /dev/null +++ b/Spigot-Server-Patches/0081-Timings-v2.patch @@ -0,0 +1,1146 @@ +From 40847345456723ebbd77e9800281885fc8b0eb8a Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 8 Jan 2016 23:36:39 -0600 +Subject: [PATCH] Timings v2 + + +diff --git a/src/main/java/net/minecraft/server/Block.java b/src/main/java/net/minecraft/server/Block.java +index c26975a..84af14c 100644 +--- a/src/main/java/net/minecraft/server/Block.java ++++ b/src/main/java/net/minecraft/server/Block.java +@@ -65,6 +65,16 @@ public class Block { + protected boolean y; + protected boolean z; + protected boolean isTileEntity; ++ // Spigot start ++ public org.spigotmc.timings.Timing timing; ++ public org.spigotmc.timings.Timing getTiming() { ++ if (timing == null) { ++ timing = org.spigotmc.timings.SpigotTimings.getBlockTiming(this); ++ } ++ return timing; ++ } ++ // Spigot end ++ + protected double minX; + protected double minY; + protected double minZ; +diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java +index 45cea14..d2c5078 100644 +--- a/src/main/java/net/minecraft/server/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/DedicatedServer.java +@@ -20,7 +20,7 @@ import java.io.PrintStream; + import org.apache.logging.log4j.Level; + + import org.bukkit.craftbukkit.LoggerOutputStream; +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot ++import org.spigotmc.timings.SpigotTimings; // Spigot + import org.bukkit.event.server.ServerCommandEvent; + import org.bukkit.craftbukkit.util.Waitable; + import org.bukkit.event.server.RemoteServerCommandEvent; +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index 20cc946..9f157ea 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -16,7 +16,8 @@ import org.bukkit.entity.Hanging; + import org.bukkit.entity.LivingEntity; + import org.bukkit.entity.Painting; + import org.bukkit.entity.Vehicle; +-import org.spigotmc.CustomTimingsHandler; // Spigot ++import org.spigotmc.timings.SpigotTimings; // Spigot ++import org.spigotmc.timings.Timing; // Spigot + import org.bukkit.event.entity.EntityCombustByEntityEvent; + import org.bukkit.event.hanging.HangingBreakByEntityEvent; + import org.bukkit.event.painting.PaintingBreakByEntityEvent; +@@ -128,7 +129,7 @@ public abstract class Entity implements ICommandListener { + public boolean loadChunks = false; // PaperSpigot - Entities can load chunks they move through and keep them loaded + + // Spigot start +- public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getEntityTimings(this); // Spigot ++ public Timing tickTimer = SpigotTimings.getEntityTimings(this); // Spigot + public final byte activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); + public final boolean defaultActivationState; + public long activatedTick = Integer.MIN_VALUE; +@@ -426,7 +427,6 @@ public abstract class Entity implements ICommandListener { + + + public void move(double d0, double d1, double d2) { +- org.bukkit.craftbukkit.SpigotTimings.entityMoveTimer.startTiming(); // Spigot + if (this.loadChunks) loadChunks(); // PaperSpigot - Load chunks + if (this.noclip) { + this.a(this.getBoundingBox().c(d0, d1, d2)); +@@ -764,7 +764,6 @@ public abstract class Entity implements ICommandListener { + + this.world.methodProfiler.b(); + } +- org.bukkit.craftbukkit.SpigotTimings.entityMoveTimer.stopTiming(); // Spigot + } + + private void recalcPosition() { +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index 0de13bb..e212835 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -23,7 +23,7 @@ import org.bukkit.event.entity.EntityRegainHealthEvent; + import org.bukkit.event.vehicle.VehicleExitEvent; + // CraftBukkit end + +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot ++import org.spigotmc.timings.SpigotTimings; // Spigot + + public abstract class EntityLiving extends Entity { + +@@ -1449,7 +1449,6 @@ public abstract class EntityLiving extends Entity { + } + + public void t_() { +- SpigotTimings.timerEntityBaseTick.startTiming(); // Spigot + super.t_(); + if (!this.world.isClientSide) { + int i = this.bv(); +@@ -1488,9 +1487,7 @@ public abstract class EntityLiving extends Entity { + } + } + +- SpigotTimings.timerEntityBaseTick.stopTiming(); // Spigot + this.m(); +- SpigotTimings.timerEntityTickRest.startTiming(); // Spigot + double d0 = this.locX - this.lastX; + double d1 = this.locZ - this.lastZ; + float f = (float) (d0 * d0 + d1 * d1); +@@ -1555,7 +1552,6 @@ public abstract class EntityLiving extends Entity { + + this.world.methodProfiler.b(); + this.aT += f2; +- SpigotTimings.timerEntityTickRest.stopTiming(); // Spigot + } + + protected float h(float f, float f1) { +@@ -1620,7 +1616,6 @@ public abstract class EntityLiving extends Entity { + } + + this.world.methodProfiler.a("ai"); +- SpigotTimings.timerEntityAI.startTiming(); // Spigot + if (this.bD()) { + this.aY = false; + this.aZ = 0.0F; +@@ -1631,7 +1626,6 @@ public abstract class EntityLiving extends Entity { + this.doTick(); + this.world.methodProfiler.b(); + } +- SpigotTimings.timerEntityAI.stopTiming(); // Spigot + + this.world.methodProfiler.b(); + this.world.methodProfiler.a("jump"); +@@ -1653,15 +1647,11 @@ public abstract class EntityLiving extends Entity { + this.aZ *= 0.98F; + this.ba *= 0.98F; + this.bb *= 0.9F; +- SpigotTimings.timerEntityAIMove.startTiming(); // Spigot + this.g(this.aZ, this.ba); +- SpigotTimings.timerEntityAIMove.stopTiming(); // Spigot + this.world.methodProfiler.b(); + this.world.methodProfiler.a("push"); + if (!this.world.isClientSide) { +- SpigotTimings.timerEntityAICollision.startTiming(); // Spigot + this.bL(); +- SpigotTimings.timerEntityAICollision.stopTiming(); // Spigot + } + + this.world.methodProfiler.b(); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index eac71e8..f4f3642 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -45,7 +45,7 @@ import jline.console.ConsoleReader; + import joptsimple.OptionSet; + + import org.bukkit.craftbukkit.Main; +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot ++import org.spigotmc.timings.SpigotTimings; // Spigot + // CraftBukkit end + + public abstract class MinecraftServer implements Runnable, ICommandListener, IAsyncTaskHandler, IMojangStatistics { +@@ -449,6 +449,8 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs + // CraftBukkit end + if (!this.N) { + MinecraftServer.LOGGER.info("Stopping server"); ++ SpigotTimings.stopServer(); // Spigot ++ + // CraftBukkit start + if (this.server != null) { + this.server.disablePlugins(); +@@ -697,7 +699,7 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs + protected void z() {} + + protected void A() throws ExceptionWorldConflict { // CraftBukkit - added throws +- SpigotTimings.serverTickTimer.startTiming(); // Spigot ++ org.spigotmc.timings.TimingsManager.FULL_SERVER_TICK.startTiming(); // Spigot + long i = System.nanoTime(); + + ++this.ticks; +@@ -757,8 +759,7 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs + this.methodProfiler.b(); + this.methodProfiler.b(); + org.spigotmc.WatchdogThread.tick(); // Spigot +- SpigotTimings.serverTickTimer.stopTiming(); // Spigot +- org.spigotmc.CustomTimingsHandler.tick(); // Spigot ++ org.spigotmc.timings.TimingsManager.FULL_SERVER_TICK.stopTiming(); // Spigot + } + + public void B() { +diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java +index 2ab01a1..f17788d 100644 +--- a/src/main/java/net/minecraft/server/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/PlayerConnection.java +@@ -60,6 +60,7 @@ import org.bukkit.event.player.PlayerToggleSprintEvent; + import org.bukkit.inventory.CraftingInventory; + import org.bukkit.inventory.InventoryView; + import org.bukkit.util.NumberConversions; ++import org.spigotmc.timings.SpigotTimings; // Spigot + // CraftBukkit end + + import org.github.paperspigot.PaperSpigotConfig; // PaperSpigot +@@ -1146,7 +1147,7 @@ public class PlayerConnection implements PacketListenerPlayIn, IUpdatePlayerList + // CraftBukkit end + + private void handleCommand(String s) { +- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.startTiming(); // Spigot ++ SpigotTimings.playerCommandTimer.startTiming(); // Spigot + // CraftBukkit start - whole method + if ( org.spigotmc.SpigotConfig.logCommands ) // Spigot + this.c.info(this.player.getName() + " issued server command: " + s); +@@ -1157,22 +1158,22 @@ public class PlayerConnection implements PacketListenerPlayIn, IUpdatePlayerList + this.server.getPluginManager().callEvent(event); + + if (event.isCancelled()) { +- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot ++ SpigotTimings.playerCommandTimer.stopTiming(); // Spigot + return; + } + + try { + if (this.server.dispatchCommand(event.getPlayer(), event.getMessage().substring(1))) { +- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot ++ SpigotTimings.playerCommandTimer.stopTiming(); // Spigot + return; + } + } catch (org.bukkit.command.CommandException ex) { + player.sendMessage(org.bukkit.ChatColor.RED + "An internal error occurred while attempting to perform this command"); + java.util.logging.Logger.getLogger(PlayerConnection.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); +- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot ++ SpigotTimings.playerCommandTimer.stopTiming(); // Spigot + return; + } +- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot ++ SpigotTimings.playerCommandTimer.stopTiming(); // Spigot + // this.minecraftServer.getCommandHandler().a(this.player, s); + // CraftBukkit end + } +diff --git a/src/main/java/net/minecraft/server/TileEntity.java b/src/main/java/net/minecraft/server/TileEntity.java +index 3fc6450..040cb9b 100644 +--- a/src/main/java/net/minecraft/server/TileEntity.java ++++ b/src/main/java/net/minecraft/server/TileEntity.java +@@ -6,12 +6,13 @@ import java.util.concurrent.Callable; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + +-import org.spigotmc.CustomTimingsHandler; // Spigot ++import org.spigotmc.timings.SpigotTimings; // Spigot ++import org.spigotmc.timings.Timing; // Spigot + import org.bukkit.inventory.InventoryHolder; // CraftBukkit + + public abstract class TileEntity { + +- public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getTileEntityTimings(this); // Spigot ++ public Timing tickTimer = SpigotTimings.getTileEntityTimings(this); // Spigot + private static final Logger a = LogManager.getLogger(); + private static Map> f = Maps.newHashMap(); + private static Map, String> g = Maps.newHashMap(); +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index 13ab789..3476031 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -7,13 +7,14 @@ import org.bukkit.Bukkit; + import org.bukkit.block.BlockState; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.CraftWorld; +-import org.bukkit.craftbukkit.SpigotTimings; + import org.bukkit.craftbukkit.event.CraftEventFactory; + import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.bukkit.craftbukkit.util.LongHashSet; + import org.bukkit.event.block.BlockCanBuildEvent; + import org.bukkit.event.block.BlockPhysicsEvent; + import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; + import org.bukkit.generator.ChunkGenerator; ++import org.spigotmc.timings.SpigotTimings; + + import java.util.*; + import java.util.concurrent.Callable; +@@ -159,7 +160,7 @@ public abstract class World implements IBlockAccess { + + public final org.github.paperspigot.PaperSpigotWorldConfig paperSpigotConfig; // PaperSpigot + +- public final SpigotTimings.WorldTimingsHandler timings; // Spigot ++ public final org.spigotmc.timings.WorldTimingsHandler timings; // Spigot + + public CraftWorld getWorld() { + return this.world; +@@ -227,7 +228,7 @@ public abstract class World implements IBlockAccess { + this.getServer().addWorld(this.world); + // CraftBukkit end + this.keepSpawnInMemory = this.paperSpigotConfig.keepSpawnInMemory; // PaperSpigot +- timings = new SpigotTimings.WorldTimingsHandler(this); // Spigot - code below can generate new world and access timings ++ timings = new org.spigotmc.timings.WorldTimingsHandler(this); // Spigot - code below can generate new world and access timings + this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); + this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); + } +@@ -1393,6 +1394,7 @@ public abstract class World implements IBlockAccess { + } + + this.methodProfiler.c("remove"); ++ timings.entityRemoval.startTiming(); // Spigot + this.entityList.removeAll(this.g); + + int j; +@@ -1412,12 +1414,14 @@ public abstract class World implements IBlockAccess { + } + + this.g.clear(); ++ timings.entityRemoval.stopTiming(); // Spigot + this.methodProfiler.c("regular"); + + org.spigotmc.ActivationRange.activateEntities(this); // Spigot + timings.entityTick.startTiming(); // Spigot + guardEntityList = true; // Spigot + // CraftBukkit start - Use field for loop variable ++ org.spigotmc.timings.TimingHistory.entityTicks += this.entityList.size(); // Spigot + int entitiesThisCycle = 0; + // PaperSpigot start - Disable tick limiters + //if (tickPosition < 0) tickPosition = 0; +@@ -1438,12 +1442,12 @@ public abstract class World implements IBlockAccess { + this.methodProfiler.a("tick"); + if (!entity.dead) { + try { +- SpigotTimings.tickEntityTimer.startTiming(); // Spigot ++ entity.tickTimer.startTiming(); // Spigot + this.g(entity); +- SpigotTimings.tickEntityTimer.stopTiming(); // Spigot ++ entity.tickTimer.stopTiming(); // Spigot + } catch (Throwable throwable1) { + // PaperSpigot start - Prevent tile entity and entity crashes +- SpigotTimings.tickEntityTimer.stopTiming(); ++ entity.tickTimer.stopTiming(); + System.err.println("Entity threw exception at " + entity.world.getWorld().getName() + ":" + entity.locX + "," + entity.locY + "," + entity.locZ); + throwable1.printStackTrace(); + entity.dead = true; +@@ -1567,6 +1571,8 @@ public abstract class World implements IBlockAccess { + } + + timings.tileEntityPending.stopTiming(); // Spigot ++ org.spigotmc.timings.TimingHistory.tileEntityTicks += this.tileEntityList.size(); // Spigot ++ + this.methodProfiler.b(); + this.methodProfiler.b(); + } +@@ -1621,7 +1627,6 @@ public abstract class World implements IBlockAccess { + } + // PaperSpigot end + } else { +- entity.tickTimer.startTiming(); // Spigot + // CraftBukkit end + entity.P = entity.locX; + entity.Q = entity.locY; +@@ -1630,6 +1635,7 @@ public abstract class World implements IBlockAccess { + entity.lastPitch = entity.pitch; + if (flag && entity.ad) { + ++entity.ticksLived; ++ ++org.spigotmc.timings.TimingHistory.activatedEntityTicks; // Spigot + if (entity.vehicle != null) { + entity.ak(); + } else { +@@ -1686,7 +1692,6 @@ public abstract class World implements IBlockAccess { + } + } + +- entity.tickTimer.stopTiming(); // Spigot + } + } + +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index 3ec58dc..964df5b 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -245,13 +245,13 @@ public class WorldServer extends World implements IAsyncTaskHandler { + + timings.doChunkUnload.stopTiming(); // Spigot + this.methodProfiler.c("tickPending"); +- timings.doTickPending.startTiming(); // Spigot ++ timings.scheduledBlocks.startTiming(); // Spigot + this.a(false); +- timings.doTickPending.stopTiming(); // Spigot ++ timings.scheduledBlocks.stopTiming(); // Spigot + this.methodProfiler.c("tickBlocks"); +- timings.doTickTiles.startTiming(); // Spigot ++ timings.chunkTicks.startTiming(); // Spigot + this.h(); +- timings.doTickTiles.stopTiming(); // Spigot ++ timings.chunkTicks.stopTiming(); // Spigot + spigotConfig.antiXrayInstance.flushUpdates(this); // PaperSpigot + this.methodProfiler.c("chunkMap"); + timings.doChunkMap.startTiming(); // Spigot +@@ -471,6 +471,7 @@ public class WorldServer extends World implements IAsyncTaskHandler { + } + + this.methodProfiler.c("tickBlocks"); ++ timings.chunkTicksBlocks.startTiming(); // Spigot + i1 = this.getGameRules().c("randomTickSpeed"); + if (i1 > 0) { + ChunkSection[] achunksection = chunk.getSections(); +@@ -499,6 +500,7 @@ public class WorldServer extends World implements IAsyncTaskHandler { + } + } + } ++ timings.chunkTicksBlocks.stopTiming(); // Spigot + } + + } +@@ -630,6 +632,7 @@ public class WorldServer extends World implements IAsyncTaskHandler { + + this.methodProfiler.a("cleaning"); + ++ timings.scheduledBlocksCleanup.startTiming(); // Spigot + NextTickListEntry nextticklistentry; + + for (int j = 0; j < i; ++j) { +@@ -642,6 +645,7 @@ public class WorldServer extends World implements IAsyncTaskHandler { + this.M.remove(nextticklistentry); + this.V.add(nextticklistentry); + } ++ timings.scheduledBlocksCleanup.stopTiming(); // Spigot + + // PaperSpigot start - Allow redstone ticks to bypass the tickNextTickListCap + if (paperSpigotConfig.tickNextTickListCapIgnoresRedstone) { +@@ -662,6 +666,7 @@ public class WorldServer extends World implements IAsyncTaskHandler { + + this.methodProfiler.b(); + this.methodProfiler.a("ticking"); ++ timings.scheduledBlocksTicking.startTiming(); // Spigot + Iterator iterator = this.V.iterator(); + + while (iterator.hasNext()) { +@@ -671,6 +676,8 @@ public class WorldServer extends World implements IAsyncTaskHandler { + + if (this.areChunksLoadedBetween(nextticklistentry.a.a(-b0, -b0, -b0), nextticklistentry.a.a(b0, b0, b0))) { + IBlockData iblockdata = this.getType(nextticklistentry.a); ++ org.spigotmc.timings.Timing timing = iblockdata.getBlock().getTiming(); // Spigot ++ timing.startTiming(); // Spigot + + if (iblockdata.getBlock().getMaterial() != Material.AIR && Block.a(iblockdata.getBlock(), nextticklistentry.a())) { + try { +@@ -683,10 +690,12 @@ public class WorldServer extends World implements IAsyncTaskHandler { + throw new ReportedException(crashreport); + } + } ++ timing.stopTiming(); // Spigot + } else { + this.a(nextticklistentry.a, nextticklistentry.a(), 0); + } + } ++ timings.scheduledBlocksTicking.stopTiming(); // Spigot + + this.methodProfiler.b(); + this.V.clear(); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 7da8d67..0acfa1f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -329,6 +329,7 @@ public final class CraftServer implements Server { + DefaultPermissions.registerCorePermissions(); + CraftDefaultPermissions.registerCorePermissions(); + helpMap.initializeCommands(); ++ org.spigotmc.timings.Timings.reset(); // Spigot + } + } + +@@ -1715,13 +1716,32 @@ public final class CraftServer implements Server { + } + // PaperSpigot end + ++ @Deprecated + @Override + public YamlConfiguration getConfig() + { ++ return getBukkitConfig(); ++ } ++ ++ @Override ++ public YamlConfiguration getBukkitConfig() ++ { ++ return configuration; ++ } ++ ++ @Override ++ public YamlConfiguration getSpigotConfig() ++ { + return org.spigotmc.SpigotConfig.config; + } + + @Override ++ public YamlConfiguration getPaperSpigotConfig() ++ { ++ return org.github.paperspigot.PaperSpigotConfig.config; ++ } ++ ++ @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 41d2d87..0000000 +--- a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java ++++ /dev/null +@@ -1,173 +0,0 @@ +-package org.bukkit.craftbukkit; +- +-import com.google.common.collect.Maps; +-import net.minecraft.server.*; +-import org.bukkit.plugin.java.JavaPluginLoader; +-import org.spigotmc.CustomTimingsHandler; +-import org.bukkit.scheduler.BukkitTask; +- +-import java.util.HashMap; +-import java.util.Map; +- +-import org.bukkit.craftbukkit.scheduler.CraftTask; +- +-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 connectionTimer = new CustomTimingsHandler("Connection Handler"); +- public static final CustomTimingsHandler tickablesTimer = new CustomTimingsHandler("Tickables"); +- public static final CustomTimingsHandler schedulerTimer = new CustomTimingsHandler("Scheduler"); +- public static final CustomTimingsHandler chunkIOTickTimer = new CustomTimingsHandler("ChunkIOTick"); +- 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 entityTypeTimingMap = new HashMap(); +- public static final HashMap tileEntityTypeTimingMap = new HashMap(); +- public static final HashMap pluginTaskTimingMap = new HashMap(); +- +- /** +- * 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 if (ctask.timingName != null) { +- plugin = "CraftScheduler"; +- } else { +- plugin = "Unknown"; +- } +- String taskname = ctask.getTaskName(); +- +- String name = "Task: " + plugin + " Runnable: " + taskname; +- if (period > 0) { +- name += "(interval:" + period +")"; +- } else { +- name += "(Single)"; +- } +- CustomTimingsHandler result = pluginTaskTimingMap.get(name); +- if (result == null) { +- result = new CustomTimingsHandler(name, SpigotTimings.schedulerSyncTimer); +- 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().getSimpleName(); +- CustomTimingsHandler result = entityTypeTimingMap.get(entityType); +- if (result == null) { +- result = new CustomTimingsHandler("** tickEntity - " + entityType, activatedEntityTimer); +- 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(TileEntity entity) { +- String entityType = entity.getClass().getSimpleName(); +- CustomTimingsHandler result = tileEntityTypeTimingMap.get(entityType); +- if (result == null) { +- result = new CustomTimingsHandler("** tickTileEntity - " + entityType, tickTileEntityTimer); +- 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 doPortalForcer; +- public final CustomTimingsHandler doTickPending; +- public final CustomTimingsHandler doTickTiles; +- public final CustomTimingsHandler doVillages; +- public final CustomTimingsHandler doChunkMap; +- public final CustomTimingsHandler doChunkGC; +- 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 syncChunkLoadDataTimer; +- public final CustomTimingsHandler syncChunkLoadStructuresTimer; +- public final CustomTimingsHandler syncChunkLoadEntitiesTimer; +- public final CustomTimingsHandler syncChunkLoadTileEntitiesTimer; +- public final CustomTimingsHandler syncChunkLoadTileTicksTimer; +- public final CustomTimingsHandler syncChunkLoadPostTimer; +- +- public WorldTimingsHandler(World server) { +- String name = server.worldData.getName() +" - "; +- +- mobSpawn = new CustomTimingsHandler("** " + name + "mobSpawn"); +- doChunkUnload = new CustomTimingsHandler("** " + name + "doChunkUnload"); +- doTickPending = new CustomTimingsHandler("** " + name + "doTickPending"); +- doTickTiles = new CustomTimingsHandler("** " + name + "doTickTiles"); +- doVillages = new CustomTimingsHandler("** " + name + "doVillages"); +- doChunkMap = new CustomTimingsHandler("** " + name + "doChunkMap"); +- doSounds = new CustomTimingsHandler("** " + name + "doSounds"); +- doChunkGC = new CustomTimingsHandler("** " + name + "doChunkGC"); +- doPortalForcer = new CustomTimingsHandler("** " + name + "doPortalForcer"); +- entityTick = new CustomTimingsHandler("** " + name + "entityTick"); +- tileEntityTick = new CustomTimingsHandler("** " + name + "tileEntityTick"); +- tileEntityPending = new CustomTimingsHandler("** " + name + "tileEntityPending"); +- +- syncChunkLoadTimer = new CustomTimingsHandler("** " + name + "syncChunkLoad"); +- syncChunkLoadDataTimer = new CustomTimingsHandler("** " + name + "syncChunkLoad - Data"); +- syncChunkLoadStructuresTimer = new CustomTimingsHandler("** " + name + "chunkLoad - Structures"); +- syncChunkLoadEntitiesTimer = new CustomTimingsHandler("** " + name + "chunkLoad - Entities"); +- syncChunkLoadTileEntitiesTimer = new CustomTimingsHandler("** " + name + "chunkLoad - TileEntities"); +- syncChunkLoadTileTicksTimer = new CustomTimingsHandler("** " + name + "chunkLoad - TileTicks"); +- syncChunkLoadPostTimer = new CustomTimingsHandler("** " + name + "chunkLoad - Post"); +- +- +- tracker = new CustomTimingsHandler(name + "tracker"); +- doTick = new CustomTimingsHandler(name + "doTick"); +- 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 1135f83..e8c6d5e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1496,6 +1496,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + ((WorldServer) getHandle().world).getPlayerChunkMap().updateViewDistance(getHandle(), viewDistance); + } + // PaperSpigot end ++ ++ @Override ++ public int getPing() ++ { ++ return getHandle().ping; ++ } + }; + + 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 93d8d42..9d94089 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -186,7 +186,7 @@ public class CraftScheduler implements BukkitScheduler { + } + } + return false; +- }}); ++ }}){{this.timings=org.spigotmc.timings.SpigotTimings.getCancelTasksTimer();}}; // Spigot + handle(task, 0l); + for (CraftTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) { + if (taskPending == task) { +@@ -219,7 +219,7 @@ public class CraftScheduler implements BukkitScheduler { + } + } + } +- }); ++ }){{this.timings=org.spigotmc.timings.SpigotTimings.getCancelTasksTimer(plugin);}}; // Spigot + handle(task, 0l); + for (CraftTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) { + if (taskPending == task) { +@@ -251,7 +251,7 @@ public class CraftScheduler implements BukkitScheduler { + CraftScheduler.this.pending.clear(); + CraftScheduler.this.temp.clear(); + } +- }); ++ }){{this.timings=org.spigotmc.timings.SpigotTimings.getCancelTasksTimer();}}; // Spigot + handle(task, 0l); + for (CraftTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) { + if (taskPending == task) { +@@ -346,9 +346,7 @@ public class CraftScheduler implements BukkitScheduler { + } + if (task.isSync()) { + try { +- task.timings.startTiming(); // Spigot + task.run(); +- task.timings.stopTiming(); // Spigot + } catch (final Throwable throwable) { + task.getOwner().getLogger().log( + Level.WARNING, +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +index 220e39a..e406e87 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +@@ -1,8 +1,8 @@ + package org.bukkit.craftbukkit.scheduler; + + import org.bukkit.Bukkit; +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot +-import org.spigotmc.CustomTimingsHandler; // Spigot ++import org.spigotmc.timings.SpigotTimings; // Spigot ++import org.spigotmc.timings.Timing; // Spigot + import org.bukkit.plugin.Plugin; + import org.bukkit.scheduler.BukkitTask; + +@@ -20,11 +20,11 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + */ + private volatile long period; + private long nextRun; +- private final Runnable task; ++ public final Runnable task; //Spigot ++ public Timing timings; // Spigot + private final Plugin plugin; + private final int id; + +- final CustomTimingsHandler timings; // Spigot + CraftTask() { + this(null, null, -1, -1); + } +@@ -34,25 +34,12 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + } + + // Spigot start +- public String timingName = null; +- CraftTask(String timingName) { +- this(timingName, null, null, -1, -1); +- } +- CraftTask(String timingName, final Runnable task) { +- this(timingName, null, task, -1, -1); +- } +- CraftTask(String timingName, final Plugin plugin, final Runnable task, final int id, final long period) { ++ CraftTask(final Plugin plugin, final Runnable task, final int id, final long period) { + this.plugin = plugin; + this.task = task; + this.id = id; + this.period = period; +- this.timingName = timingName == null && task == null ? "Unknown" : timingName; +- timings = this.isSync() ? SpigotTimings.getPluginTaskTimings(this, period) : null; +- } +- +- CraftTask(final Plugin plugin, final Runnable task, final int id, final long period) { +- this(null, plugin, task, id, period); +- // Spigot end ++ timings = task != null ? SpigotTimings.getPluginTaskTimings(this, period) : null; // Spigot + } + + public final int getTaskId() { +@@ -68,7 +55,9 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + } + + public void run() { ++ if (timings != null && isSync()) timings.startTiming(); // Spigot + task.run(); ++ if (timings != null && isSync()) timings.stopTiming(); // Spigot + } + + long getPeriod() { +@@ -113,12 +102,4 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + return true; + } + +- // Spigot start +- public String getTaskName() { +- if (timingName != null) { +- return timingName; +- } +- return task.getClass().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 e52ef47..bd0b887 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java +@@ -5,6 +5,7 @@ import org.bukkit.util.CachedServerIcon; + public class CraftIconCache implements CachedServerIcon { + public final String value; + ++ public String getData() { return value; } // Spigot + public CraftIconCache(final String value) { + this.value = value; + } +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index 5b0c64d..3336534 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -31,8 +31,8 @@ import net.minecraft.server.EntityWither; + import net.minecraft.server.MathHelper; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.World; +-import org.bukkit.craftbukkit.SpigotTimings; +-import org.bukkit.entity.Creeper; ++import org.spigotmc.timings.SpigotTimings; ++ + + public class ActivationRange + { +diff --git a/src/main/java/org/spigotmc/AntiXray.java b/src/main/java/org/spigotmc/AntiXray.java +index 5466a61..1a84295 100644 +--- a/src/main/java/org/spigotmc/AntiXray.java ++++ b/src/main/java/org/spigotmc/AntiXray.java +@@ -7,6 +7,7 @@ import net.minecraft.server.BlockPosition; + import net.minecraft.server.Blocks; + import net.minecraft.server.World; + import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.spigotmc.timings.SpigotTimings; + + // PaperSpigot start + import java.util.HashSet; +@@ -16,9 +17,6 @@ import java.util.Set; + public class AntiXray + { + +- private static final CustomTimingsHandler update = new CustomTimingsHandler( "xray - update" ); +- private static final CustomTimingsHandler obfuscate = new CustomTimingsHandler( "xray - obfuscate" ); +- /*========================================================================*/ + // Used to keep track of which blocks to obfuscate + private final boolean[] obfuscateBlocks = new boolean[ Short.MAX_VALUE ]; + // Used to select a random replacement ore +@@ -86,9 +84,9 @@ public class AntiXray + return; + } + // PaperSpigot end +- update.startTiming(); ++ SpigotTimings.antiXrayUpdateTimer.startTiming(); + updateNearbyBlocks( world, position, 2, false ); // 2 is the radius, we shouldn't change it as that would make it exponentially slower +- update.stopTiming(); ++ SpigotTimings.antiXrayUpdateTimer.stopTiming(); + } + } + +@@ -100,9 +98,9 @@ public class AntiXray + { + if ( world.spigotConfig.antiXray ) + { +- obfuscate.startTiming(); ++ SpigotTimings.antiXrayObfuscateTimer.startTiming(); + obfuscate( chunkX, chunkY, bitmask, buffer, world ); +- obfuscate.stopTiming(); ++ SpigotTimings.antiXrayObfuscateTimer.stopTiming(); + } + } + +diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java +index f6a67d6..cddf04b 100644 +--- a/src/main/java/org/spigotmc/SpigotConfig.java ++++ b/src/main/java/org/spigotmc/SpigotConfig.java +@@ -12,8 +12,10 @@ import java.util.HashSet; + import java.util.List; + import java.util.Map; + import java.util.Set; ++import java.util.concurrent.TimeUnit; + import java.util.logging.Level; + import gnu.trove.map.hash.TObjectIntHashMap; ++import com.google.common.collect.Lists; + import net.minecraft.server.AttributeRanged; + import net.minecraft.server.GenericAttributes; + import net.minecraft.server.MinecraftServer; +@@ -26,6 +28,8 @@ import org.bukkit.command.Command; + import org.bukkit.configuration.ConfigurationSection; + import org.bukkit.configuration.InvalidConfigurationException; + import org.bukkit.configuration.file.YamlConfiguration; ++import org.spigotmc.timings.Timings; ++import org.spigotmc.timings.TimingsManager; + + public class SpigotConfig + { +@@ -232,6 +236,39 @@ public class SpigotConfig + bungee = getBoolean( "settings.bungeecord", false ); + } + ++ private static void timings() ++ { ++ boolean timings = getBoolean( "timings.enabled", true ); ++ boolean verboseTimings = getBoolean( "timings.verbose", false ); ++ TimingsManager.privacy = getBoolean( "timings.server-name-privacy", false ); ++ TimingsManager.hiddenConfigs = getList( "timings.hidden-config-entries", Lists.newArrayList("database", "settings.bungeecord-addresses")); ++ int timingHistoryInterval = getInt( "timings.history-interval", 300 ); ++ int timingHistoryLength = getInt( "timings.history-length", 3600 ); ++ ++ ++ Timings.setVerboseTimingsEnabled( verboseTimings ); ++ Timings.setTimingsEnabled( timings ); ++ Timings.setHistoryInterval( timingHistoryInterval * 20 ); ++ Timings.setHistoryLength( timingHistoryLength * 20 ); ++ ++ Bukkit.getLogger().log( Level.INFO, "Spigot Timings: " + timings + ++ " - Verbose: " + verboseTimings + ++ " - Interval: " + timeSummary(Timings.getHistoryInterval() / 20) + ++ " - Length: " + timeSummary(Timings.getHistoryLength() / 20)); ++ } ++ protected static String timeSummary(int seconds) { ++ String time = ""; ++ if (seconds > 60*60) { ++ time += TimeUnit.SECONDS.toHours(seconds) + "h"; ++ seconds /= 60; ++ } ++ ++ if (seconds > 0) { ++ time += TimeUnit.SECONDS.toMinutes(seconds) + "m"; ++ } ++ return time; ++ } ++ + private static void nettyThreads() + { + int count = getInt( "settings.netty-threads", 4 ); +diff --git a/src/main/java/org/spigotmc/timings/SpigotTimings.java b/src/main/java/org/spigotmc/timings/SpigotTimings.java +new file mode 100644 +index 0000000..ea5b168 +--- /dev/null ++++ b/src/main/java/org/spigotmc/timings/SpigotTimings.java +@@ -0,0 +1,110 @@ ++package org.spigotmc.timings; ++ ++import net.minecraft.server.*; ++import org.bukkit.plugin.Plugin; ++import org.bukkit.scheduler.BukkitTask; ++ ++import org.bukkit.craftbukkit.scheduler.CraftTask; ++ ++public final class SpigotTimings { ++ ++ public static final Timing playerListTimer = Timings.ofSafe("Player List"); ++ public static final Timing connectionTimer = Timings.ofSafe("Connection Handler"); ++ public static final Timing tickablesTimer = Timings.ofSafe("Tickables"); ++ public static final Timing schedulerTimer = Timings.ofSafe("Scheduler"); ++ 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 worldSaveTimer = Timings.ofSafe("World Save"); ++ ++ public static final Timing tickEntityTimer = Timings.ofSafe("## tickEntity"); ++ public static final Timing tickTileEntityTimer = Timings.ofSafe("## tickTileEntity"); ++ ++ public static final Timing processQueueTimer = Timings.ofSafe("processQueue"); ++ ++ public static final Timing playerCommandTimer = Timings.ofSafe("playerCommand"); ++ ++ public static final Timing entityActivationCheckTimer = Timings.ofSafe("entityActivationCheck"); ++ public static final Timing checkIfActiveTimer = Timings.ofSafe("checkIfActive"); ++ ++ public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update"); ++ public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate"); ++ ++ private SpigotTimings() {} ++ ++ /** ++ * 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 null; ++ } ++ Plugin plugin; ++ ++ Runnable task = ((CraftTask) bukkitTask).task; ++ ++ final Class taskClass = task.getClass(); ++ if (bukkitTask.getOwner() != null) { ++ plugin = bukkitTask.getOwner(); ++ } else { ++ plugin = TimingsManager.getPluginByClassloader(taskClass); ++ } ++ ++ final String taskname; ++ if (taskClass.isAnonymousClass()) { ++ taskname = taskClass.getName(); ++ } else { ++ taskname = taskClass.getCanonicalName(); ++ } ++ ++ String name = "Task: " +taskname; ++ if (period > 0) { ++ name += " (interval:" + period +")"; ++ } else { ++ name += " (Single)"; ++ } ++ ++ if (plugin == null) { ++ return Timings.ofSafe(null, name, TimingsManager.PLUGIN_GROUP_HANDLER); ++ } ++ ++ return Timings.ofSafe(plugin, name); ++ } ++ ++ /** ++ * Get a named timer for the specified entity type to track type specific timings. ++ * @param entity ++ * @return ++ */ ++ public static Timing getEntityTimings(Entity entity) { ++ String entityType = entity.getClass().getName(); ++ return Timings.ofSafe("Minecraft", "## tickEntity - " + entityType, tickEntityTimer); ++ } ++ ++ /** ++ * Get a named timer for the specified tile entity type to track type specific timings. ++ * @param entity ++ * @return ++ */ ++ public static Timing getTileEntityTimings(TileEntity 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.getName()); ++ } ++} +diff --git a/src/main/java/org/spigotmc/timings/WorldTimingsHandler.java b/src/main/java/org/spigotmc/timings/WorldTimingsHandler.java +new file mode 100644 +index 0000000..e1c7987 +--- /dev/null ++++ b/src/main/java/org/spigotmc/timings/WorldTimingsHandler.java +@@ -0,0 +1,69 @@ ++package org.spigotmc.timings; ++ ++import net.minecraft.server.World; ++ ++/** ++ * Set of timers per world, to track world specific timings. ++ */ ++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 chunkTicksBlocks; ++ public final Timing doVillages; ++ public final Timing doChunkMap; ++ 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 tracker; ++ public final Timing doTick; ++ public final Timing tickEntities; ++ ++ public final Timing syncChunkLoadTimer; ++ public final Timing syncChunkLoadDataTimer; ++ public final Timing syncChunkLoadStructuresTimer; ++ public final Timing syncChunkLoadEntitiesTimer; ++ public final Timing syncChunkLoadTileEntitiesTimer; ++ public final Timing syncChunkLoadTileTicksTimer; ++ public final Timing syncChunkLoadPostTimer; ++ ++ public WorldTimingsHandler(World server) { ++ String name = server.worldData.getName() +" - "; ++ ++ 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"); ++ chunkTicksBlocks = Timings.ofSafe(name + "Chunk Ticks - Blocks"); ++ doVillages = Timings.ofSafe(name + "doVillages"); ++ doChunkMap = Timings.ofSafe(name + "doChunkMap"); ++ 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"); ++ ++ syncChunkLoadTimer = Timings.ofSafe(name + "syncChunkLoad"); ++ syncChunkLoadDataTimer = Timings.ofSafe(name + "syncChunkLoad - Data"); ++ syncChunkLoadStructuresTimer = Timings.ofSafe(name + "chunkLoad - Structures"); ++ syncChunkLoadEntitiesTimer = Timings.ofSafe(name + "chunkLoad - Entities"); ++ syncChunkLoadTileEntitiesTimer = Timings.ofSafe(name + "chunkLoad - TileEntities"); ++ syncChunkLoadTileTicksTimer = Timings.ofSafe(name + "chunkLoad - TileTicks"); ++ syncChunkLoadPostTimer = Timings.ofSafe(name + "chunkLoad - Post"); ++ ++ tracker = Timings.ofSafe(name + "tracker"); ++ doTick = Timings.ofSafe(name + "doTick"); ++ tickEntities = Timings.ofSafe(name + "tickEntities"); ++ } ++} +-- +2.7.0 +