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