/
RequestCounter.java
137 lines (108 loc) · 3.7 KB
/
RequestCounter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package voldemort.store.stats;
import java.util.concurrent.atomic.AtomicReference;
import voldemort.utils.Time;
/**
* A thread-safe request counter that calculates throughput for a specified
* duration of time.
*
* @author elias, gmj
*
*/
public class RequestCounter {
private final AtomicReference<Accumulator> values;
private final int durationMS;
/**
* @param durationMS specifies for how long you want to maintain this
* counter (in milliseconds).
*/
public RequestCounter(int durationMS) {
this.values = new AtomicReference<Accumulator>(new Accumulator());
this.durationMS = durationMS;
}
public long getCount() {
return getValidAccumulator().count;
}
public long getTotalCount() {
return getValidAccumulator().total;
}
public float getThroughput() {
Accumulator oldv = getValidAccumulator();
float elapsed = (System.currentTimeMillis() - oldv.startTimeMS) / Time.MS_PER_SECOND;
if(elapsed > 0f) {
return oldv.count / elapsed;
} else {
return -1f;
}
}
public String getDisplayThroughput() {
return String.format("%.2f", getThroughput());
}
public double getAverageTimeInMs() {
return getValidAccumulator().getAverageTimeNS() / Time.NS_PER_MS;
}
public String getDisplayAverageTimeInMs() {
return String.format("%.4f", getAverageTimeInMs());
}
public int getDuration() {
return durationMS;
}
private Accumulator getValidAccumulator() {
Accumulator accum = values.get();
long now = System.currentTimeMillis();
/*
* if still in the window, just return it
*/
if(now - accum.startTimeMS <= durationMS) {
return accum;
}
/*
* try to set. if we fail, then someone else set it, so just return that new one
*/
Accumulator newWithTotal = accum.newWithTotal();
if(values.compareAndSet(accum, newWithTotal)) {
return newWithTotal;
}
return values.get();
}
/*
* Updates the stats accumulator with another operation. We need to make
* sure that the request is only added to a non-expired pair. If so, start a
* new counter pair with recent time. We'll only try to do this 3 times - if
* other threads keep modifying while we're doing our own work, just bail.
*
* @param timeNS time of operation, in nanoseconds
*/
public void addRequest(long timeNS) {
for(int i = 0; i < 3; i++) {
Accumulator oldv = getValidAccumulator();
long startTimeMS = oldv.startTimeMS;
long count = oldv.count + 1;
long totalTimeNS = oldv.totalTimeNS + timeNS;
long total = oldv.total + 1;
if(values.compareAndSet(oldv, new Accumulator(startTimeMS, count, totalTimeNS, total))) {
return;
}
}
}
private static class Accumulator {
final long startTimeMS;
final long count;
final long totalTimeNS;
final long total;
public Accumulator() {
this(System.currentTimeMillis(), 0, 0, 0);
}
public Accumulator newWithTotal() {
return new Accumulator(System.currentTimeMillis(), 0, 0, total);
}
public Accumulator(long startTimeMS, long count, long totalTimeNS, long total) {
this.startTimeMS = startTimeMS;
this.count = count;
this.totalTimeNS = totalTimeNS;
this.total = total;
}
public double getAverageTimeNS() {
return count > 0 ? 1f * totalTimeNS / count : -0f;
}
}
}