Add counter for accumulating data.
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Wed, 10 Apr 2013 20:13:44 +0000 (22:13 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Wed, 10 Apr 2013 20:13:44 +0000 (22:13 +0200)
src/main/java/net/pterodactylus/xdcc/util/io/AccumulatingDataCounter.java [new file with mode: 0644]

diff --git a/src/main/java/net/pterodactylus/xdcc/util/io/AccumulatingDataCounter.java b/src/main/java/net/pterodactylus/xdcc/util/io/AccumulatingDataCounter.java
new file mode 100644 (file)
index 0000000..7e3f892
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * XdccDownloader - AccumulatingDataCounter.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.xdcc.util.io;
+
+import java.util.SortedMap;
+import java.util.concurrent.TimeUnit;
+
+import com.google.common.collect.Maps;
+
+/**
+ * Counter for data that can be accumulated over a period. A typical use would
+ * be a bandwidth counter.
+ * <p/>
+ * A counter has a maximum age for its timestamped entries. Occurrences older
+ * than the maximum lifetime are discarded when new occurrences are added.
+ * <p/>
+ * The returned rates are always bytes/second, averaged over the given amount of
+ * time (or the maximum lifetime, if unspecified).
+ * <p/>
+ * A counter can also be {@link #stop()}’ed in which case it will remember the
+ * time when it was stopped so that the calculation of the overall rate is now
+ * skewed by the further passage of time. The calculation of the current rate is
+ * not affected by stopping the counter.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class AccumulatingDataCounter {
+
+       /** The timestamped occurrences. */
+       private final SortedMap<Long, Long> occurrences = Maps.newTreeMap();
+
+       /** The maximum occurrence lifetime. */
+       private final long maximumLifetime;
+
+       /** The start time. */
+       private final long startTime = System.currentTimeMillis();
+
+       /** The end time. */
+       private long endTime = Long.MIN_VALUE;
+
+       /** The total number of bytes processed. */
+       private long total = 0;
+
+       /**
+        * Creates a new accumulating data counter with a maximum lifetime of 1
+        * minute.
+        */
+       public AccumulatingDataCounter() {
+               this(1, TimeUnit.MINUTES);
+       }
+
+       /**
+        * Creates a new accumulating data counter with the given maximum lifetime. A
+        * larger lifetime will also use more memory for the counter data.
+        *
+        * @param lifetime
+        *              The lifetime of the counter data
+        * @param timeUnit
+        *              The unit of the lifetime
+        */
+       public AccumulatingDataCounter(long lifetime, TimeUnit timeUnit) {
+               maximumLifetime = timeUnit.toMillis(lifetime);
+       }
+
+       //
+       // ACTIONS
+       //
+
+       /**
+        * Counts the given number of bytes at this moment.
+        *
+        * @param bytes
+        *              The number of bytes to count
+        */
+       public void count(long bytes) {
+               long now = System.currentTimeMillis();
+               synchronized (occurrences) {
+                       Long counter = occurrences.get(now);
+                       if (counter == null) {
+                               counter = 0L;
+                       }
+                       counter += bytes;
+                       occurrences.put(now, counter);
+
+                       if (endTime == Long.MIN_VALUE) {
+                               total += bytes;
+                       }
+                       trimOccurrences();
+               }
+       }
+
+       /**
+        * Stops the counter and prevents the overall rate from changing. Calling this
+        * method more than once does not change the end time.
+        */
+       public void stop() {
+               if (endTime == Long.MIN_VALUE) {
+                       endTime = System.currentTimeMillis();
+               }
+       }
+
+       /**
+        * Returns the current rate of this counter, in bytes/second, averaged over the
+        * maximum lifetime of this counter.
+        *
+        * @return The current rate averaged over the maximum lifetime
+        */
+       public long getCurrentRate() {
+               return getCurrentRate(maximumLifetime);
+       }
+
+       /**
+        * Returns the current rate of this counter, in bytes/second, averaged over the
+        * given duration. If the given duration is longer than the maximum lifetime of
+        * this counter, the counter data is averaged over the maximum lifetime as
+        * using the originally given lifetime would skew the results.
+        *
+        * @return The current rate averaged over the given duration, in bytes/second
+        */
+       public long getCurrentRate(long millis) {
+               long now = System.currentTimeMillis();
+               long sum = 0;
+               synchronized (occurrences) {
+                       for (long count : occurrences.tailMap(now - Math.min(millis, maximumLifetime)).values()) {
+                               sum += count;
+                       }
+               }
+               return sum * 1000 / Math.min(millis, maximumLifetime);
+       }
+
+       /**
+        * Returns the overall rate of this counter, counted since its creation.
+        *
+        * @return The overall rate of this counter, in bytes/second
+        */
+       public long getOverallRate() {
+               return total * 1000 / (((endTime > Long.MIN_VALUE) ? endTime : System.currentTimeMillis()) - startTime);
+       }
+
+       //
+       // PRIVATE METHODS
+       //
+
+       /**
+        * Trims the occurrences by removing all entries that are older than the
+        * current time and the maximum lifetime of the counter data allows.
+        */
+       private void trimOccurrences() {
+               long oldestValid = System.currentTimeMillis() - maximumLifetime;
+               occurrences.headMap(oldestValid).clear();
+       }
+
+}