/
RequestCounter.java
358 lines (299 loc) · 13.8 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
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
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
/*
* Copyright 2012 LinkedIn, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package voldemort.store.stats;
import org.tehuti.Metric;
import org.tehuti.metrics.MetricConfig;
import org.tehuti.metrics.MetricsRepository;
import org.tehuti.metrics.Sensor;
import org.tehuti.metrics.stats.*;
import org.tehuti.utils.Time;
import org.tehuti.utils.SystemTime;
import org.apache.log4j.Logger;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* A thread-safe request counter that calculates throughput for a specified
* duration of time.
*
*
*/
public class RequestCounter {
private final Time time;
// Sensors
private Sensor timeSensor, emptyResponseKeysSensor, valueBytesSensor, keyBytesSensor, getAllKeysCountSensor;
// Metrics
private Metric
// Averages
latencyAverage, valueBytesAverage, keyBytesAverage,
// Percentiles
latency10thPercentile, latency50thPercentile, latency95thPercentile, latency99thPercentile,
// Maximums
latencyMax, valueBytesMax, keyBytesMax, getAllKeysCountMax,
// Sampled Totals
getAllKeysCountSampledTotal, emptyResponseKeysSampledTotal,
// Sampled Counts
requestSampledCount,
// All-time Count
requestAllTimeCount,
// Rates
getAllKeysThroughput, requestThroughput, requestThroughputInBytes;
private MetricsRepository metricsRepository;
private static final Logger logger = Logger.getLogger(RequestCounter.class.getName());
public RequestCounter(String name, long durationMs) {
this(name, durationMs, new SystemTime(), false, (RequestCounter[]) null);
}
/**
* @param durationMs specifies for how long you want to maintain this
* counter (in milliseconds).
*/
public RequestCounter(String name, long durationMs, RequestCounter... parents) {
this(name, durationMs, new SystemTime(), false, parents);
}
public RequestCounter(String name, long durationMs, boolean useHistogram) {
this(name, durationMs, new SystemTime(), useHistogram, (RequestCounter[]) null);
}
/**
* @param durationMs specifies for how long you want to maintain this
* counter (in milliseconds). useHistogram indicates that this
* counter should also use a histogram.
*/
public RequestCounter(String name, long durationMs, boolean useHistogram, RequestCounter... parents) {
this(name, durationMs, new SystemTime(), useHistogram, parents);
}
RequestCounter(String name, long durationMs, Time time) {
this(name, durationMs, time, false, (RequestCounter[]) null);
}
/**
* For testing request expiration via an injected time provider
*/
RequestCounter(String name, long durationMs, Time time, RequestCounter... parents) {
this(name, durationMs, time, false, parents);
}
RequestCounter(String name, long durationMs, Time time, boolean useHistogram, RequestCounter... parents) {
this.time = time;
this.metricsRepository = new MetricsRepository(time);
// Initialize parent sensors arrays...
int amountOfParentSensors = 0;
if (parents != null) {
amountOfParentSensors = parents.length;
}
Sensor[] timeParentSensors = null;
Sensor[] emptyResponseKeysParentSensors = null;
Sensor[] getAllKeysCountParentSensors = null;
Sensor[] valueBytesParentSensors = new Sensor[amountOfParentSensors + 1];
Sensor[] keyBytesParentSensors = new Sensor[amountOfParentSensors + 1];
if (parents != null && parents.length > 0) {
timeParentSensors = new Sensor[parents.length];
emptyResponseKeysParentSensors = new Sensor[parents.length];
getAllKeysCountParentSensors = new Sensor[parents.length];
for (int i = 0; i < parents.length; i++) {
timeParentSensors[i] = parents[i].timeSensor;
emptyResponseKeysParentSensors[i] = parents[i].emptyResponseKeysSensor;
getAllKeysCountParentSensors[i] = parents[i].getAllKeysCountSensor;
valueBytesParentSensors[i] = parents[i].valueBytesSensor;
keyBytesParentSensors[i] = parents[i].keyBytesSensor;
}
}
// Initialize MetricConfig
MetricConfig metricConfig = new MetricConfig().timeWindow(durationMs, TimeUnit.MILLISECONDS);
// Time Sensor
String timeSensorName = name + ".time";
this.timeSensor =
metricsRepository.sensor(timeSensorName, metricConfig, timeParentSensors);
if (useHistogram) {
Percentiles timePercentiles = new Percentiles(40000, 10000, Percentiles.BucketSizing.LINEAR,
new Percentile(timeSensorName + ".10thPercentile", 10),
new Percentile(timeSensorName + ".50thPercentile", 50),
new Percentile(timeSensorName + ".95thPercentile", 95),
new Percentile(timeSensorName + ".99thPercentile", 99));
Map<String, Metric> percentiles = this.timeSensor.add(timePercentiles);
this.latency10thPercentile = percentiles.get(timeSensorName + ".10thPercentile");
this.latency50thPercentile = percentiles.get(timeSensorName + ".50thPercentile");
this.latency95thPercentile = percentiles.get(timeSensorName + ".95thPercentile");
this.latency99thPercentile = percentiles.get(timeSensorName + ".99thPercentile");
}
this.latencyMax = this.timeSensor.add(timeSensorName + ".max", new Max(0));
this.latencyAverage = this.timeSensor.add(timeSensorName + ".avg", new Avg());
// Sampled count, all-time count and throughput rate, piggy-backing off of the Time Sensor
this.requestSampledCount = this.timeSensor.add(name + ".sampled-count", new SampledCount());
this.requestAllTimeCount = this.timeSensor.add(name + ".count", new Count());
this.requestThroughput = this.timeSensor.add(name + ".throughput", new OccurrenceRate());
// Empty Reponse Keys Sensor
String emptyResponseKeysSensorName = name + ".empty-response-keys";
this.emptyResponseKeysSensor =
metricsRepository.sensor(emptyResponseKeysSensorName, metricConfig, emptyResponseKeysParentSensors);
this.emptyResponseKeysSampledTotal =
this.emptyResponseKeysSensor.add(emptyResponseKeysSensorName + ".sampled-total", new SampledTotal());
// Key and Value Bytes Sensor
String keyAndValueBytesSensorName= name + ".key-and-value-bytes";
Sensor keyAndValueBytesSensor = metricsRepository.sensor(keyAndValueBytesSensorName, metricConfig);
this.requestThroughputInBytes =
keyAndValueBytesSensor.add(keyAndValueBytesSensorName + ".bytes-throughput", new Rate());
valueBytesParentSensors[amountOfParentSensors] = keyAndValueBytesSensor;
keyBytesParentSensors[amountOfParentSensors] = keyAndValueBytesSensor;
// Value Bytes Sensor
String valueBytesSensorName = name + ".value-bytes";
this.valueBytesSensor = metricsRepository.sensor(valueBytesSensorName, metricConfig, valueBytesParentSensors);
this.valueBytesMax = this.valueBytesSensor.add(valueBytesSensorName + ".max", new Max(0));
this.valueBytesAverage = this.valueBytesSensor.add(valueBytesSensorName + ".avg", new Avg());
// Key Bytes Sensor
String keyBytesSensorName = name + ".key-bytes";
this.keyBytesSensor = metricsRepository.sensor(keyBytesSensorName, metricConfig, keyBytesParentSensors);
this.keyBytesMax = this.keyBytesSensor.add(keyBytesSensorName + ".max", new Max(0));
this.keyBytesAverage = this.keyBytesSensor.add(keyBytesSensorName + ".avg", new Avg());
// Get All Keys Count Sensor
String getAllKeysCountSensorName = name + ".get-all-keys-count";
this.getAllKeysCountSensor =
metricsRepository.sensor(getAllKeysCountSensorName, metricConfig, getAllKeysCountParentSensors);
this.getAllKeysCountSampledTotal =
this.getAllKeysCountSensor.add(getAllKeysCountSensorName + ".sampled-total", new SampledTotal());
this.getAllKeysCountMax = this.getAllKeysCountSensor.add(getAllKeysCountSensorName + ".max", new Max(0));
this.getAllKeysThroughput = this.getAllKeysCountSensor.add(getAllKeysCountSensorName + ".throughput", new Rate());
}
/**
* @return The count of queries tracked by this RequestCounter during the current set of (non-expired) sample windows.
*/
public long getCount() {
return (long) requestSampledCount.value();
}
/**
* @return The total amount of queries tracked by this RequestCounter since the beginning of time.
*/
public long getTotalCount() {
return (long) requestAllTimeCount.value();
}
/**
* @return The rate of queries per second to this RequestCounter.
*/
public float getThroughput() {
return (float) requestThroughput.value();
}
/**
* @return The rate of keys per second that were queried through GetAll requests.
*/
public float getGetAllKeysThroughput() {
return (float) getAllKeysThroughput.value();
}
/**
* @return The rate of bytes per second for queries tracked by this RequestCounter.
*/
public float getThroughputInBytes() {
return (float) requestThroughputInBytes.value();
}
public String getDisplayThroughput() {
return String.format("%.2f", getThroughput());
}
public double getAverageTimeInMs() {
return latencyAverage.value();
}
public String getDisplayAverageTimeInMs() {
return String.format("%.4f", getAverageTimeInMs());
}
public long getMaxLatencyInMs() {
return (long) latencyMax.value();
}
/**
* @param timeNS time of operation, in nanoseconds
*/
public void addRequest(long timeNS) {
addRequest(timeNS, 0, 0, 0, 0);
}
/**
* Detailed request to track additional data about PUT, GET and GET_ALL
*
* @param timeNS The time in nanoseconds that the operation took to complete
* @param numEmptyResponses For GET and GET_ALL, how many keys were no values found
* @param valueBytes Total number of bytes across all versions of values' bytes
* @param keyBytes Total number of bytes in the keys
* @param getAllAggregatedCount Total number of keys returned for getAll calls
*/
public void addRequest(long timeNS,
long numEmptyResponses,
long valueBytes,
long keyBytes,
long getAllAggregatedCount) {
// timing instrumentation (trace only)
long startTimeNs = 0;
if(logger.isTraceEnabled()) {
startTimeNs = System.nanoTime();
}
long currentTime = time.milliseconds();
timeSensor.record((double) timeNS / voldemort.utils.Time.NS_PER_MS, currentTime);
emptyResponseKeysSensor.record(numEmptyResponses, currentTime);
valueBytesSensor.record(valueBytes, currentTime);
keyBytesSensor.record(keyBytes, currentTime);
getAllKeysCountSensor.record(getAllAggregatedCount, currentTime);
// timing instrumentation (trace only)
if(logger.isTraceEnabled()) {
logger.trace("addRequest took " + (System.nanoTime() - startTimeNs) + " ns.");
}
}
/**
* @return the number of requests that have returned returned no value for the requested key. Tracked only for GET.
*/
public long getNumEmptyResponses() {
return (long) emptyResponseKeysSampledTotal.value();
}
/**
* @return the size of the largest response or request in bytes returned. Tracked only for GET, GET_ALL and PUT.
*/
public long getMaxValueSizeInBytes() {
return (long) valueBytesMax.value();
}
/**
* @return the size of the largest response or request in bytes returned.
*/
public long getMaxKeySizeInBytes() {
return (long) keyBytesMax.value();
}
/**
* @return the average size of all the versioned values returned. Tracked only for GET, GET_ALL and PUT.
*/
public double getAverageValueSizeInBytes() {
return valueBytesAverage.value();
}
/**
* @return the average size of all the keys. Tracked for all operations.
*/
public double getAverageKeySizeInBytes() {
return keyBytesAverage.value();
}
/**
* @return the aggregated number of keys returned across all getAll calls,
* taking into account multiple values returned per call.
*/
public long getGetAllAggregatedCount() {
return (long) getAllKeysCountSampledTotal.value();
}
/**
* @return the maximum number of keys returned across all getAll calls.
*/
public long getGetAllMaxCount() {
return (long) getAllKeysCountMax.value();
}
public double getQ10LatencyMs() {
return latency10thPercentile.value();
}
public double getQ50LatencyMs() {
return latency50thPercentile.value();
}
public double getQ95LatencyMs() {
return latency95thPercentile.value();
}
public double getQ99LatencyMs() {
return latency99thPercentile.value();
}
}