Add the ability to track the amount of time spent by each plugin.
ProtocolLib can now keep track of the amount of time spent by each listener (for each packet), generated as a report file. This is done in the new "protocol timings" command.
Dieser Commit ist enthalten in:
Ursprung
4e2af45428
Commit
2001c15132
@ -83,6 +83,30 @@ abstract class CommandBase implements CommandExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a boolean value at a specific location.
|
||||||
|
* @param args - the argument array.
|
||||||
|
* @param parameterName - the parameter name.
|
||||||
|
* @param index - the argument index.
|
||||||
|
* @return The parsed boolean, or NULL if not valid.
|
||||||
|
*/
|
||||||
|
protected Boolean parseBoolean(String[] args, String parameterName, int index) {
|
||||||
|
if (index < args.length) {
|
||||||
|
String arg = args[index];
|
||||||
|
|
||||||
|
if (arg.equalsIgnoreCase("true") || arg.equalsIgnoreCase("on"))
|
||||||
|
return true;
|
||||||
|
else if (arg.equalsIgnoreCase(parameterName))
|
||||||
|
return true;
|
||||||
|
else if (arg.equalsIgnoreCase("false") || arg.equalsIgnoreCase("off"))
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the permission necessary to execute this command.
|
* Retrieve the permission necessary to execute this command.
|
||||||
* @return The permission, or NULL if not needed.
|
* @return The permission, or NULL if not needed.
|
||||||
|
@ -543,20 +543,4 @@ class CommandPacket extends CommandBase {
|
|||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse a boolean
|
|
||||||
private Boolean parseBoolean(String[] args, String parameterName, int index) {
|
|
||||||
if (index < args.length) {
|
|
||||||
if (args[index].equalsIgnoreCase("true"))
|
|
||||||
return true;
|
|
||||||
else if (args[index].equalsIgnoreCase(parameterName))
|
|
||||||
return true;
|
|
||||||
else if (args[index].equalsIgnoreCase("false"))
|
|
||||||
return false;
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package com.comphenix.protocol;
|
package com.comphenix.protocol;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.bukkit.ChatColor;
|
import org.bukkit.ChatColor;
|
||||||
@ -29,6 +30,8 @@ import com.comphenix.protocol.error.ReportType;
|
|||||||
import com.comphenix.protocol.metrics.Updater;
|
import com.comphenix.protocol.metrics.Updater;
|
||||||
import com.comphenix.protocol.metrics.Updater.UpdateResult;
|
import com.comphenix.protocol.metrics.Updater.UpdateResult;
|
||||||
import com.comphenix.protocol.metrics.Updater.UpdateType;
|
import com.comphenix.protocol.metrics.Updater.UpdateType;
|
||||||
|
import com.comphenix.protocol.timing.TimedListenerManager;
|
||||||
|
import com.comphenix.protocol.timing.TimingReportGenerator;
|
||||||
import com.comphenix.protocol.utility.WrappedScheduler;
|
import com.comphenix.protocol.utility.WrappedScheduler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,6 +71,8 @@ class CommandProtocol extends CommandBase {
|
|||||||
checkVersion(sender);
|
checkVersion(sender);
|
||||||
else if (subCommand.equalsIgnoreCase("update"))
|
else if (subCommand.equalsIgnoreCase("update"))
|
||||||
updateVersion(sender);
|
updateVersion(sender);
|
||||||
|
else if (subCommand.equalsIgnoreCase("timings"))
|
||||||
|
toggleTimings(sender, args);
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
@ -119,6 +124,55 @@ class CommandProtocol extends CommandBase {
|
|||||||
updateFinished();
|
updateFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void toggleTimings(CommandSender sender, String[] args) {
|
||||||
|
TimedListenerManager manager = TimedListenerManager.getInstance();
|
||||||
|
boolean state = !manager.isTiming(); // toggle
|
||||||
|
|
||||||
|
// Parse the boolean parameter
|
||||||
|
if (args.length == 2) {
|
||||||
|
Boolean parsed = parseBoolean(args, "start", 2);
|
||||||
|
|
||||||
|
if (parsed != null) {
|
||||||
|
state = parsed;
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(ChatColor.RED + "Specify a state: ON or OFF.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (args.length > 2) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "Too many parameters.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now change the state
|
||||||
|
if (state) {
|
||||||
|
if (manager.startTiming())
|
||||||
|
sender.sendMessage(ChatColor.GOLD + "Started timing packet listeners.");
|
||||||
|
else
|
||||||
|
sender.sendMessage(ChatColor.RED + "Packet timing already started.");
|
||||||
|
} else {
|
||||||
|
if (manager.stopTiming()) {
|
||||||
|
saveTimings(manager);
|
||||||
|
sender.sendMessage(ChatColor.GOLD + "Stopped and saved result in plugin folder.");
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(ChatColor.RED + "Packet timing already stopped.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveTimings(TimedListenerManager manager) {
|
||||||
|
try {
|
||||||
|
File destination = new File(plugin.getDataFolder(), "Timings - " + System.currentTimeMillis() + ".txt");
|
||||||
|
TimingReportGenerator generator = new TimingReportGenerator();
|
||||||
|
|
||||||
|
// Print to a text file
|
||||||
|
generator.saveTo(destination, manager);
|
||||||
|
manager.clear();
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
reporter.reportMinimal(plugin, "saveTimings()", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isHttpError(Exception e) {
|
private boolean isHttpError(Exception e) {
|
||||||
Throwable cause = e.getCause();
|
Throwable cause = e.getCause();
|
||||||
|
|
||||||
|
@ -30,6 +30,9 @@ import com.comphenix.protocol.events.ListeningWhitelist;
|
|||||||
import com.comphenix.protocol.events.PacketAdapter;
|
import com.comphenix.protocol.events.PacketAdapter;
|
||||||
import com.comphenix.protocol.events.PacketEvent;
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
import com.comphenix.protocol.events.PacketListener;
|
import com.comphenix.protocol.events.PacketListener;
|
||||||
|
import com.comphenix.protocol.timing.TimedListenerManager;
|
||||||
|
import com.comphenix.protocol.timing.TimedTracker;
|
||||||
|
import com.comphenix.protocol.timing.TimedListenerManager.ListenerType;
|
||||||
import com.comphenix.protocol.utility.WrappedScheduler;
|
import com.comphenix.protocol.utility.WrappedScheduler;
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
@ -84,6 +87,9 @@ public class AsyncListenerHandler {
|
|||||||
// Minecraft main thread
|
// Minecraft main thread
|
||||||
private Thread mainThread;
|
private Thread mainThread;
|
||||||
|
|
||||||
|
// Timing manager
|
||||||
|
private TimedListenerManager timedManager = TimedListenerManager.getInstance();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a manager for an asynchronous packet handler.
|
* Construct a manager for an asynchronous packet handler.
|
||||||
* @param mainThread - the main game thread.
|
* @param mainThread - the main game thread.
|
||||||
@ -575,10 +581,27 @@ public class AsyncListenerHandler {
|
|||||||
marker.setListenerHandler(this);
|
marker.setListenerHandler(this);
|
||||||
marker.setWorkerID(workerID);
|
marker.setWorkerID(workerID);
|
||||||
|
|
||||||
|
// We're not THAT worried about performance here
|
||||||
|
if (timedManager.isTiming()) {
|
||||||
|
// Retrieve the tracker to use
|
||||||
|
TimedTracker tracker = timedManager.getTracker(listener,
|
||||||
|
packet.isServerPacket() ? ListenerType.ASYNC_SERVER_SIDE : ListenerType.ASYNC_CLIENT_SIDE);
|
||||||
|
long token = tracker.beginTracking();
|
||||||
|
|
||||||
if (packet.isServerPacket())
|
if (packet.isServerPacket())
|
||||||
listener.onPacketSending(packet);
|
listener.onPacketSending(packet);
|
||||||
else
|
else
|
||||||
listener.onPacketReceiving(packet);
|
listener.onPacketReceiving(packet);
|
||||||
|
|
||||||
|
// And we're done
|
||||||
|
tracker.endTracking(token, packet.getPacketID());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (packet.isServerPacket())
|
||||||
|
listener.onPacketSending(packet);
|
||||||
|
else
|
||||||
|
listener.onPacketReceiving(packet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
@ -25,6 +25,9 @@ import com.comphenix.protocol.error.ErrorReporter;
|
|||||||
import com.comphenix.protocol.events.ListenerPriority;
|
import com.comphenix.protocol.events.ListenerPriority;
|
||||||
import com.comphenix.protocol.events.PacketEvent;
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
import com.comphenix.protocol.events.PacketListener;
|
import com.comphenix.protocol.events.PacketListener;
|
||||||
|
import com.comphenix.protocol.timing.TimedListenerManager;
|
||||||
|
import com.comphenix.protocol.timing.TimedTracker;
|
||||||
|
import com.comphenix.protocol.timing.TimedListenerManager.ListenerType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registry of synchronous packet listeners.
|
* Registry of synchronous packet listeners.
|
||||||
@ -32,6 +35,9 @@ import com.comphenix.protocol.events.PacketListener;
|
|||||||
* @author Kristian
|
* @author Kristian
|
||||||
*/
|
*/
|
||||||
public final class SortedPacketListenerList extends AbstractConcurrentListenerMultimap<PacketListener> {
|
public final class SortedPacketListenerList extends AbstractConcurrentListenerMultimap<PacketListener> {
|
||||||
|
// The current listener manager
|
||||||
|
private TimedListenerManager timedManager = TimedListenerManager.getInstance();
|
||||||
|
|
||||||
public SortedPacketListenerList() {
|
public SortedPacketListenerList() {
|
||||||
super(Packets.MAXIMUM_PACKET_ID);
|
super(Packets.MAXIMUM_PACKET_ID);
|
||||||
}
|
}
|
||||||
@ -48,19 +54,18 @@ public final class SortedPacketListenerList extends AbstractConcurrentListenerMu
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// The returned list is thread-safe
|
// The returned list is thread-safe
|
||||||
|
if (timedManager.isTiming()) {
|
||||||
for (PrioritizedListener<PacketListener> element : list) {
|
for (PrioritizedListener<PacketListener> element : list) {
|
||||||
try {
|
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_CLIENT_SIDE);
|
||||||
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
|
long token = tracker.beginTracking();
|
||||||
element.getListener().onPacketReceiving(event);
|
|
||||||
|
|
||||||
} catch (OutOfMemoryError e) {
|
// Measure and record the execution time
|
||||||
throw e;
|
invokeReceivingListener(reporter, event, element);
|
||||||
} catch (ThreadDeath e) {
|
tracker.endTracking(token, event.getPacketID());
|
||||||
throw e;
|
}
|
||||||
} catch (Throwable e) {
|
} else {
|
||||||
// Minecraft doesn't want your Exception.
|
for (PrioritizedListener<PacketListener> element : list) {
|
||||||
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketReceiving(PacketEvent)", e,
|
invokeReceivingListener(reporter, event, element);
|
||||||
event.getPacket().getHandle());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,12 +82,37 @@ public final class SortedPacketListenerList extends AbstractConcurrentListenerMu
|
|||||||
if (list == null)
|
if (list == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// The returned list is thread-safe
|
||||||
|
if (timedManager.isTiming()) {
|
||||||
for (PrioritizedListener<PacketListener> element : list) {
|
for (PrioritizedListener<PacketListener> element : list) {
|
||||||
try {
|
|
||||||
if (element.getPriority() == priorityFilter) {
|
if (element.getPriority() == priorityFilter) {
|
||||||
|
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_CLIENT_SIDE);
|
||||||
|
long token = tracker.beginTracking();
|
||||||
|
|
||||||
|
// Measure and record the execution time
|
||||||
|
invokeReceivingListener(reporter, event, element);
|
||||||
|
tracker.endTracking(token, event.getPacketID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (PrioritizedListener<PacketListener> element : list) {
|
||||||
|
if (element.getPriority() == priorityFilter) {
|
||||||
|
invokeReceivingListener(reporter, event, element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke a particular receiving listener.
|
||||||
|
* @param reporter - the error reporter.
|
||||||
|
* @param event - the related packet event.
|
||||||
|
* @param element - the listener to invoke.
|
||||||
|
*/
|
||||||
|
private final void invokeReceivingListener(ErrorReporter reporter, PacketEvent event, PrioritizedListener<PacketListener> element) {
|
||||||
|
try {
|
||||||
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
|
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
|
||||||
element.getListener().onPacketReceiving(event);
|
element.getListener().onPacketReceiving(event);
|
||||||
}
|
|
||||||
|
|
||||||
} catch (OutOfMemoryError e) {
|
} catch (OutOfMemoryError e) {
|
||||||
throw e;
|
throw e;
|
||||||
@ -94,7 +124,6 @@ public final class SortedPacketListenerList extends AbstractConcurrentListenerMu
|
|||||||
event.getPacket().getHandle());
|
event.getPacket().getHandle());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invokes the given packet event for every registered listener.
|
* Invokes the given packet event for every registered listener.
|
||||||
@ -107,19 +136,18 @@ public final class SortedPacketListenerList extends AbstractConcurrentListenerMu
|
|||||||
if (list == null)
|
if (list == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (timedManager.isTiming()) {
|
||||||
for (PrioritizedListener<PacketListener> element : list) {
|
for (PrioritizedListener<PacketListener> element : list) {
|
||||||
try {
|
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_SERVER_SIDE);
|
||||||
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
|
long token = tracker.beginTracking();
|
||||||
element.getListener().onPacketSending(event);
|
|
||||||
|
|
||||||
} catch (OutOfMemoryError e) {
|
// Measure and record the execution time
|
||||||
throw e;
|
invokeSendingListener(reporter, event, element);
|
||||||
} catch (ThreadDeath e) {
|
tracker.endTracking(token, event.getPacketID());
|
||||||
throw e;
|
}
|
||||||
} catch (Throwable e) {
|
} else {
|
||||||
// Minecraft doesn't want your Exception.
|
for (PrioritizedListener<PacketListener> element : list) {
|
||||||
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketSending(PacketEvent)", e,
|
invokeSendingListener(reporter, event, element);
|
||||||
event.getPacket().getHandle());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,12 +164,36 @@ public final class SortedPacketListenerList extends AbstractConcurrentListenerMu
|
|||||||
if (list == null)
|
if (list == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (timedManager.isTiming()) {
|
||||||
for (PrioritizedListener<PacketListener> element : list) {
|
for (PrioritizedListener<PacketListener> element : list) {
|
||||||
try {
|
|
||||||
if (element.getPriority() == priorityFilter) {
|
if (element.getPriority() == priorityFilter) {
|
||||||
|
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_SERVER_SIDE);
|
||||||
|
long token = tracker.beginTracking();
|
||||||
|
|
||||||
|
// Measure and record the execution time
|
||||||
|
invokeSendingListener(reporter, event, element);
|
||||||
|
tracker.endTracking(token, event.getPacketID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (PrioritizedListener<PacketListener> element : list) {
|
||||||
|
if (element.getPriority() == priorityFilter) {
|
||||||
|
invokeSendingListener(reporter, event, element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke a particular sending listener.
|
||||||
|
* @param reporter - the error reporter.
|
||||||
|
* @param event - the related packet event.
|
||||||
|
* @param element - the listener to invoke.
|
||||||
|
*/
|
||||||
|
private final void invokeSendingListener(ErrorReporter reporter, PacketEvent event, PrioritizedListener<PacketListener> element) {
|
||||||
|
try {
|
||||||
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
|
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
|
||||||
element.getListener().onPacketSending(event);
|
element.getListener().onPacketSending(event);
|
||||||
}
|
|
||||||
|
|
||||||
} catch (OutOfMemoryError e) {
|
} catch (OutOfMemoryError e) {
|
||||||
throw e;
|
throw e;
|
||||||
@ -153,5 +205,4 @@ public final class SortedPacketListenerList extends AbstractConcurrentListenerMu
|
|||||||
event.getPacket().getHandle());
|
event.getPacket().getHandle());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,137 @@
|
|||||||
|
package com.comphenix.protocol.timing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an online algortihm for computing the mean and standard deviation without storing every value.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class StatisticsStream {
|
||||||
|
// This algorithm is due to Donald Knuth, as described in:
|
||||||
|
// Donald E. Knuth (1998). The Art of Computer Programming, volume 2:
|
||||||
|
// Seminumerical Algorithms, 3rd edn., p. 232. Boston: Addison-Wesley.
|
||||||
|
|
||||||
|
private int count = 0;
|
||||||
|
private double mean = 0;
|
||||||
|
private double m2 = 0;
|
||||||
|
|
||||||
|
// Also keep track of minimum and maximum observation
|
||||||
|
private double minimum = Double.MAX_VALUE;
|
||||||
|
private double maximum = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new stream with no observations.
|
||||||
|
*/
|
||||||
|
public StatisticsStream() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a copy of the given stream.
|
||||||
|
* @param other - copy of the stream.
|
||||||
|
*/
|
||||||
|
public StatisticsStream(StatisticsStream other) {
|
||||||
|
this.count = other.count;
|
||||||
|
this.mean = other.mean;
|
||||||
|
this.m2 = other.m2;
|
||||||
|
this.minimum = other.minimum;
|
||||||
|
this.maximum = other.maximum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observe a value.
|
||||||
|
* @param value - the observed value.
|
||||||
|
*/
|
||||||
|
public void observe(double value) {
|
||||||
|
double delta = value - mean;
|
||||||
|
|
||||||
|
// As per Knuth
|
||||||
|
count++;
|
||||||
|
mean += delta / count;
|
||||||
|
m2 += delta * (value - mean);
|
||||||
|
|
||||||
|
// Update extremes
|
||||||
|
if (value < minimum)
|
||||||
|
minimum = value;
|
||||||
|
if (value > maximum)
|
||||||
|
maximum = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the average of all the observations.
|
||||||
|
* @return The average.
|
||||||
|
*/
|
||||||
|
public double getMean() {
|
||||||
|
checkCount();
|
||||||
|
return mean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the variance of all the observations.
|
||||||
|
* @return The variance.
|
||||||
|
*/
|
||||||
|
public double getVariance() {
|
||||||
|
checkCount();
|
||||||
|
return m2 / (count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the standard deviation of all the observations.
|
||||||
|
* @return The STDV.
|
||||||
|
*/
|
||||||
|
public double getStandardDeviation() {
|
||||||
|
return Math.sqrt(getVariance());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the minimum observation yet observed.
|
||||||
|
* @return The minimum observation.
|
||||||
|
*/
|
||||||
|
public double getMinimum() {
|
||||||
|
checkCount();
|
||||||
|
return minimum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the maximum observation yet observed.
|
||||||
|
* @return The maximum observation.
|
||||||
|
*/
|
||||||
|
public double getMaximum() {
|
||||||
|
checkCount();
|
||||||
|
return maximum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine the two statistics.
|
||||||
|
* @param other - the other statistics.
|
||||||
|
*/
|
||||||
|
public StatisticsStream add(StatisticsStream other) {
|
||||||
|
// Special cases
|
||||||
|
if (count == 0)
|
||||||
|
return other;
|
||||||
|
else if (other.count == 0)
|
||||||
|
return this;
|
||||||
|
|
||||||
|
StatisticsStream stream = new StatisticsStream();
|
||||||
|
double delta = other.mean - mean;
|
||||||
|
double n = count + other.count;
|
||||||
|
|
||||||
|
stream.count = (int) n;
|
||||||
|
stream.mean = mean + delta * (other.count / n);
|
||||||
|
stream.m2 = m2 + other.m2 + ((delta * delta) * (count * other.count) / n);
|
||||||
|
stream.minimum = Math.min(minimum, other.minimum);
|
||||||
|
stream.maximum = Math.max(maximum, other.maximum);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the number of observations.
|
||||||
|
* @return Number of observations.
|
||||||
|
*/
|
||||||
|
public int getCount() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkCount() {
|
||||||
|
if (count == 0) {
|
||||||
|
throw new IllegalStateException("No observations in stream.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,184 @@
|
|||||||
|
package com.comphenix.protocol.timing;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.events.PacketListener;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a system for recording the time spent by each packet listener.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class TimedListenerManager {
|
||||||
|
public enum ListenerType {
|
||||||
|
ASYNC_SERVER_SIDE,
|
||||||
|
ASYNC_CLIENT_SIDE,
|
||||||
|
SYNC_SERVER_SIDE,
|
||||||
|
SYNC_CLIENT_SIDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The shared manager
|
||||||
|
private final static TimedListenerManager INSTANCE = new TimedListenerManager();
|
||||||
|
// Running?
|
||||||
|
private final static AtomicBoolean timing = new AtomicBoolean();
|
||||||
|
// When it was started
|
||||||
|
private volatile Date started;
|
||||||
|
private volatile Date stopped;
|
||||||
|
|
||||||
|
// The map of time trackers
|
||||||
|
private ConcurrentMap<String, ImmutableMap<ListenerType, TimedTracker>> map = Maps.newConcurrentMap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the shared listener manager.
|
||||||
|
* <p>
|
||||||
|
* This should never change.
|
||||||
|
* @return The shared listener manager.
|
||||||
|
*/
|
||||||
|
public static TimedListenerManager getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start timing listeners.
|
||||||
|
* @return TRUE if we started timing, FALSE if we are already timing listeners.
|
||||||
|
*/
|
||||||
|
public boolean startTiming() {
|
||||||
|
if (setTiming(true)) {
|
||||||
|
started = Calendar.getInstance().getTime();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**s
|
||||||
|
* Stop timing listeners.
|
||||||
|
* @return TRUE if we stopped timing, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean stopTiming() {
|
||||||
|
if (setTiming(false)) {
|
||||||
|
stopped = Calendar.getInstance().getTime();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the time the listener was started.
|
||||||
|
* @return The time it was started, or NULL if they have never been started.
|
||||||
|
*/
|
||||||
|
public Date getStarted() {
|
||||||
|
return started;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the time the time listeners was stopped.
|
||||||
|
* @return The time they were stopped, or NULL if not found.
|
||||||
|
*/
|
||||||
|
public Date getStopped() {
|
||||||
|
return stopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether or not the timing manager is enabled.
|
||||||
|
* @param value - TRUE if it should be enabled, FALSE otherwise.
|
||||||
|
* @return TRUE if the value was changed, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
private boolean setTiming(boolean value) {
|
||||||
|
return timing.compareAndSet(!value, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if we are currently timing listeners.
|
||||||
|
* @return TRUE if we are, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isTiming() {
|
||||||
|
return timing.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all packet gathering data.
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve every tracked plugin.
|
||||||
|
* @return Every tracked plugin.
|
||||||
|
*/
|
||||||
|
public Set<String> getTrackedPlugins() {
|
||||||
|
return map.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the timed tracker associated with the given plugin and listener type.
|
||||||
|
* @param plugin - the plugin.
|
||||||
|
* @param type - the listener type.
|
||||||
|
* @return The timed tracker.
|
||||||
|
*/
|
||||||
|
public TimedTracker getTracker(Plugin plugin, ListenerType type) {
|
||||||
|
return getTracker(plugin.getName(), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the timed tracker associated with the given listener and listener type.
|
||||||
|
* @param listener - the listener.
|
||||||
|
* @param type - the listener type.
|
||||||
|
* @return The timed tracker.
|
||||||
|
*/
|
||||||
|
public TimedTracker getTracker(PacketListener listener, ListenerType type) {
|
||||||
|
return getTracker(listener.getPlugin().getName(), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the timed tracker associated with the given listener and listener type.
|
||||||
|
* @param listener - the listener.
|
||||||
|
* @param type - the listener type.
|
||||||
|
* @return The timed tracker.
|
||||||
|
*/
|
||||||
|
public TimedTracker getTracker(String pluginName, ListenerType type) {
|
||||||
|
return getTrackers(pluginName).get(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the map of timed trackers for a specific plugin.
|
||||||
|
* @param pluginName - the plugin name.
|
||||||
|
* @return Map of timed trackers.
|
||||||
|
*/
|
||||||
|
private ImmutableMap<ListenerType, TimedTracker> getTrackers(String pluginName) {
|
||||||
|
ImmutableMap<ListenerType, TimedTracker> trackers = map.get(pluginName);
|
||||||
|
|
||||||
|
// Atomic pattern
|
||||||
|
if (trackers == null) {
|
||||||
|
ImmutableMap<ListenerType, TimedTracker> created = newTrackerMap();
|
||||||
|
trackers = map.putIfAbsent(pluginName, created);
|
||||||
|
|
||||||
|
// Success!
|
||||||
|
if (trackers == null) {
|
||||||
|
trackers = created;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return trackers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a new map of trackers for an unspecified plugin.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private ImmutableMap<ListenerType, TimedTracker> newTrackerMap() {
|
||||||
|
ImmutableMap.Builder<ListenerType, TimedTracker> builder = ImmutableMap.builder();
|
||||||
|
|
||||||
|
// Construct a map with every listener type
|
||||||
|
for (ListenerType type : ListenerType.values()) {
|
||||||
|
builder.put(type, new TimedTracker());
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package com.comphenix.protocol.timing;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.Packets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks the invocation time for a particular plugin against a list of packets.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class TimedTracker {
|
||||||
|
// Table of packets and invocations
|
||||||
|
private StatisticsStream[] packets;
|
||||||
|
private int observations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin tracking an execution time.
|
||||||
|
* @return The current tracking token.
|
||||||
|
*/
|
||||||
|
public long beginTracking() {
|
||||||
|
return System.nanoTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop and record the execution time since the creation of the given tracking token.
|
||||||
|
* @param trackingToken - the tracking token.
|
||||||
|
* @param packetId - the packet ID.
|
||||||
|
*/
|
||||||
|
public synchronized void endTracking(long trackingToken, int packetId) {
|
||||||
|
// Lazy initialization
|
||||||
|
if (packets == null)
|
||||||
|
packets = new StatisticsStream[Packets.PACKET_COUNT];
|
||||||
|
if (packets[packetId] == null)
|
||||||
|
packets[packetId] = new StatisticsStream();
|
||||||
|
|
||||||
|
// Store this observation
|
||||||
|
packets[packetId].observe(System.nanoTime() - trackingToken);
|
||||||
|
observations++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the total number of observations.
|
||||||
|
* @return Total number of observations.
|
||||||
|
*/
|
||||||
|
public int getObservations() {
|
||||||
|
return observations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an array (indexed by packet ID) of all relevant statistics.
|
||||||
|
* @return The array of statistics.
|
||||||
|
*/
|
||||||
|
public synchronized StatisticsStream[] getStatistics() {
|
||||||
|
StatisticsStream[] clone = new StatisticsStream[Packets.PACKET_COUNT];
|
||||||
|
|
||||||
|
if (packets != null) {
|
||||||
|
for (int i = 0; i < clone.length; i++) {
|
||||||
|
if (packets[i] != null) {
|
||||||
|
clone[i] = new StatisticsStream(packets[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,119 @@
|
|||||||
|
package com.comphenix.protocol.timing;
|
||||||
|
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.Packets;
|
||||||
|
import com.comphenix.protocol.timing.TimedListenerManager.ListenerType;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.io.Closeables;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
|
||||||
|
public class TimingReportGenerator {
|
||||||
|
private static final String NEWLINE = System.lineSeparator();
|
||||||
|
private static final String META_STARTED = "Started: %s" + NEWLINE;
|
||||||
|
private static final String META_STOPPED = "Stopped: %s (after %s seconds)" + NEWLINE;
|
||||||
|
private static final String PLUGIN_HEADER = "=== PLUGIN %s ===" + NEWLINE;
|
||||||
|
private static final String LISTENER_HEADER = " TYPE: %s " + NEWLINE;
|
||||||
|
private static final String SEPERATION_LINE = " ------------------------------- " + NEWLINE;
|
||||||
|
private static final String STATISTICS_HEADER = " Packet: Count: Min (ms): Max (ms): Mean (ms): Std (ms): " + NEWLINE;
|
||||||
|
private static final String STATISTICS_ROW = " %-12s %-12d %-15.6f %-15.6f %-15.6f %.6f " + NEWLINE;
|
||||||
|
private static final String SUM_MAIN_THREAD = " => Time on main thread: %.6f ms" + NEWLINE;
|
||||||
|
|
||||||
|
public void saveTo(File destination, TimedListenerManager manager) throws IOException {
|
||||||
|
BufferedWriter writer = null;
|
||||||
|
Date started = manager.getStarted();
|
||||||
|
Date stopped = manager.getStopped();
|
||||||
|
long seconds = Math.abs((stopped.getTime() - started.getTime()) / 1000);
|
||||||
|
|
||||||
|
try {
|
||||||
|
writer = Files.newWriter(destination, Charsets.UTF_8);
|
||||||
|
|
||||||
|
// Write some timing information
|
||||||
|
writer.write(String.format(META_STARTED, started));
|
||||||
|
writer.write(String.format(META_STOPPED, stopped, seconds));
|
||||||
|
writer.write(NEWLINE);
|
||||||
|
|
||||||
|
for (String plugin : manager.getTrackedPlugins()) {
|
||||||
|
writer.write(String.format(PLUGIN_HEADER, plugin));
|
||||||
|
|
||||||
|
for (ListenerType type : ListenerType.values()) {
|
||||||
|
TimedTracker tracker = manager.getTracker(plugin, type);
|
||||||
|
|
||||||
|
// We only care if it has any observations at all
|
||||||
|
if (tracker.getObservations() > 0) {
|
||||||
|
writer.write(String.format(LISTENER_HEADER, type));
|
||||||
|
|
||||||
|
writer.write(SEPERATION_LINE);
|
||||||
|
saveStatistics(writer, tracker, type);
|
||||||
|
writer.write(SEPERATION_LINE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Next plugin
|
||||||
|
writer.write(NEWLINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (writer != null) {
|
||||||
|
// Don't suppress exceptions
|
||||||
|
writer.flush();
|
||||||
|
Closeables.closeQuietly(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveStatistics(Writer destination, TimedTracker tracker, ListenerType type) throws IOException {
|
||||||
|
StatisticsStream[] streams = tracker.getStatistics();
|
||||||
|
StatisticsStream sum = new StatisticsStream();
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
destination.write(STATISTICS_HEADER);
|
||||||
|
destination.write(SEPERATION_LINE);
|
||||||
|
|
||||||
|
// Write every packet ID that we care about
|
||||||
|
for (int i = 0; i < Packets.PACKET_COUNT; i++) {
|
||||||
|
final StatisticsStream stream = streams[i];
|
||||||
|
|
||||||
|
if (stream != null && stream.getCount() > 0) {
|
||||||
|
printStatistic(destination, Integer.toString(i), stream);
|
||||||
|
|
||||||
|
// Add it
|
||||||
|
count++;
|
||||||
|
sum = sum.add(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the sum - if its useful
|
||||||
|
if (count > 1) {
|
||||||
|
printStatistic(destination, "SUM", sum);
|
||||||
|
}
|
||||||
|
// These are executed on the main thread
|
||||||
|
if (type == ListenerType.SYNC_SERVER_SIDE) {
|
||||||
|
destination.write(String.format(SUM_MAIN_THREAD,
|
||||||
|
toMilli(sum.getCount() * sum.getMean())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printStatistic(Writer destination, String key, final StatisticsStream stream) throws IOException {
|
||||||
|
destination.write(String.format(STATISTICS_ROW,
|
||||||
|
key, stream.getCount(),
|
||||||
|
toMilli(stream.getMinimum()),
|
||||||
|
toMilli(stream.getMaximum()),
|
||||||
|
toMilli(stream.getMean()),
|
||||||
|
toMilli(stream.getStandardDeviation())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a value in nanoseconds to milliseconds.
|
||||||
|
* @param value - the value.
|
||||||
|
* @return The value in milliseconds.
|
||||||
|
*/
|
||||||
|
private double toMilli(double value) {
|
||||||
|
return value / 1000000.0;
|
||||||
|
}
|
||||||
|
}
|
@ -106,7 +106,6 @@ class WrappedCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<N
|
|||||||
/**
|
/**
|
||||||
* Determine if an entry with the given key exists or not.
|
* Determine if an entry with the given key exists or not.
|
||||||
* @param key - the key to lookup.
|
* @param key - the key to lookup.
|
||||||
* @return TRUE if an entry with the given key exists, FALSE otherwise.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean containsKey(String key) {
|
public boolean containsKey(String key) {
|
||||||
|
In neuem Issue referenzieren
Einen Benutzer sperren