diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/command/NoCheatPlusCommand.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/command/NoCheatPlusCommand.java index bb88c502..017a3148 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/command/NoCheatPlusCommand.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/command/NoCheatPlusCommand.java @@ -11,24 +11,26 @@ import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.bukkit.plugin.java.JavaPlugin; +import fr.neatmonster.nocheatplus.command.actions.AllowLoginCommand; import fr.neatmonster.nocheatplus.command.actions.BanCommand; +import fr.neatmonster.nocheatplus.command.actions.DenyLoginCommand; import fr.neatmonster.nocheatplus.command.actions.KickCommand; import fr.neatmonster.nocheatplus.command.actions.KickListCommand; import fr.neatmonster.nocheatplus.command.actions.TellCommand; -import fr.neatmonster.nocheatplus.command.actions.DenyLoginCommand; -import fr.neatmonster.nocheatplus.command.actions.AllowLoginCommand; import fr.neatmonster.nocheatplus.command.actions.delay.DelayCommand; import fr.neatmonster.nocheatplus.command.admin.CommandsCommand; import fr.neatmonster.nocheatplus.command.admin.InfoCommand; import fr.neatmonster.nocheatplus.command.admin.InspectCommand; import fr.neatmonster.nocheatplus.command.admin.LagCommand; -import fr.neatmonster.nocheatplus.command.admin.VersionCommand; import fr.neatmonster.nocheatplus.command.admin.ReloadCommand; import fr.neatmonster.nocheatplus.command.admin.RemovePlayerCommand; +import fr.neatmonster.nocheatplus.command.admin.VersionCommand; import fr.neatmonster.nocheatplus.command.admin.exemption.ExemptCommand; import fr.neatmonster.nocheatplus.command.admin.exemption.ExemptionsCommand; import fr.neatmonster.nocheatplus.command.admin.exemption.UnexemptCommand; +import fr.neatmonster.nocheatplus.command.admin.log.LogCommand; import fr.neatmonster.nocheatplus.command.admin.notify.NotifyCommand; +import fr.neatmonster.nocheatplus.command.admin.reset.ResetCommand; import fr.neatmonster.nocheatplus.components.INotifyReload; import fr.neatmonster.nocheatplus.config.ConfPaths; import fr.neatmonster.nocheatplus.config.ConfigFile; @@ -77,7 +79,7 @@ public class NoCheatPlusCommand extends BaseCommand{ */ public NoCheatPlusCommand(final JavaPlugin plugin, final List notifyReload) { super(plugin, "nocheatplus", null, new String[]{"ncp"}); - // Register sub commands: + // Register sub commands (special order): for (BaseCommand cmd : new BaseCommand[]{ new BanCommand(plugin), new CommandsCommand(plugin), @@ -97,6 +99,8 @@ public class NoCheatPlusCommand extends BaseCommand{ new DenyLoginCommand(plugin), new UnexemptCommand(plugin), new AllowLoginCommand(plugin), + new LogCommand(plugin), + new ResetCommand(plugin), }){ addSubCommands(cmd); rootLabels.add(cmd.label); diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/command/admin/log/LogCommand.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/command/admin/log/LogCommand.java new file mode 100644 index 00000000..4a8e095a --- /dev/null +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/command/admin/log/LogCommand.java @@ -0,0 +1,18 @@ +package fr.neatmonster.nocheatplus.command.admin.log; + +import org.bukkit.plugin.java.JavaPlugin; + +import fr.neatmonster.nocheatplus.command.BaseCommand; +import fr.neatmonster.nocheatplus.command.admin.log.counters.CountersCommand; +import fr.neatmonster.nocheatplus.permissions.Permissions; + +public class LogCommand extends BaseCommand{ + + public LogCommand(JavaPlugin plugin) { + super(plugin, "log", Permissions.COMMAND_LOG); + addSubCommands( + new CountersCommand(plugin) + ); + } + +} diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/command/admin/log/counters/CountersCommand.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/command/admin/log/counters/CountersCommand.java new file mode 100644 index 00000000..f9f7e9c7 --- /dev/null +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/command/admin/log/counters/CountersCommand.java @@ -0,0 +1,25 @@ +package fr.neatmonster.nocheatplus.command.admin.log.counters; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.java.JavaPlugin; + +import fr.neatmonster.nocheatplus.NCPAPIProvider; +import fr.neatmonster.nocheatplus.command.BaseCommand; +import fr.neatmonster.nocheatplus.stats.Counters; + +public class CountersCommand extends BaseCommand { + + public CountersCommand(JavaPlugin plugin) { + super(plugin, "counters", null); // TODO: Maybe add a permission. + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String alias, String[] args) { + sender.sendMessage(NCPAPIProvider.getNoCheatPlusAPI().getGenericInstance(Counters.class).getMergedCountsString(true)); + return true; + } + + + +} diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/command/admin/reset/ResetCommand.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/command/admin/reset/ResetCommand.java new file mode 100644 index 00000000..204fe424 --- /dev/null +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/command/admin/reset/ResetCommand.java @@ -0,0 +1,23 @@ +package fr.neatmonster.nocheatplus.command.admin.reset; + +import org.bukkit.plugin.java.JavaPlugin; + +import fr.neatmonster.nocheatplus.command.BaseCommand; +import fr.neatmonster.nocheatplus.command.admin.reset.counters.CountersCommand; +import fr.neatmonster.nocheatplus.permissions.Permissions; + +/** + * Reset stuff, e.g. statistics counters. + * @author dev1mc + * + */ +public class ResetCommand extends BaseCommand{ + + public ResetCommand(JavaPlugin plugin) { + super(plugin, "reset", Permissions.COMMAND_RESET); + addSubCommands( + new CountersCommand(plugin) + ); + } + +} diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/command/admin/reset/counters/CountersCommand.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/command/admin/reset/counters/CountersCommand.java new file mode 100644 index 00000000..ac4f4acb --- /dev/null +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/command/admin/reset/counters/CountersCommand.java @@ -0,0 +1,24 @@ +package fr.neatmonster.nocheatplus.command.admin.reset.counters; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.java.JavaPlugin; + +import fr.neatmonster.nocheatplus.NCPAPIProvider; +import fr.neatmonster.nocheatplus.command.BaseCommand; +import fr.neatmonster.nocheatplus.stats.Counters; + +public class CountersCommand extends BaseCommand { + + public CountersCommand(JavaPlugin plugin) { + super(plugin, "counters", null); + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String alias, String[] args) { + NCPAPIProvider.getNoCheatPlusAPI().getGenericInstance(Counters.class).resetAll(); + sender.sendMessage("Counters reset."); + return true; + } + +} diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/permissions/Permissions.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/permissions/Permissions.java index 63c16c0c..82d2f04f 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/permissions/Permissions.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/permissions/Permissions.java @@ -30,9 +30,11 @@ public class Permissions { public static final String COMMAND_INFO = COMMAND + ".info"; public static final String COMMAND_INSPECT = COMMAND + ".inspect"; public static final String COMMAND_LAG = COMMAND + ".lag"; + public static final String COMMAND_LOG = COMMAND + ".log"; public static final String COMMAND_NOTIFY = COMMAND + ".notify"; public static final String COMMAND_RELOAD = COMMAND + ".reload"; public static final String COMMAND_REMOVEPLAYER = COMMAND + ".removeplayer"; + public static final String COMMAND_RESET = COMMAND + ".reset"; public static final String COMMAND_UNEXEMPT = COMMAND + ".unexempt"; public static final String COMMAND_VERSION = COMMAND + ".version"; diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/stats/Counters.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/stats/Counters.java new file mode 100644 index 00000000..780bdbdd --- /dev/null +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/stats/Counters.java @@ -0,0 +1,188 @@ +package fr.neatmonster.nocheatplus.stats; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.bukkit.Bukkit; + +/** + * Utility to count things, set up to get input from the primary server thread, as + * well as from other threads. Consequently summaries for both are only + * available from the primary thread. + * + * @author dev1mc + * + */ +public class Counters { + + /** Map strings for display/processing to "fast-access" ids. */ + private final Map idMap = new LinkedHashMap(); + + /** Keys by id. */ + private String[] keys = new String[0]; + // Not sure if to use longs. + /** Primary thread. */ + private int[] ptCounts = new int[0]; + /** Synchronized. */ + private int[] syCounts = new int[0]; + // TODO: Consider adding extra counts or ActionFrequency to track "n per minute". + + /** + * Register a key and return the id that is used for access. If the key is already registered, the registered id is returned.
+ * Must only be called from the primary thread, or during (encapsulated) initialization. + * @param key + * @return The id to be used for adding to counts. + */ + public int registerKey(String key) { + if (key == null) { + throw new NullPointerException("Key must not be null."); + } + Integer registeredId = idMap.get(key); + if (registeredId != null) { + return registeredId.intValue(); + } + final int newId = ptCounts.length; + idMap.put(key, newId); + keys = Arrays.copyOf(keys, newId + 1); + keys[newId] = key; + ptCounts = Arrays.copyOf(ptCounts, newId + 1); + synchronized (syCounts) { + syCounts = Arrays.copyOf(syCounts, newId + 1); + } + return newId; + } + + /** + * Convenience method for quick testing / uncertain contexts, checks + * Bukkit.isPrimaryThread(), then delegates, thus is slower. + * + * @param id + * @param count + */ + public void add(int id, int count) { + if (Bukkit.isPrimaryThread()) { + addPrimaryThread(id, count); + } else { + addSynchronized(id, count); + } + } + + /** + * Only call from the primary thread. + * @param id + * @param count + */ + public void addPrimaryThread(int id, int count) { + ptCounts[id] += count; + } + + /** + * Call from any thread. + * @param id + * @param count + */ + public void addSynchronized(int id, int count) { + synchronized (syCounts) { + syCounts[id] += count; + } + } + + /** + * Reset all counters to 0.
+ * Must only be called from the primary thread. + */ + public void resetAll() { + for (int i = 0; i < ptCounts.length; i ++) { + ptCounts[i] = 0; + } + synchronized (syCounts) { + for (int i = 0; i < syCounts.length; i ++) { + syCounts[i] = 0; + } + } + } + + public Map getPrimaryThreadCounts() { + final Map counts = new LinkedHashMap(); + final int length = keys.length; + for (int i = 0; i < length; i++) { + counts.put(keys[i], ptCounts[i]); + } + return counts; + } + + public Map getSynchronizedCounts() { + final Map counts = new LinkedHashMap(); + final int[] syCounts; + synchronized (this.syCounts) { + syCounts = Arrays.copyOf(this.syCounts, this.syCounts.length); + } + for (int i = 0; i < syCounts.length; i++) { + counts.put(keys[i], syCounts[i]); + } + return counts; + } + + /** + * Get a map for keys to counts, preserving the registration order of keys + * for iteration (LinkedHashMap).
+ * Only call from the primary thread. + * + * @return + */ + public Map getMergedCounts() { + final Map counts = new LinkedHashMap(); + final int[] syCounts; + synchronized (this.syCounts) { + syCounts = Arrays.copyOf(this.syCounts, this.syCounts.length); + } + for (int i = 0; i < syCounts.length; i++) { + counts.put(keys[i], syCounts[i] + ptCounts[i]); + } + return counts; + } + + /** + * Return a String (one line), which summarizes the contents: key merged-count.
+ * Only call in the primary thread. + * @return + */ + public String getMergedCountsString() { + return getMergedCountsString(false); + } + + /** + * Return a String (one line), which summarizes the contents: key merged-count (pt count / sy count).
+ * Only call in the primary thread. + * @param details If to show difference of primary thread / synchronized. + * @return + */ + public String getMergedCountsString(final boolean details) { + final StringBuilder builder = new StringBuilder(1024); + final Map syCounts = getSynchronizedCounts(); + final Map ptCounts = getPrimaryThreadCounts(); + builder.append('|'); + for (final Entry entry : ptCounts.entrySet()) { + final String key = entry.getKey(); + builder.append(' '); + builder.append(key); + builder.append(' '); + final int pt = entry.getValue(); + final int sy = syCounts.get(key); + final int sum = pt + sy; + builder.append(Integer.toString(sum)); + if (details && sum > 0) { + builder.append(" ("); + builder.append(Integer.toString(pt)); + builder.append('/'); + builder.append(Integer.toString(sy)); + builder.append(')'); + } + builder.append(" |"); + } + return builder.toString(); + } + +} diff --git a/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/NoCheatPlus.java b/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/NoCheatPlus.java index 7937a40e..5de66022 100644 --- a/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/NoCheatPlus.java +++ b/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/NoCheatPlus.java @@ -76,6 +76,7 @@ import fr.neatmonster.nocheatplus.permissions.Permissions; import fr.neatmonster.nocheatplus.players.DataManager; import fr.neatmonster.nocheatplus.players.PlayerData; import fr.neatmonster.nocheatplus.players.PlayerMessageSender; +import fr.neatmonster.nocheatplus.stats.Counters; import fr.neatmonster.nocheatplus.updates.Updates; import fr.neatmonster.nocheatplus.utilities.BlockProperties; import fr.neatmonster.nocheatplus.utilities.ColorUtil; @@ -552,6 +553,7 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI { // Stop consistency checking task. if (consistencyCheckerTaskId != -1){ sched.cancelTask(consistencyCheckerTaskId); + consistencyCheckerTaskId = -1; } // Just to be sure nothing gets left out. @@ -574,6 +576,12 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI { } } + // Write some debug/statistics. + final Counters counters = getGenericInstance(Counters.class); + if (counters != null) { + LogUtil.logInfo(counters.getMergedCountsString(true)); + } + // Hooks: // (Expect external plugins to unregister their hooks on their own.) // (No native hooks present, yet.) @@ -688,6 +696,10 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI { TickTask.cancel(); TickTask.reset(); + // Register some generic stuff. + // Counters: debugging purposes, maybe integrated for statistics later. + registerGenericInstance(new Counters()); + // Read the configuration files. ConfigManager.init(this); diff --git a/NCPPlugin/src/main/resources/plugin.yml b/NCPPlugin/src/main/resources/plugin.yml index ecc066e9..594d772b 100644 --- a/NCPPlugin/src/main/resources/plugin.yml +++ b/NCPPlugin/src/main/resources/plugin.yml @@ -299,6 +299,10 @@ permissions: description: Allow use of the ncp removeplayer command. nocheatplus.command.commands: description: Allow use of the ncp commands command. + nocheatplus.command.log: + description: Show various stats/debugging information. [Incomplete, experimental.] + nocheatplus.command.reset: + description: Reset statistics or debugging counters. # Legacy: nocheatplus.command.tempkick: description: Obsolete, use nocheatplus.command.denylogin instead.