From 839c186609dff342205761f3c82a245fd9676ba4 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 29 Mar 2014 23:29:25 +0100 Subject: [PATCH] Added a way to compute a "histogram" (ordered by time slices). This allows us to see snapshots of the online statistics algorihm. --- .../protocol/timing/HistogramStream.java | 125 ++++++++++++++++++ .../protocol/timing/OnlineComputation.java | 49 +++++++ .../protocol/timing/StatisticsStream.java | 25 +++- 3 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/timing/HistogramStream.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/timing/OnlineComputation.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/timing/HistogramStream.java b/ProtocolLib/src/main/java/com/comphenix/protocol/timing/HistogramStream.java new file mode 100644 index 00000000..61d325a3 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/timing/HistogramStream.java @@ -0,0 +1,125 @@ +package com.comphenix.protocol.timing; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +/** + * Represents an online algortihm of computing histograms over time. + * @author Kristian + */ +public class HistogramStream extends OnlineComputation { + /** + * Each bin in the histogram, indexed by time. + */ + protected List bins; + + /** + * The current statistics stream we are updating. + */ + protected StatisticsStream current; + + /** + * The maximum number of observations in each bin. + */ + protected int binWidth; + + /** + * The number of total observations. + */ + protected int count; + + /** + * Construct a new histogram stream which splits up every observation in different bins, ordered by time. + * @param binWidth - maximum number of observations in each bin. + */ + public HistogramStream(int binWidth) { + this(new ArrayList(), new StatisticsStream(), binWidth); + } + + /** + * Construct a new copy of the given histogram. + * @param other - the histogram to copy. + */ + public HistogramStream(HistogramStream other) { + // Deep cloning + for (StatisticsStream stream : other.bins) { + StatisticsStream copy = stream.copy(); + + // Update current + if (stream == other.current) + this.current = copy; + this.bins.add(copy); + } + this.binWidth = other.binWidth; + } + + /** + * Construct a new histogram stream. + * @param bins - list of bins. + * @param current - the current selected bin. This will be added to the list if it is not already present. + * @param binWidth - the desired number of observations in each bin. + */ + protected HistogramStream(List bins, StatisticsStream current, int binWidth) { + if (binWidth < 1) + throw new IllegalArgumentException("binWidth cannot be less than 1"); + this.bins = Preconditions.checkNotNull(bins, "bins cannot be NULL"); + this.current = Preconditions.checkNotNull(current, "current cannot be NULL"); + this.binWidth = binWidth; + + if (!this.bins.contains(current)) { + this.bins.add(current); + } + } + + @Override + public HistogramStream copy() { + return new HistogramStream(this); + } + + /** + * Retrieve an immutable view of every bin in the histogram. + * @return Every bin in the histogram. + */ + public ImmutableList getBins() { + return ImmutableList.copyOf(bins); + } + + @Override + public void observe(double value) { + checkOverflow(); + count++; + current.observe(value); + } + + /** + * See if the current bin has overflowed. If so, construct a new bin and set it as the current. + */ + protected void checkOverflow() { + if (current.getCount() >= binWidth) { + bins.add(current = new StatisticsStream()); + } + } + + /** + * Retrieve the total statistics of every bin in the histogram. + *

+ * This method is not thread safe. + * @return The total statistics. + */ + public StatisticsStream getTotal() { + StatisticsStream sum = null; + + for (StatisticsStream stream : bins) { + sum = sum != null ? stream.add(sum) : stream; + } + return sum; + } + + @Override + public int getCount() { + return count; + } +} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/timing/OnlineComputation.java b/ProtocolLib/src/main/java/com/comphenix/protocol/timing/OnlineComputation.java new file mode 100644 index 00000000..39b641f5 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/timing/OnlineComputation.java @@ -0,0 +1,49 @@ +package com.comphenix.protocol.timing; + +/** + * Represents an online computation. + * @author Kristian + */ +public abstract class OnlineComputation { + /** + * Retrieve the number of observations. + * @return Number of observations. + */ + public abstract int getCount(); + + /** + * Observe a value. + * @param value - the observed value. + */ + public abstract void observe(double value); + + /** + * Construct a copy of the current online computation. + * @return The new copy. + */ + public abstract OnlineComputation copy(); + + /** + * Retrieve a wrapper for another online computation that is synchronized. + * @param computation - the computation. + * @return The synchronized wrapper. + */ + public static OnlineComputation synchronizedComputation(final OnlineComputation computation) { + return new OnlineComputation() { + @Override + public synchronized void observe(double value) { + computation.observe(value); + } + + @Override + public synchronized int getCount() { + return computation.getCount(); + } + + @Override + public synchronized OnlineComputation copy() { + return computation.copy(); + } + }; + } +} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/timing/StatisticsStream.java b/ProtocolLib/src/main/java/com/comphenix/protocol/timing/StatisticsStream.java index 97237348..c231c521 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/timing/StatisticsStream.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/timing/StatisticsStream.java @@ -4,7 +4,7 @@ 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 { +public class StatisticsStream extends OnlineComputation { // 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. @@ -35,11 +35,17 @@ public class StatisticsStream { this.maximum = other.maximum; } + @Override + public StatisticsStream copy() { + return new StatisticsStream(this); + } + /** * Observe a value. * @param value - the observed value. */ - public void observe(double value) { + @Override + public void observe(double value) { double delta = value - mean; // As per Knuth @@ -125,7 +131,8 @@ public class StatisticsStream { * Retrieve the number of observations. * @return Number of observations. */ - public int getCount() { + @Override + public int getCount() { return count; } @@ -134,4 +141,14 @@ public class StatisticsStream { throw new IllegalStateException("No observations in stream."); } } -} + + @Override + public String toString() { + if (count == 0) + return "StatisticsStream [Nothing recorded]"; + + return String.format("StatisticsStream [Average: %.3f, SD: %.3f, Min: %.3f, Max: %.3f, Count: %s]", + getMean(), getStandardDeviation(), + getMinimum(), getMaximum(), getCount()); + } +} \ No newline at end of file