Add a general purpose counter for stats/debug, and log+reset commands.

This may get changed around, e.g. to allow log output to file and other.
This commit is contained in:
asofold 2014-07-27 18:26:58 +02:00
parent e0f81b43b6
commit 4176937dd1
9 changed files with 304 additions and 4 deletions

View File

@ -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<INotifyReload> 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);

View File

@ -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)
);
}
}

View File

@ -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;
}
}

View File

@ -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)
);
}
}

View File

@ -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;
}
}

View File

@ -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";

View File

@ -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<String, Integer> idMap = new LinkedHashMap<String, Integer>();
/** 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.<br>
* 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.<br>
* 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<String, Integer> getPrimaryThreadCounts() {
final Map<String, Integer> counts = new LinkedHashMap<String, Integer>();
final int length = keys.length;
for (int i = 0; i < length; i++) {
counts.put(keys[i], ptCounts[i]);
}
return counts;
}
public Map<String, Integer> getSynchronizedCounts() {
final Map<String, Integer> counts = new LinkedHashMap<String, Integer>();
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).<br>
* Only call from the primary thread.
*
* @return
*/
public Map<String, Integer> getMergedCounts() {
final Map<String, Integer> counts = new LinkedHashMap<String, Integer>();
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.<br>
* 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).<br>
* 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<String, Integer> syCounts = getSynchronizedCounts();
final Map<String, Integer> ptCounts = getPrimaryThreadCounts();
builder.append('|');
for (final Entry<String, Integer> 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();
}
}

View File

@ -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);

View File

@ -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.