--- /dev/null
+/*
+ * 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();
+ }
+
+}