1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.client;
19
20 import com.google.common.annotations.VisibleForTesting;
21 import com.google.protobuf.Descriptors.MethodDescriptor;
22 import com.google.protobuf.Message;
23 import com.yammer.metrics.core.Counter;
24 import com.yammer.metrics.core.Histogram;
25 import com.yammer.metrics.core.MetricName;
26 import com.yammer.metrics.core.MetricsRegistry;
27 import com.yammer.metrics.core.Timer;
28 import com.yammer.metrics.reporting.JmxReporter;
29 import com.yammer.metrics.util.RatioGauge;
30 import org.apache.hadoop.hbase.ServerName;
31 import org.apache.hadoop.hbase.classification.InterfaceAudience;
32 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos;
33 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ClientService;
34 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutateRequest;
35 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutationProto.MutationType;
36 import org.apache.hadoop.hbase.util.Bytes;
37
38 import java.util.concurrent.ConcurrentHashMap;
39 import java.util.concurrent.ConcurrentSkipListMap;
40 import java.util.concurrent.ConcurrentMap;
41 import java.util.concurrent.ThreadPoolExecutor;
42 import java.util.concurrent.TimeUnit;
43
44
45
46
47
48
49
50
51
52
53 @InterfaceAudience.Private
54 public class MetricsConnection implements StatisticTrackable {
55
56
57 public static final String CLIENT_SIDE_METRICS_ENABLED_KEY = "hbase.client.metrics.enable";
58
59 private static final String DRTN_BASE = "rpcCallDurationMs_";
60 private static final String REQ_BASE = "rpcCallRequestSizeBytes_";
61 private static final String RESP_BASE = "rpcCallResponseSizeBytes_";
62 private static final String MEMLOAD_BASE = "memstoreLoad_";
63 private static final String HEAP_BASE = "heapOccupancy_";
64 private static final String CACHE_BASE = "cacheDroppingExceptions_";
65 private static final String UNKNOWN_EXCEPTION = "UnknownException";
66 private static final String CLIENT_SVC = ClientService.getDescriptor().getName();
67
68
69 public static class CallStats {
70 private long requestSizeBytes = 0;
71 private long responseSizeBytes = 0;
72 private long startTime = 0;
73 private long callTimeMs = 0;
74 private int concurrentCallsPerServer = 0;
75
76 public long getRequestSizeBytes() {
77 return requestSizeBytes;
78 }
79
80 public void setRequestSizeBytes(long requestSizeBytes) {
81 this.requestSizeBytes = requestSizeBytes;
82 }
83
84 public long getResponseSizeBytes() {
85 return responseSizeBytes;
86 }
87
88 public void setResponseSizeBytes(long responseSizeBytes) {
89 this.responseSizeBytes = responseSizeBytes;
90 }
91
92 public long getStartTime() {
93 return startTime;
94 }
95
96 public void setStartTime(long startTime) {
97 this.startTime = startTime;
98 }
99
100 public long getCallTimeMs() {
101 return callTimeMs;
102 }
103
104 public void setCallTimeMs(long callTimeMs) {
105 this.callTimeMs = callTimeMs;
106 }
107
108 public int getConcurrentCallsPerServer() {
109 return concurrentCallsPerServer;
110 }
111
112 public void setConcurrentCallsPerServer(int callsPerServer) {
113 this.concurrentCallsPerServer = callsPerServer;
114 }
115 }
116
117 @VisibleForTesting
118 protected static final class CallTracker {
119 private final String name;
120 @VisibleForTesting final Timer callTimer;
121 @VisibleForTesting final Histogram reqHist;
122 @VisibleForTesting final Histogram respHist;
123
124 private CallTracker(MetricsRegistry registry, String name, String subName, String scope) {
125 StringBuilder sb = new StringBuilder(CLIENT_SVC).append("_").append(name);
126 if (subName != null) {
127 sb.append("(").append(subName).append(")");
128 }
129 this.name = sb.toString();
130 this.callTimer = registry.newTimer(MetricsConnection.class, DRTN_BASE + this.name, scope);
131 this.reqHist = registry.newHistogram(MetricsConnection.class, REQ_BASE + this.name, scope);
132 this.respHist = registry.newHistogram(MetricsConnection.class, RESP_BASE + this.name, scope);
133 }
134
135 private CallTracker(MetricsRegistry registry, String name, String scope) {
136 this(registry, name, null, scope);
137 }
138
139 public void updateRpc(CallStats stats) {
140 this.callTimer.update(stats.getCallTimeMs(), TimeUnit.MILLISECONDS);
141 this.reqHist.update(stats.getRequestSizeBytes());
142 this.respHist.update(stats.getResponseSizeBytes());
143 }
144
145 @Override
146 public String toString() {
147 return "CallTracker:" + name;
148 }
149 }
150
151 protected static class RegionStats {
152 final String name;
153 final Histogram memstoreLoadHist;
154 final Histogram heapOccupancyHist;
155
156 public RegionStats(MetricsRegistry registry, String name) {
157 this.name = name;
158 this.memstoreLoadHist = registry.newHistogram(MetricsConnection.class,
159 MEMLOAD_BASE + this.name);
160 this.heapOccupancyHist = registry.newHistogram(MetricsConnection.class,
161 HEAP_BASE + this.name);
162 }
163
164 public void update(ClientProtos.RegionLoadStats regionStatistics) {
165 this.memstoreLoadHist.update(regionStatistics.getMemstoreLoad());
166 this.heapOccupancyHist.update(regionStatistics.getHeapOccupancy());
167 }
168 }
169
170 @VisibleForTesting
171 protected static class RunnerStats {
172 final Counter normalRunners;
173 final Counter delayRunners;
174 final Histogram delayIntevalHist;
175
176 public RunnerStats(MetricsRegistry registry) {
177 this.normalRunners = registry.newCounter(MetricsConnection.class, "normalRunnersCount");
178 this.delayRunners = registry.newCounter(MetricsConnection.class, "delayRunnersCount");
179 this.delayIntevalHist = registry.newHistogram(MetricsConnection.class, "delayIntervalHist");
180 }
181
182 public void incrNormalRunners() {
183 this.normalRunners.inc();
184 }
185
186 public void incrDelayRunners() {
187 this.delayRunners.inc();
188 }
189
190 public void updateDelayInterval(long interval) {
191 this.delayIntevalHist.update(interval);
192 }
193 }
194
195 @VisibleForTesting
196 protected ConcurrentHashMap<ServerName, ConcurrentMap<byte[], RegionStats>> serverStats
197 = new ConcurrentHashMap<ServerName, ConcurrentMap<byte[], RegionStats>>();
198
199 public void updateServerStats(ServerName serverName, byte[] regionName,
200 Object r) {
201 if (!(r instanceof Result)) {
202 return;
203 }
204 Result result = (Result) r;
205 ClientProtos.RegionLoadStats stats = result.getStats();
206 if (stats == null) {
207 return;
208 }
209 updateRegionStats(serverName, regionName, stats);
210 }
211
212 @Override
213 public void updateRegionStats(ServerName serverName, byte[] regionName,
214 ClientProtos.RegionLoadStats stats) {
215 String name = serverName.getServerName() + "," + Bytes.toStringBinary(regionName);
216 ConcurrentMap<byte[], RegionStats> rsStats = null;
217 if (serverStats.containsKey(serverName)) {
218 rsStats = serverStats.get(serverName);
219 } else {
220 rsStats = serverStats.putIfAbsent(serverName,
221 new ConcurrentSkipListMap<byte[], RegionStats>(Bytes.BYTES_COMPARATOR));
222 if (rsStats == null) {
223 rsStats = serverStats.get(serverName);
224 }
225 }
226 RegionStats regionStats = null;
227 if (rsStats.containsKey(regionName)) {
228 regionStats = rsStats.get(regionName);
229 } else {
230 regionStats = rsStats.putIfAbsent(regionName, new RegionStats(this.registry, name));
231 if (regionStats == null) {
232 regionStats = rsStats.get(regionName);
233 }
234 }
235 regionStats.update(stats);
236 }
237
238
239
240 private static interface NewMetric<T> {
241 T newMetric(Class<?> clazz, String name, String scope);
242 }
243
244
245 private static final int CAPACITY = 50;
246
247 private static final float LOAD_FACTOR = 0.75f;
248
249
250
251
252 private static final int CONCURRENCY_LEVEL = 256;
253
254 private final MetricsRegistry registry;
255 private final JmxReporter reporter;
256 private final String scope;
257
258 private final NewMetric<Timer> timerFactory = new NewMetric<Timer>() {
259 @Override public Timer newMetric(Class<?> clazz, String name, String scope) {
260 return registry.newTimer(clazz, name, scope);
261 }
262 };
263
264 private final NewMetric<Histogram> histogramFactory = new NewMetric<Histogram>() {
265 @Override public Histogram newMetric(Class<?> clazz, String name, String scope) {
266 return registry.newHistogram(clazz, name, scope);
267 }
268 };
269
270 private final NewMetric<Counter> counterFactory = new NewMetric<Counter>() {
271 @Override public Counter newMetric(Class<?> clazz, String name, String scope) {
272 return registry.newCounter(clazz, name, scope);
273 }
274 };
275
276
277
278 @VisibleForTesting protected final Counter metaCacheHits;
279 @VisibleForTesting protected final Counter metaCacheMisses;
280 @VisibleForTesting protected final CallTracker getTracker;
281 @VisibleForTesting protected final CallTracker scanTracker;
282 @VisibleForTesting protected final CallTracker appendTracker;
283 @VisibleForTesting protected final CallTracker deleteTracker;
284 @VisibleForTesting protected final CallTracker incrementTracker;
285 @VisibleForTesting protected final CallTracker putTracker;
286 @VisibleForTesting protected final CallTracker multiTracker;
287 @VisibleForTesting protected final RunnerStats runnerStats;
288 @VisibleForTesting protected final Counter metaCacheNumClearServer;
289 @VisibleForTesting protected final Counter metaCacheNumClearRegion;
290 @VisibleForTesting protected final Counter hedgedReadOps;
291 @VisibleForTesting protected final Counter hedgedReadWin;
292 @VisibleForTesting protected final Histogram concurrentCallsPerServerHist;
293
294
295
296
297
298
299 @VisibleForTesting protected final ConcurrentMap<String, Timer> rpcTimers =
300 new ConcurrentHashMap<>(CAPACITY, LOAD_FACTOR, CONCURRENCY_LEVEL);
301 @VisibleForTesting protected final ConcurrentMap<String, Histogram> rpcHistograms =
302 new ConcurrentHashMap<>(CAPACITY * 2
303 LOAD_FACTOR, CONCURRENCY_LEVEL);
304 private final ConcurrentMap<String, Counter> cacheDroppingExceptions =
305 new ConcurrentHashMap<>(CAPACITY, LOAD_FACTOR, CONCURRENCY_LEVEL);
306
307 public MetricsConnection(final ConnectionManager.HConnectionImplementation conn) {
308 this.scope = conn.toString();
309 this.registry = new MetricsRegistry();
310
311 this.registry.newGauge(getExecutorPoolName(),
312 new RatioGauge() {
313 @Override protected double getNumerator() {
314 ThreadPoolExecutor batchPool = (ThreadPoolExecutor) conn.getCurrentBatchPool();
315 if (batchPool == null) {
316 return 0;
317 }
318 return batchPool.getActiveCount();
319 }
320 @Override protected double getDenominator() {
321 ThreadPoolExecutor batchPool = (ThreadPoolExecutor) conn.getCurrentBatchPool();
322 if (batchPool == null) {
323 return 0;
324 }
325 return batchPool.getMaximumPoolSize();
326 }
327 });
328 this.registry.newGauge(getMetaPoolName(),
329 new RatioGauge() {
330 @Override protected double getNumerator() {
331 ThreadPoolExecutor metaPool = (ThreadPoolExecutor) conn.getCurrentMetaLookupPool();
332 if (metaPool == null) {
333 return 0;
334 }
335 return metaPool.getActiveCount();
336 }
337 @Override protected double getDenominator() {
338 ThreadPoolExecutor metaPool = (ThreadPoolExecutor) conn.getCurrentMetaLookupPool();
339 if (metaPool == null) {
340 return 0;
341 }
342 return metaPool.getMaximumPoolSize();
343 }
344 });
345 this.metaCacheHits = registry.newCounter(this.getClass(), "metaCacheHits", scope);
346 this.metaCacheMisses = registry.newCounter(this.getClass(), "metaCacheMisses", scope);
347 this.metaCacheNumClearServer = registry.newCounter(this.getClass(),
348 "metaCacheNumClearServer", scope);
349 this.metaCacheNumClearRegion = registry.newCounter(this.getClass(),
350 "metaCacheNumClearRegion", scope);
351 this.hedgedReadOps = registry.newCounter(this.getClass(), "hedgedReadOps", scope);
352 this.hedgedReadWin = registry.newCounter(this.getClass(), "hedgedReadWin", scope);
353 this.getTracker = new CallTracker(this.registry, "Get", scope);
354 this.scanTracker = new CallTracker(this.registry, "Scan", scope);
355 this.appendTracker = new CallTracker(this.registry, "Mutate", "Append", scope);
356 this.deleteTracker = new CallTracker(this.registry, "Mutate", "Delete", scope);
357 this.incrementTracker = new CallTracker(this.registry, "Mutate", "Increment", scope);
358 this.putTracker = new CallTracker(this.registry, "Mutate", "Put", scope);
359 this.multiTracker = new CallTracker(this.registry, "Multi", scope);
360 this.runnerStats = new RunnerStats(this.registry);
361 this.concurrentCallsPerServerHist = registry.newHistogram(this.getClass(),
362 "concurrentCallsPerServer", scope);
363
364 this.reporter = new JmxReporter(this.registry);
365 this.reporter.start();
366 }
367
368 @VisibleForTesting
369 final MetricName getExecutorPoolName() {
370 return new MetricName(getClass(), "executorPoolActiveThreads", scope);
371 }
372
373 @VisibleForTesting
374 final MetricName getMetaPoolName() {
375 return new MetricName(getClass(), "metaPoolActiveThreads", scope);
376 }
377
378 @VisibleForTesting
379 MetricsRegistry getMetricsRegistry() {
380 return registry;
381 }
382
383 public void shutdown() {
384 this.reporter.shutdown();
385 this.registry.shutdown();
386 }
387
388
389 public static CallStats newCallStats() {
390
391 return new CallStats();
392 }
393
394
395 public void incrMetaCacheHit() {
396 metaCacheHits.inc();
397 }
398
399
400 public void incrMetaCacheMiss() {
401 metaCacheMisses.inc();
402 }
403
404
405 public void incrMetaCacheNumClearServer() {
406 metaCacheNumClearServer.inc();
407 }
408
409
410 public void incrMetaCacheNumClearRegion() {
411 metaCacheNumClearRegion.inc();
412 }
413
414
415 public void incrHedgedReadOps() {
416 hedgedReadOps.inc();
417 }
418
419
420 public void incrHedgedReadWin() {
421 hedgedReadWin.inc();
422 }
423
424
425 public void incrNormalRunners() {
426 this.runnerStats.incrNormalRunners();
427 }
428
429
430 public void incrDelayRunners() {
431 this.runnerStats.incrDelayRunners();
432 }
433
434
435 public void updateDelayInterval(long interval) {
436 this.runnerStats.updateDelayInterval(interval);
437 }
438
439
440
441
442 private <T> T getMetric(String key, ConcurrentMap<String, T> map, NewMetric<T> factory) {
443 T t = map.get(key);
444 if (t == null) {
445 t = factory.newMetric(this.getClass(), key, scope);
446 T tmp = map.putIfAbsent(key, t);
447 t = (tmp == null) ? t : tmp;
448 }
449 return t;
450 }
451
452
453 private void updateRpcGeneric(MethodDescriptor method, CallStats stats) {
454 final String methodName = method.getService().getName() + "_" + method.getName();
455 getMetric(DRTN_BASE + methodName, rpcTimers, timerFactory)
456 .update(stats.getCallTimeMs(), TimeUnit.MILLISECONDS);
457 getMetric(REQ_BASE + methodName, rpcHistograms, histogramFactory)
458 .update(stats.getRequestSizeBytes());
459 getMetric(RESP_BASE + methodName, rpcHistograms, histogramFactory)
460 .update(stats.getResponseSizeBytes());
461 }
462
463
464 public void updateRpc(MethodDescriptor method, Message param, CallStats stats) {
465 int callsPerServer = stats.getConcurrentCallsPerServer();
466 if (callsPerServer > 0) {
467 concurrentCallsPerServerHist.update(callsPerServer);
468 }
469
470
471 if (method.getService() == ClientService.getDescriptor()) {
472 switch(method.getIndex()) {
473 case 0:
474 assert "Get".equals(method.getName());
475 getTracker.updateRpc(stats);
476 return;
477 case 1:
478 assert "Mutate".equals(method.getName());
479 final MutationType mutationType = ((MutateRequest) param).getMutation().getMutateType();
480 switch(mutationType) {
481 case APPEND:
482 appendTracker.updateRpc(stats);
483 return;
484 case DELETE:
485 deleteTracker.updateRpc(stats);
486 return;
487 case INCREMENT:
488 incrementTracker.updateRpc(stats);
489 return;
490 case PUT:
491 putTracker.updateRpc(stats);
492 return;
493 default:
494 throw new RuntimeException("Unrecognized mutation type " + mutationType);
495 }
496 case 2:
497 assert "Scan".equals(method.getName());
498 scanTracker.updateRpc(stats);
499 return;
500 case 3:
501 assert "BulkLoadHFile".equals(method.getName());
502
503 break;
504 case 4:
505 assert "ExecService".equals(method.getName());
506
507 break;
508 case 5:
509 assert "ExecRegionServerService".equals(method.getName());
510
511 break;
512 case 6:
513 assert "Multi".equals(method.getName());
514 multiTracker.updateRpc(stats);
515 return;
516 default:
517 throw new RuntimeException("Unrecognized ClientService RPC type " + method.getFullName());
518 }
519 }
520
521 updateRpcGeneric(method, stats);
522 }
523
524 public void incrCacheDroppingExceptions(Object exception) {
525 getMetric(CACHE_BASE +
526 (exception == null? UNKNOWN_EXCEPTION : exception.getClass().getSimpleName()),
527 cacheDroppingExceptions, counterFactory).inc();
528 }
529 }