-
Notifications
You must be signed in to change notification settings - Fork 102
/
TimerRollup.java
310 lines (253 loc) · 10.4 KB
/
TimerRollup.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
package com.rackspacecloud.blueflood.types;
import com.google.common.base.Joiner;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.rackspacecloud.blueflood.utils.Util;
import java.io.IOException;
import java.util.*;
public class TimerRollup implements Rollup, IBasicRollup {
private double sum = 0;
private long count = 0;
private double rate = 0;
/**
* Number of pre-aggregated timers received by Blueflood
* No relationship to 'count', which indicates number of raw timings.
* If you have a 5-minute rollup and sent a Timer to Blueflood every 60 seconds,
* the value would be 5.
*/
private int sampleCount = 0;
private MinValue min = new MinValue();
private MaxValue max = new MaxValue();
private Average average = new Average();
private Variance variance = new Variance();
// to support percentiles, we will overload the count and treat it as sum.
private Map<String, Percentile> percentiles = new HashMap<String, Percentile>();
public TimerRollup() {
super();
}
public TimerRollup withSum(double sum) {
this.sum = sum;
return this;
}
public TimerRollup withCount(long count) {
this.count = count;
return this;
}
public TimerRollup withCountPS(double count_ps) {
this.rate = count_ps;
return this;
}
public TimerRollup withSampleCount(int sampleCount) {
this.sampleCount = sampleCount;
return this;
}
public TimerRollup withMinValue(MinValue min) {
this.min = min;
return this;
}
public TimerRollup withMinValue(Number num) {
AbstractRollupStat.set(this.min, num);
return this;
}
public TimerRollup withMaxValue(MaxValue max) {
this.max = max;
return this;
}
public TimerRollup withMaxValue(Number num) {
AbstractRollupStat.set(this.max, num);
return this;
}
public TimerRollup withAverage(Average average) {
this.average = average;
return this;
}
public TimerRollup withAverage(Number average) {
AbstractRollupStat.set(this.average, average);
return this;
}
public TimerRollup withVariance(Variance variance) {
this.variance = variance;
return this;
}
public TimerRollup withVariance(Number variance) {
AbstractRollupStat.set(this.variance, variance);
return this;
}
public AbstractRollupStat getAverage() { return average; }
public AbstractRollupStat getMaxValue() { return max; }
public AbstractRollupStat getMinValue() { return min; }
public AbstractRollupStat getVariance() { return variance; }
public void setPercentile(String label, Number mean) {
percentiles.put(label, new Percentile(mean));
}
@Override
public Boolean hasData() {
return sampleCount > 0;
}
// todo: consider moving this to its own class.
public static class Percentile {
private Number mean;
public Percentile(Number mean) {
// longs and doubles only please.
this.mean = maybePromote(mean);
}
@SuppressWarnings("unused") // used by Jackson
public Percentile(Double mean) {
this.mean = maybePromote(mean);
}
@SuppressWarnings("unused") // used by Jackson
public Percentile(Long mean) {
this.mean = maybePromote(mean);
}
public boolean equals(Object obj) {
if (!(obj instanceof Percentile)) return false;
Percentile other = (Percentile)obj;
if (!other.mean.equals(this.mean)) return false;
return true;
}
public Number getMean() { return mean; }
public String toString() {
return String.format("{mean:%s}", mean.toString());
}
public static Number maybePromote(Number number) {
if (number instanceof Float)
return number.doubleValue();
else if (number instanceof Integer)
return number.longValue();
else
return number;
}
}
// per second rate.
public double getRate() { return rate; }
public double getSum() { return sum; }
public long getCount() { return count; };
public int getSampleCount() { return sampleCount; }
public String toString() {
return String.format("sum:%s, rate:%s, count:%s, min:%s, max:%s, avg:%s, var:%s, sample_cnt:%s, %s",
sum, rate, count, min, max, average, variance, sampleCount,
Joiner.on(", ").withKeyValueSeparator(": ").join(percentiles.entrySet()));
}
@Override
public RollupType getRollupType() {
return RollupType.TIMER;
}
public boolean equals(Object obj) {
if (!(obj instanceof TimerRollup)) return false;
TimerRollup other = (TimerRollup)obj;
if (other.sum != this.sum) return false;
if (other.sampleCount != this.sampleCount) return false;
if (other.rate != this.rate) return false;
if (!other.average.equals(this.average)) return false;
if (!other.variance.equals(this.variance)) return false;
if (!other.min.equals(this.min)) return false;
if (!other.max.equals(this.max)) return false;
if (other.count != this.count) return false;
Map<String, Percentile> otherPct = other.getPercentiles();
Set<String> allKeys = Sets.union(otherPct.keySet(), this.getPercentiles().keySet());
if (allKeys.size() != this.getPercentiles().size()) return false;
for (Map.Entry<String, Percentile> otherEntry : otherPct.entrySet())
if (!otherEntry.getValue().equals(this.getPercentiles().get(otherEntry.getKey())))
return false;
return true;
}
private void computeFromRollups(Points<TimerRollup> input) throws IOException {
if (input == null)
throw new IOException("Null input to create rollup from");
if (input.isEmpty())
return;
Map<Long, Points.Point<TimerRollup>> points = input.getPoints();
Set<String> labels = new HashSet<String>();
Multimap<String, Number> pctMeans = LinkedListMultimap.create();
Multimap<String, Number> pctUppers = LinkedListMultimap.create();
Multimap<String, Number> pctSums = LinkedListMultimap.create();
for (Map.Entry<Long, Points.Point<TimerRollup>> item : points.entrySet()) {
TimerRollup rollup = item.getValue().getData();
// todo: put this calculation in a static method and put tests for it.
long count = this.getCount() + rollup.getCount();
double time = Util.safeDiv((double) getCount(), this.rate) + Util.safeDiv((double) rollup.getCount(), rollup.rate);
this.rate = Util.safeDiv((double) count, time);
// update fields.
this.count += rollup.getCount();
this.sum += rollup.getSum();
this.sampleCount += rollup.getSampleCount();
this.average.handleRollupMetric(rollup);
this.variance.handleRollupMetric(rollup);
this.min.handleRollupMetric(rollup);
this.max.handleRollupMetric(rollup);
// now the percentiles.
Map<String, Percentile> percentilesToMerge = rollup.getPercentiles();
for (String label : percentilesToMerge.keySet()) {
labels.add(label);
Percentile percentile = percentilesToMerge.get(label);
pctMeans.get(label).add(percentile.getMean());
}
}
// now go through the percentiles and calculate!
for (String label : labels) {
Number mean = TimerRollup.avg(pctMeans.get(label));
this.setPercentile(label, mean);
}
// wooo!
}
public static Number sum(Collection<Number> numbers) {
long longSum = 0;
double doubleSum = 0d;
boolean useDouble = false;
for (Number number : numbers) {
if (useDouble || number instanceof Double || number instanceof Float) {
if (!useDouble) {
useDouble = true;
doubleSum += longSum;
}
doubleSum += number.doubleValue();
} else if (number instanceof Long || number instanceof Integer)
longSum += number.longValue();
}
if (useDouble)
return doubleSum;
else
return longSum;
}
public static Number avg(Collection<Number> numbers) {
Number sum = TimerRollup.sum(numbers);
if (sum instanceof Long || sum instanceof Integer)
return (Long)sum / numbers.size();
else
return (Double)sum / (double)numbers.size();
}
public static Number max(Collection<Number> numbers) {
long longMax = numbers.iterator().next().longValue();
double doubleMax = numbers.iterator().next().doubleValue();
boolean useDouble = false;
for (Number number : numbers) {
if (useDouble || number instanceof Double || number instanceof Float) {
if (!useDouble) {
useDouble = true;
doubleMax = Math.max(doubleMax, (double)longMax);
}
doubleMax = Math.max(doubleMax, number.doubleValue());
} else {
longMax = Math.max(longMax, number.longValue());
}
}
if (useDouble)
return doubleMax;
else
return longMax;
}
public static double calculatePerSecond(long countA, double countPerSecA, long countB, double countPerSecB) {
double totalCount = countA + countB;
double totalTime = ((double)countA / countPerSecA) + ((double)countB / countPerSecB);
return totalCount / totalTime;
}
public Map<String, Percentile> getPercentiles() {
return Collections.unmodifiableMap(percentiles);
}
public static TimerRollup buildRollupFromTimerRollups(Points<TimerRollup> input) throws IOException {
TimerRollup rollup = new TimerRollup();
rollup.computeFromRollups(input);
return rollup;
}
}