forked from voldemort/voldemort
/
ClientSocketStats.java
375 lines (331 loc) · 14.2 KB
/
ClientSocketStats.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
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
/*
* Copyright 2008-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 java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import voldemort.store.socket.SocketDestination;
import voldemort.store.socket.clientrequest.ClientRequestExecutor;
import voldemort.utils.JmxUtils;
import voldemort.utils.pool.QueuedKeyedResourcePool;
/**
* Some convenient statistics to track about the client requests
*
*
*/
// TODO: This approach to stats tracking seems scary. All of the getter methods
// query current counters/histograms that are being updated. If you happen to
// use a getter soon after the monitoringInterval has rolled over, then your
// answer is likely statistically insignificant and potentially totally whacked
// out (not a technical term, sorry). Either of the following approaches seem
// like an improvement to me:
//
// (1) Effectively have two copies of all stats tracking "current" and "prev".
// All the getters would access "last". This means the responses are
// statistically meaningful, but potentially stale. reset() would copy
// "current" to "prev" and then resets "current".
//
// (2) A more general variant of (1) is to have n copies of all stats tracking.
// The getters would aggregated over all n copies of stats tracking. This
// provides a "sliding" window of statistically valid responses. reset() would
// create a new stats tracking object and delete the oldest stats trackig
// object.
public class ClientSocketStats {
private final ClientSocketStats parent;
private final ConcurrentMap<SocketDestination, ClientSocketStats> statsMap;
private final SocketDestination destination;
private QueuedKeyedResourcePool<SocketDestination, ClientRequestExecutor> pool;
// monitoringInterval <= connectionCheckouts + resourceRequests
private final AtomicInteger monitoringInterval = new AtomicInteger(10000);
// Connection lifecycle
private final AtomicInteger connectionsCreated = new AtomicInteger(0);
private final AtomicInteger connectionsDestroyed = new AtomicInteger(0);
// "Sync checkouts" / KeyedResourcePool::checkout
private final Histogram checkoutTimeUsHistogram = new Histogram(20000, 100);
private final AtomicLong totalCheckoutTimeUs = new AtomicLong(0);
private final AtomicInteger checkoutCount = new AtomicInteger(0);
private final Histogram checkoutQueueLengthHistogram = new Histogram(250, 1);
// "Async checkouts" / QueuedKeyedResourcePool::registerResourceRequest
private final Histogram resourceRequestTimeUsHistogram = new Histogram(20000, 100);
private final AtomicLong totalResourceRequestTimeUs = new AtomicLong(0);
private final AtomicInteger resourceRequestCount = new AtomicInteger(0);
private final Histogram resourceRequestQueueLengthHistogram = new Histogram(250, 1);
private final int jmxId;
/**
* To construct a per node stats object
*
* @param parent An optional parent stats object that will maintain
* aggregate data across many sockets
* @param destination The destination object that defines the node
* @param pool The socket pool that will give out connection information
*/
public ClientSocketStats(ClientSocketStats parent,
SocketDestination destination,
QueuedKeyedResourcePool<SocketDestination, ClientRequestExecutor> pool,
int jmxId) {
this.parent = parent;
this.statsMap = null;
this.destination = destination;
this.pool = pool;
this.jmxId = jmxId;
}
/**
* Construction of a new aggregate stats object
*
* @param pool The socket pool that will give out connection information
*/
public ClientSocketStats(int jmxId) {
this.parent = null;
this.statsMap = new ConcurrentHashMap<SocketDestination, ClientSocketStats>();
this.destination = null;
this.pool = null;
this.jmxId = jmxId;
}
/* get per node stats, create one if not exist */
private ClientSocketStats getOrCreateNodeStats(SocketDestination destination) {
if(destination == null) {
return null;
}
ClientSocketStats stats = statsMap.get(destination);
if(stats == null) {
stats = new ClientSocketStats(this, destination, pool, jmxId);
statsMap.putIfAbsent(destination, stats);
stats = statsMap.get(destination);
JmxUtils.registerMbean(new ClientSocketStatsJmx(stats),
JmxUtils.createObjectName(JmxUtils.getPackageName(ClientRequestExecutor.class),
"stats_"
+ destination.toString()
.replace(':', '_')
+ JmxUtils.getJmxId(jmxId)));
}
return stats;
}
/**
* Record the checkout wait time in us
*
* @param dest Destination of the socket to checkout. Will actually record
* if null. Otherwise will call this on self and corresponding child
* with this param null.
* @param checkoutTimeUs The number of us to wait before getting a socket
*/
public void recordCheckoutTimeUs(SocketDestination dest, long checkoutTimeUs) {
if(dest != null) {
getOrCreateNodeStats(dest).recordCheckoutTimeUs(null, checkoutTimeUs);
recordCheckoutTimeUs(null, checkoutTimeUs);
} else {
this.totalCheckoutTimeUs.getAndAdd(checkoutTimeUs);
this.checkoutTimeUsHistogram.insert(checkoutTimeUs);
this.checkoutCount.getAndIncrement();
checkMonitoringInterval();
}
}
/**
* Record the checkout queue length
*
* @param dest Destination of the socket to checkout. Will actually record
* if null. Otherwise will call this on self and corresponding child
* with this param null.
* @param queueLength The number of entries in the "synchronous" checkout
* queue.
*/
public void recordCheckoutQueueLength(SocketDestination dest, int queueLength) {
if(dest != null) {
getOrCreateNodeStats(dest).recordCheckoutQueueLength(null, queueLength);
recordCheckoutQueueLength(null, queueLength);
} else {
this.checkoutQueueLengthHistogram.insert(queueLength);
}
}
/**
* Record the resource request wait time in us
*
* @param dest Destination of the socket for which the resource was
* requested. Will actually record if null. Otherwise will call this
* on self and corresponding child with this param null.
* @param resourceRequestTimeUs The number of us to wait before getting a
* socket
*/
public void recordResourceRequestTimeUs(SocketDestination dest, long resourceRequestTimeUs) {
if(dest != null) {
getOrCreateNodeStats(dest).recordResourceRequestTimeUs(null, resourceRequestTimeUs);
recordResourceRequestTimeUs(null, resourceRequestTimeUs);
} else {
this.totalResourceRequestTimeUs.getAndAdd(resourceRequestTimeUs);
this.resourceRequestTimeUsHistogram.insert(resourceRequestTimeUs);
this.resourceRequestCount.getAndIncrement();
checkMonitoringInterval();
}
}
/**
* Record the resource request queue length
*
* @param dest Destination of the socket for which resource request is
* enqueued. Will actually record if null. Otherwise will call this
* on self and corresponding child with this param null.
* @param queueLength The number of entries in the "asynchronous" resource
* request queue.
*/
public void recordResourceRequestQueueLength(SocketDestination dest, int queueLength) {
if(dest != null) {
getOrCreateNodeStats(dest).recordResourceRequestQueueLength(null, queueLength);
recordResourceRequestQueueLength(null, queueLength);
} else {
this.resourceRequestQueueLengthHistogram.insert(queueLength);
}
}
public void connectionCreate(SocketDestination dest) {
if(dest != null) {
getOrCreateNodeStats(dest).connectionCreate(null);
connectionCreate(null);
} else {
this.connectionsCreated.getAndIncrement();
}
}
public void connectionDestroy(SocketDestination dest) {
if(dest != null) {
getOrCreateNodeStats(dest).connectionDestroy(null);
connectionDestroy(null);
} else {
this.connectionsDestroyed.getAndIncrement();
}
}
// Getters for connection life cycle stats
public int getConnectionsCreated() {
return connectionsCreated.intValue();
}
public int getConnectionsDestroyed() {
return connectionsDestroyed.intValue();
}
// Getters for checkout stats
public int getCheckoutCount() {
return checkoutCount.intValue();
}
public Histogram getCheckoutWaitUsHistogram() {
return this.checkoutTimeUsHistogram;
}
/**
* @return 0 if there have been no checkout invocations
*/
public long getAvgCheckoutWaitUs() {
long count = checkoutCount.get();
if(count > 0)
return totalCheckoutTimeUs.get() / count;
return 0;
}
public Histogram getCheckoutQueueLengthHistogram() {
return this.checkoutQueueLengthHistogram;
}
// Getters for resourceRequest stats
public int resourceRequestCount() {
return resourceRequestCount.intValue();
}
public Histogram getResourceRequestWaitUsHistogram() {
return this.resourceRequestTimeUsHistogram;
}
/**
* @return 0 if there have been no resourceRequest invocations
*/
public long getAvgResourceRequestWaitUs() {
long count = resourceRequestCount.get();
if(count > 0)
return totalResourceRequestTimeUs.get() / count;
return 0;
}
public Histogram getResourceRequestQueueLengthHistogram() {
return this.resourceRequestQueueLengthHistogram;
}
// Getters for (queued)pool stats
public int getConnectionsActive(SocketDestination destination) {
if(destination == null) {
return pool.getTotalResourceCount();
} else {
return pool.getTotalResourceCount(destination);
}
}
public int getConnectionsInPool(SocketDestination destination) {
if(destination == null) {
return pool.getCheckedInResourceCount();
} else {
return pool.getCheckedInResourcesCount(destination);
}
}
// Config & administrivia interfaces
public void setMonitoringInterval(int count) {
this.monitoringInterval.set(count);
}
public int getMonitoringInterval() {
return this.monitoringInterval.get();
}
protected void checkMonitoringInterval() {
int monitoringCount = this.checkoutCount.get() + this.resourceRequestCount.get();
// reset aggregated stats and all the node stats for new interval
if(parent == null && statsMap != null) {
int monitoringInterval = this.monitoringInterval.get();
if(monitoringCount % (monitoringInterval + 1) == monitoringInterval) {
// reset all children
Iterator<SocketDestination> it = statsMap.keySet().iterator();
while(it.hasNext()) {
ClientSocketStats stats = statsMap.get(it.next());
stats.resetForInterval();
}
// reset itself
resetForInterval();
}
}
}
/**
* Reset all of the stats counters
*/
protected void resetForInterval() {
// harmless race conditions amongst all of this counter resetting:
this.totalCheckoutTimeUs.set(0);
this.checkoutCount.set(0);
this.checkoutTimeUsHistogram.reset();
this.checkoutQueueLengthHistogram.reset();
this.totalResourceRequestTimeUs.set(0);
this.resourceRequestCount.set(0);
this.resourceRequestTimeUsHistogram.reset();
this.resourceRequestQueueLengthHistogram.reset();
}
public void setPool(QueuedKeyedResourcePool<SocketDestination, ClientRequestExecutor> pool) {
this.pool = pool;
}
public ConcurrentMap<SocketDestination, ClientSocketStats> getStatsMap() {
return statsMap;
}
SocketDestination getDestination() {
return destination;
}
/**
* Unregister all MBeans
*/
public void close() {
Iterator<SocketDestination> it = getStatsMap().keySet().iterator();
while(it.hasNext()) {
try {
SocketDestination destination = it.next();
JmxUtils.unregisterMbean(JmxUtils.createObjectName(JmxUtils.getPackageName(ClientRequestExecutor.class),
"stats_"
+ destination.toString()
.replace(':',
'_')
+ JmxUtils.getJmxId(jmxId)));
} catch(Exception e) {}
}
}
}