/* * This file is licensed under the MIT License (MIT). * * Copyright (c) 2014 Daniel Ennis * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package co.aikar.timings; import co.aikar.util.LoadingMap; 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 java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * @deprecated Timings will likely be replaced with Spark in the future */ @Deprecated(forRemoval = true) public final class TimingsManager { static final Map TIMING_MAP = LoadingMap.of( new ConcurrentHashMap<>(4096, .5F), TimingHandler::new ); 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 String url = "https://timings.aikar.co/"; public static List hiddenConfigs = new ArrayList(); public static boolean privacy = false; static final List HANDLERS = new ArrayList<>(1024); static final List MINUTE_REPORTS = new ArrayList<>(64); static EvictingQueue HISTORY = EvictingQueue.create(12); 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(); } @NotNull static TimingHandler getHandler(@Nullable String group, @NotNull String name, @Nullable Timing parent) { return TIMING_MAP.get(new TimingIdentifier(group, name, parent)); } /** *

Due to access restrictions, we need a helper method to get a Command TimingHandler with String group

* * Plugins should never call this * * @param pluginName Plugin this command is associated with * @param command Command to get timings for * @return TimingHandler */ @NotNull public static Timing getCommandTiming(@Nullable String pluginName, @NotNull Command command) { Plugin plugin = null; final Server server = Bukkit.getServer(); if (!( server == null || pluginName == null || "minecraft".equals(pluginName) || "bukkit".equals(pluginName) || "spigot".equalsIgnoreCase(pluginName) || "paper".equals(pluginName) )) { 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 */ @Nullable public static Plugin getPluginByClassloader(@Nullable 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; } }