aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorZach Brown <[email protected]>2016-01-09 00:32:38 -0600
committerZach Brown <[email protected]>2016-01-09 00:32:38 -0600
commit9172103531f0d63bf233113ba7ce21b5b826143d (patch)
treeb48ad1904a994de0465045bad431d9f267bfa3e3
parente9c23b0c389ddae3ecb978d9dc3bdc978ecb2c6d (diff)
downloadPaper-9172103531f0d63bf233113ba7ce21b5b826143d.tar.gz
Paper-9172103531f0d63bf233113ba7ce21b5b826143d.zip
Migrate PaperSpigot to Timings version 2
-rw-r--r--Spigot-API-Patches/0014-Require-Java-8.patch6
-rw-r--r--Spigot-API-Patches/0015-Timings-v2.patch3427
-rw-r--r--Spigot-Server-Patches/0002-PaperSpigot-config-files.patch14
-rw-r--r--Spigot-Server-Patches/0081-Timings-v2.patch1146
4 files changed, 4583 insertions, 10 deletions
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 <[email protected]>
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
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
-@@ -136,26 +137,6 @@
+@@ -129,26 +130,6 @@
</dependencies>
</plugin>
<plugin>
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 <[email protected]>
+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 @@
+
+ <dependencies>
+ <dependency>
++ <groupId>net.sf.trove4j</groupId>
++ <artifactId>trove4j</artifactId>
++ <version>3.0.3</version>
++ <!-- Trove Provided by CraftBukkit -->
++ <scope>provided</scope>
++ </dependency>
++ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ <version>2.6</version>
+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<String>());
+@@ -45,7 +46,6 @@ public abstract class Command {
+ this.usageMessage = usageMessage;
+ this.aliases = aliases;
+ this.activeAliases = new ArrayList<String>(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<String> TIMINGS_SUBCOMMANDS = ImmutableList.of("report", "reset", "on", "off", "paste"); // Spigot
+- public static long timingStart = 0; // Spigot
++ public static final List<String> 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 <reset|report|on|off|paste>"; // Spigot
++ this.description = "Records timings for all plugin events";
++ this.usageMessage = "/timings <reset>";
+ 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( "<spigotConfig>" );
+- fileTimings.println( Bukkit.spigot().getConfig().saveToString() );
+- fileTimings.println( "</spigotConfig>" );
+-
+- 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<String, Class<?>> classes = new java.util.concurrent.ConcurrentHashMap<String, Class<?>>(); // Spigot
+ private final Map<String, PluginClassLoader> loaders = new LinkedHashMap<String, PluginClassLoader>();
+- 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<String, Class<?>> classes = new java.util.concurrent.ConcurrentHashMap<String, Class<?>>(); // 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 <http://aikar.co>
++ *
++ * 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<CustomTimingsHandler> HANDLERS = new ConcurrentLinkedQueue<CustomTimingsHandler>();
+- /*========================================================================*/
+- 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 <http://aikar.co>
++ *
++ * 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 <http://aikar.co>
++ *
++ * 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<? extends Event> 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 <http://aikar.co>
++ *
++ * 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.
++ * <p/>
++ * 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.
++ * <p/>
++ * 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 <http://aikar.co>
++ *
++ * 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
++ * <p/>
++ * This is broken out to reduce memory usage
++ */
++class TimingData {
++ static Function<Integer, TimingData> LOADER = new Function<Integer, TimingData>() {
++ @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 <http://aikar.co>
++ *
++ * 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<TimingData> children = new LoadingIntMap<TimingData>(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 <http://aikar.co>
++ *
++ * 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<String, Integer> worldMap = LoadingMap.newHashMap(new Function<String, Integer>() {
++ @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<Material> tileEntityTypeSet = Sets.newHashSet();
++ final Set<EntityType> entityTypeSet = Sets.newHashSet();
++ final Map<Object, Object> 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<EntityType, Counter> entityCounts = MRUMapCache.of(LoadingMap.of(
++ new EnumMap<EntityType, Counter>(EntityType.class), Counter.LOADER
++ ));
++ final Map<Material, Counter> tileEntityCounts = MRUMapCache.of(LoadingMap.of(
++ new EnumMap<Material, Counter>(Material.class), Counter.LOADER
++ ));
++ // Information about all loaded chunks/entities
++ this.worlds = toObjectMapper(Bukkit.getWorlds(), new Function<World, JSONPair>() {
++ @Override
++ public JSONPair apply(World world) {
++ return pair(
++ worldMap.get(world.getName()),
++ toArrayMapper(world.getLoadedChunks(), new Function<Chunk, Object>() {
++ @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<Map.Entry<EntityType, Counter>, JSONPair>() {
++ @Override
++ public JSONPair apply(Map.Entry<EntityType, Counter> entry) {
++ entityTypeSet.add(entry.getKey());
++ return pair(
++ String.valueOf(entry.getKey().getTypeId()),
++ entry.getValue().count()
++ );
++ }
++ }
++ ),
++ toObjectMapper(tileEntityCounts.entrySet(),
++ new Function<Map.Entry<Material, Counter>, JSONPair>() {
++ @Override
++ public JSONPair apply(Map.Entry<Material, Counter> 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<TimingHistoryEntry, Object>() {
++ @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<MinuteReport, Object>() {
++ @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<? extends Player> 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<Counter>() {
++ @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 <http://aikar.co>
++ *
++ * 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<TimingData, Object>() {
++ @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 <http://aikar.co>
++ *
++ * 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.
++ * <p/>
++ * 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<String, TimingGroup> GROUP_MAP = MRUMapCache.of(
++ LoadingMap.newIdentityHashMap(new Function<String, TimingGroup>() {
++ @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<TimingHandler> handlers = new ArrayDeque<TimingHandler>(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 <http://aikar.co>
++ *
++ * 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.
++ * <p/>
++ * 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
++ * <p/>
++ * 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.
++ * <p/>
++ * 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.
++ * <p/>
++ * 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.
++ * <p/>
++ * Defaults to 5 minutes (6000 ticks)
++ *
++ * @return Interval in ticks
++ */
++ public static int getHistoryInterval() {
++ return historyInterval;
++ }
++
++ /**
++ * Sets the interval between Timing History report generations.
++ * <p/>
++ * 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<TimingHistory> 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 <http://aikar.co>
++ *
++ * 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<String> 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 <reset|report|on|off|verbon|verboff>";
++ 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<String> 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<String>(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 <http://aikar.co>
++ *
++ * 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<GarbageCollectorMXBean, JSONPair>() {
++ @Override
++ public JSONPair apply(GarbageCollectorMXBean input) {
++ return pair(input.getName(), toArray(input.getCollectionCount(), input.getCollectionTime()));
++ }
++ }))
++ )
++ );
++
++ Set<Material> tileEntityTypeSet = Sets.newHashSet();
++ Set<EntityType> 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<TimingIdentifier.TimingGroup, JSONPair>() {
++ @Override
++ public JSONPair apply(TimingIdentifier.TimingGroup group) {
++ return pair(group.id, group.name);
++ }
++ })),
++ pair("handlers", handlers),
++ pair("worlds", toObjectMapper(TimingHistory.worldMap.entrySet(), new Function<Map.Entry<String, Integer>, JSONPair>() {
++ @Override
++ public JSONPair apply(Map.Entry<String, Integer> input) {
++ return pair(input.getValue(), input.getKey());
++ }
++ })),
++ pair("tileentity",
++ toObjectMapper(tileEntityTypeSet, new Function<Material, JSONPair>() {
++ @Override
++ public JSONPair apply(Material input) {
++ return pair(input.getId(), input.name());
++ }
++ })),
++ pair("entity",
++ toObjectMapper(entityTypeSet, new Function<EntityType, JSONPair>() {
++ @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<Plugin, JSONPair>() {
++ @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<Object> v = (Iterable<Object>) val;
++ return toArrayMapper(v, new Function<Object, Object>() {
++ @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<TimingHistory, Object>() {
++ @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 <http://aikar.co>
++ *
++ * 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<TimingIdentifier, TimingHandler> TIMING_MAP =
++ Collections.synchronizedMap(LoadingMap.newHashMap(
++ new Function<TimingIdentifier, TimingHandler>() {
++ @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<String> hiddenConfigs = new ArrayList<String>();
++ public static boolean privacy = false;
++
++ static final Collection<TimingHandler> HANDLERS = new ArrayDeque<TimingHandler>();
++ static final ArrayDeque<TimingHistory.MinuteReport> MINUTE_REPORTS = new ArrayDeque<TimingHistory.MinuteReport>();
++
++ static EvictingQueue<TimingHistory> 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
++ * <p/>
++ * 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 <http://aikar.co>
++ *
++ * 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 <E>
++ * @return
++ */
++ public static <E> List toArrayMapper(E[] collection, Function<E, Object> mapper) {
++ return toArrayMapper(Lists.newArrayList(collection), mapper);
++ }
++
++ public static <E> List toArrayMapper(Iterable<E> collection, Function<E, Object> 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 <E>
++ * @return
++ */
++ public static <E> Map toObjectMapper(E[] collection, Function<E, JSONPair> mapper) {
++ return toObjectMapper(Lists.newArrayList(collection), mapper);
++ }
++
++ public static <E> Map toObjectMapper(Iterable<E> collection, Function<E, JSONPair> 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 <K> Key
++ * @param <V> Value
++ */
++public class LoadingIntMap<V> extends TIntObjectHashMap<V> {
++ private final Function<Integer, V> loader;
++
++ /**
++ * Initializes an auto loading map using specified loader and backing map
++ * @param backingMap
++ * @param loader
++ */
++ public LoadingIntMap(Function<Integer, V> 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 <T>
++ */
++ public abstract static class Feeder <T> implements Function<T, T> {
++ @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 <http://aikar.co>
++ *
++ * 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 <K> Key
++ * @param <V> Value
++ */
++public class LoadingMap <K,V> extends AbstractMap<K, V> {
++ private final Map<K, V> backingMap;
++ private final Function<K, V> loader;
++
++ /**
++ * Initializes an auto loading map using specified loader and backing map
++ * @param backingMap
++ * @param loader
++ */
++ public LoadingMap(Map<K, V> backingMap, Function<K, V> loader) {
++ this.backingMap = backingMap;
++ this.loader = loader;
++ }
++
++ /**
++ * Creates a new LoadingMap with the specified map and loader
++ * @param backingMap
++ * @param loader
++ * @param <K>
++ * @param <V>
++ * @return
++ */
++ public static <K, V> Map<K, V> of(Map<K, V> backingMap, Function<K, V> loader) {
++ return new LoadingMap<K, V>(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 <K> Key Type of the Map
++ * @param <V> Value Type of the Map
++ * @return Map that auto instantiates on .get()
++ */
++ public static <K, V> Map<K, V> newAutoMap(Map<K, V> backingMap, final Class<? extends K> keyClass,
++ final Class<? extends V> valueClass) {
++ return new LoadingMap<K, V>(backingMap, new AutoInstantiatingLoader<K, V>(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 <K> Key Type of the Map
++ * @param <V> Value Type of the Map
++ * @return Map that auto instantiates on .get()
++ */
++ public static <K, V> Map<K, V> newAutoMap(Map<K, V> backingMap,
++ final Class<? extends V> valueClass) {
++ return newAutoMap(backingMap, null, valueClass);
++ }
++
++ /**
++ * @see #newAutoMap
++ *
++ * new Auto initializing map using a HashMap.
++ * @param keyClass
++ * @param valueClass
++ * @param <K>
++ * @param <V>
++ * @return
++ */
++ public static <K, V> Map<K, V> newHashAutoMap(final Class<? extends K> keyClass, final Class<? extends V> valueClass) {
++ return newAutoMap(new HashMap<K, V>(), keyClass, valueClass);
++ }
++
++ /**
++ * @see #newAutoMap
++ *
++ * new Auto initializing map using a HashMap.
++ * @param valueClass
++ * @param <K>
++ * @param <V>
++ * @return
++ */
++ public static <K, V> Map<K, V> newHashAutoMap(final Class<? extends V> valueClass) {
++ return newHashAutoMap(null, valueClass);
++ }
++
++ /**
++ * @see #newAutoMap
++ *
++ * new Auto initializing map using a HashMap.
++ *
++ * @param keyClass
++ * @param valueClass
++ * @param initialCapacity
++ * @param loadFactor
++ * @param <K>
++ * @param <V>
++ * @return
++ */
++ public static <K, V> Map<K, V> newHashAutoMap(final Class<? extends K> keyClass, final Class<? extends V> valueClass, int initialCapacity, float loadFactor) {
++ return newAutoMap(new HashMap<K, V>(initialCapacity, loadFactor), keyClass, valueClass);
++ }
++
++ /**
++ * @see #newAutoMap
++ *
++ * new Auto initializing map using a HashMap.
++ *
++ * @param valueClass
++ * @param initialCapacity
++ * @param loadFactor
++ * @param <K>
++ * @param <V>
++ * @return
++ */
++ public static <K, V> Map<K, V> newHashAutoMap(final Class<? extends V> valueClass, int initialCapacity, float loadFactor) {
++ return newHashAutoMap(null, valueClass, initialCapacity, loadFactor);
++ }
++
++ /**
++ * Initializes an auto loading map using a HashMap
++ * @param loader
++ * @param <K>
++ * @param <V>
++ * @return
++ */
++ public static <K, V> Map<K, V> newHashMap(Function<K, V> loader) {
++ return new LoadingMap<K, V>(new HashMap<K, V>(), loader);
++ }
++
++ /**
++ * Initializes an auto loading map using a HashMap
++ * @param loader
++ * @param initialCapacity
++ * @param loadFactor
++ * @param <K>
++ * @param <V>
++ * @return
++ */
++ public static <K, V> Map<K, V> newHashMap(Function<K, V> loader, int initialCapacity, float loadFactor) {
++ return new LoadingMap<K, V>(new HashMap<K, V>(initialCapacity, loadFactor), loader);
++ }
++
++ /**
++ * Initializes an auto loading map using an Identity HashMap
++ * @param loader
++ * @param <K>
++ * @param <V>
++ * @return
++ */
++ public static <K, V> Map<K, V> newIdentityHashMap(Function<K, V> loader) {
++ return new LoadingMap<K, V>(new IdentityHashMap<K, V>(), loader);
++ }
++
++ /**
++ * Initializes an auto loading map using an Identity HashMap
++ * @param loader
++ * @param initialCapacity
++ * @param <K>
++ * @param <V>
++ * @return
++ */
++ public static <K, V> Map<K, V> newIdentityHashMap(Function<K, V> loader, int initialCapacity) {
++ return new LoadingMap<K, V>(new IdentityHashMap<K, V>(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<? extends K, ? extends V> m) {backingMap.putAll(m);}
++
++ @Override
++ public void clear() {backingMap.clear();}
++
++ @Override
++ public Set<K> keySet() {return backingMap.keySet();}
++
++ @Override
++ public Collection<V> values() {return backingMap.values();}
++
++ @Override
++ public boolean equals(Object o) {return backingMap.equals(o);}
++
++ @Override
++ public int hashCode() {return backingMap.hashCode();}
++
++ @Override
++ public Set<Entry<K, V>> entrySet() {
++ return backingMap.entrySet();
++ }
++
++ public LoadingMap<K, V> clone() {
++ return new LoadingMap<K, V>(backingMap, loader);
++ }
++
++ private static class AutoInstantiatingLoader<K, V> implements Function<K, V> {
++ final Constructor<? extends V> constructor;
++ private final Class<? extends V> valueClass;
++
++ AutoInstantiatingLoader(Class<? extends K> keyClass, Class<? extends V> 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 <T>
++ */
++ public abstract static class Feeder <T> implements Function<T, T> {
++ @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 <http://aikar.co>
++ *
++ * 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 <K>
++ * @param <V>
++ */
++public class MRUMapCache<K, V> extends AbstractMap<K, V> {
++ final Map<K, V> backingMap;
++ Object cacheKey;
++ V cacheValue;
++ public MRUMapCache(final Map<K, V> 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<? extends K, ? extends V> m) {backingMap.putAll(m);}
++
++ public void clear() {
++ cacheKey = null;
++ cacheValue = null;
++ backingMap.clear();
++ }
++
++ public Set<K> keySet() {return backingMap.keySet();}
++
++ public Collection<V> values() {return backingMap.values();}
++
++ public Set<Map.Entry<K, V>> entrySet() {return backingMap.entrySet();}
++
++ /**
++ * Wraps the specified map with a most recently used cache
++ * @param map
++ * @param <K>
++ * @param <V>
++ * @return
++ */
++ public static <K, V> Map<K, V> of(Map<K, V> map) {
++ return new MRUMapCache<K, V>(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 <[email protected]>
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<String, Command> 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 <[email protected]>
+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<String, Class<? extends TileEntity>> f = Maps.newHashMap();
+ private static Map<Class<? extends TileEntity>, 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<String, CustomTimingsHandler> entityTypeTimingMap = new HashMap<String, CustomTimingsHandler>();
+- public static final HashMap<String, CustomTimingsHandler> tileEntityTypeTimingMap = new HashMap<String, CustomTimingsHandler>();
+- public static final HashMap<String, CustomTimingsHandler> pluginTaskTimingMap = new HashMap<String, CustomTimingsHandler>();
+-
+- /**
+- * 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<? extends Runnable> 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
+