Add counter for accumulating data.
[xudocci.git] / src / main / java / net / pterodactylus / xdcc / util / io / AccumulatingDataCounter.java
1 /*
2  * XdccDownloader - AccumulatingDataCounter.java - Copyright © 2013 David Roden
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 package net.pterodactylus.xdcc.util.io;
19
20 import java.util.SortedMap;
21 import java.util.concurrent.TimeUnit;
22
23 import com.google.common.collect.Maps;
24
25 /**
26  * Counter for data that can be accumulated over a period. A typical use would
27  * be a bandwidth counter.
28  * <p/>
29  * A counter has a maximum age for its timestamped entries. Occurrences older
30  * than the maximum lifetime are discarded when new occurrences are added.
31  * <p/>
32  * The returned rates are always bytes/second, averaged over the given amount of
33  * time (or the maximum lifetime, if unspecified).
34  * <p/>
35  * A counter can also be {@link #stop()}’ed in which case it will remember the
36  * time when it was stopped so that the calculation of the overall rate is now
37  * skewed by the further passage of time. The calculation of the current rate is
38  * not affected by stopping the counter.
39  *
40  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
41  */
42 public class AccumulatingDataCounter {
43
44         /** The timestamped occurrences. */
45         private final SortedMap<Long, Long> occurrences = Maps.newTreeMap();
46
47         /** The maximum occurrence lifetime. */
48         private final long maximumLifetime;
49
50         /** The start time. */
51         private final long startTime = System.currentTimeMillis();
52
53         /** The end time. */
54         private long endTime = Long.MIN_VALUE;
55
56         /** The total number of bytes processed. */
57         private long total = 0;
58
59         /**
60          * Creates a new accumulating data counter with a maximum lifetime of 1
61          * minute.
62          */
63         public AccumulatingDataCounter() {
64                 this(1, TimeUnit.MINUTES);
65         }
66
67         /**
68          * Creates a new accumulating data counter with the given maximum lifetime. A
69          * larger lifetime will also use more memory for the counter data.
70          *
71          * @param lifetime
72          *              The lifetime of the counter data
73          * @param timeUnit
74          *              The unit of the lifetime
75          */
76         public AccumulatingDataCounter(long lifetime, TimeUnit timeUnit) {
77                 maximumLifetime = timeUnit.toMillis(lifetime);
78         }
79
80         //
81         // ACTIONS
82         //
83
84         /**
85          * Counts the given number of bytes at this moment.
86          *
87          * @param bytes
88          *              The number of bytes to count
89          */
90         public void count(long bytes) {
91                 long now = System.currentTimeMillis();
92                 synchronized (occurrences) {
93                         Long counter = occurrences.get(now);
94                         if (counter == null) {
95                                 counter = 0L;
96                         }
97                         counter += bytes;
98                         occurrences.put(now, counter);
99
100                         if (endTime == Long.MIN_VALUE) {
101                                 total += bytes;
102                         }
103                         trimOccurrences();
104                 }
105         }
106
107         /**
108          * Stops the counter and prevents the overall rate from changing. Calling this
109          * method more than once does not change the end time.
110          */
111         public void stop() {
112                 if (endTime == Long.MIN_VALUE) {
113                         endTime = System.currentTimeMillis();
114                 }
115         }
116
117         /**
118          * Returns the current rate of this counter, in bytes/second, averaged over the
119          * maximum lifetime of this counter.
120          *
121          * @return The current rate averaged over the maximum lifetime
122          */
123         public long getCurrentRate() {
124                 return getCurrentRate(maximumLifetime);
125         }
126
127         /**
128          * Returns the current rate of this counter, in bytes/second, averaged over the
129          * given duration. If the given duration is longer than the maximum lifetime of
130          * this counter, the counter data is averaged over the maximum lifetime as
131          * using the originally given lifetime would skew the results.
132          *
133          * @return The current rate averaged over the given duration, in bytes/second
134          */
135         public long getCurrentRate(long millis) {
136                 long now = System.currentTimeMillis();
137                 long sum = 0;
138                 synchronized (occurrences) {
139                         for (long count : occurrences.tailMap(now - Math.min(millis, maximumLifetime)).values()) {
140                                 sum += count;
141                         }
142                 }
143                 return sum * 1000 / Math.min(millis, maximumLifetime);
144         }
145
146         /**
147          * Returns the overall rate of this counter, counted since its creation.
148          *
149          * @return The overall rate of this counter, in bytes/second
150          */
151         public long getOverallRate() {
152                 return total * 1000 / (((endTime > Long.MIN_VALUE) ? endTime : System.currentTimeMillis()) - startTime);
153         }
154
155         //
156         // PRIVATE METHODS
157         //
158
159         /**
160          * Trims the occurrences by removing all entries that are older than the
161          * current time and the maximum lifetime of the counter data allows.
162          */
163         private void trimOccurrences() {
164                 long oldestValid = System.currentTimeMillis() - maximumLifetime;
165                 occurrences.headMap(oldestValid).clear();
166         }
167
168 }