001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.regionserver;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertNotEquals;
022import static org.junit.Assert.assertNotNull;
023import static org.junit.Assert.assertNull;
024import static org.junit.Assert.assertTrue;
025
026import java.io.IOException;
027import java.util.ArrayList;
028import java.util.List;
029import java.util.Optional;
030import org.apache.hadoop.conf.Configuration;
031import org.apache.hadoop.hbase.CompatibilityFactory;
032import org.apache.hadoop.hbase.HBaseClassTestRule;
033import org.apache.hadoop.hbase.HBaseTestingUtil;
034import org.apache.hadoop.hbase.HConstants;
035import org.apache.hadoop.hbase.HRegionLocation;
036import org.apache.hadoop.hbase.NamespaceDescriptor;
037import org.apache.hadoop.hbase.SingleProcessHBaseCluster;
038import org.apache.hadoop.hbase.TableName;
039import org.apache.hadoop.hbase.client.Admin;
040import org.apache.hadoop.hbase.client.Append;
041import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
042import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
043import org.apache.hadoop.hbase.client.Connection;
044import org.apache.hadoop.hbase.client.Durability;
045import org.apache.hadoop.hbase.client.Get;
046import org.apache.hadoop.hbase.client.Increment;
047import org.apache.hadoop.hbase.client.Put;
048import org.apache.hadoop.hbase.client.RegionInfo;
049import org.apache.hadoop.hbase.client.RegionLocator;
050import org.apache.hadoop.hbase.client.Result;
051import org.apache.hadoop.hbase.client.ResultScanner;
052import org.apache.hadoop.hbase.client.Scan;
053import org.apache.hadoop.hbase.client.Scan.ReadType;
054import org.apache.hadoop.hbase.client.Table;
055import org.apache.hadoop.hbase.client.TableDescriptor;
056import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
057import org.apache.hadoop.hbase.regionserver.compactions.CompactionContext;
058import org.apache.hadoop.hbase.regionserver.compactions.CompactionLifeCycleTracker;
059import org.apache.hadoop.hbase.regionserver.throttle.NoLimitThroughputController;
060import org.apache.hadoop.hbase.security.User;
061import org.apache.hadoop.hbase.test.MetricsAssertHelper;
062import org.apache.hadoop.hbase.testclassification.LargeTests;
063import org.apache.hadoop.hbase.testclassification.RegionServerTests;
064import org.apache.hadoop.hbase.util.Bytes;
065import org.apache.hadoop.hbase.util.Threads;
066import org.junit.After;
067import org.junit.AfterClass;
068import org.junit.Before;
069import org.junit.BeforeClass;
070import org.junit.ClassRule;
071import org.junit.Ignore;
072import org.junit.Rule;
073import org.junit.Test;
074import org.junit.experimental.categories.Category;
075import org.junit.rules.TestName;
076import org.slf4j.Logger;
077import org.slf4j.LoggerFactory;
078
079@Category({ RegionServerTests.class, LargeTests.class })
080public class TestRegionServerMetrics {
081
082  @ClassRule
083  public static final HBaseClassTestRule CLASS_RULE =
084    HBaseClassTestRule.forClass(TestRegionServerMetrics.class);
085
086  private static final Logger LOG = LoggerFactory.getLogger(TestRegionServerMetrics.class);
087
088  @Rule
089  public TestName testName = new TestName();
090
091  private static MetricsAssertHelper metricsHelper;
092  private static SingleProcessHBaseCluster cluster;
093  private static HRegionServer rs;
094  private static Configuration conf;
095  private static HBaseTestingUtil TEST_UTIL;
096  private static Connection connection;
097  private static MetricsRegionServer metricsRegionServer;
098  private static MetricsRegionServerSource serverSource;
099  private static final int NUM_SCAN_NEXT = 30;
100  private static int numScanNext = 0;
101  private static byte[] cf = Bytes.toBytes("cf");
102  private static byte[] row = Bytes.toBytes("row");
103  private static byte[] qualifier = Bytes.toBytes("qual");
104  private static byte[] val = Bytes.toBytes("val");
105  private static Admin admin;
106
107  @BeforeClass
108  public static void startCluster() throws Exception {
109    metricsHelper = CompatibilityFactory.getInstance(MetricsAssertHelper.class);
110    TEST_UTIL = new HBaseTestingUtil();
111    conf = TEST_UTIL.getConfiguration();
112    conf.getLong("hbase.splitlog.max.resubmit", 0);
113    // Make the failure test faster
114    conf.setInt("zookeeper.recovery.retry", 0);
115    // testMobMetrics creates few hfiles and manages compaction manually.
116    conf.setInt("hbase.hstore.compactionThreshold", 100);
117    conf.setInt("hbase.hstore.compaction.max", 100);
118    conf.setInt("hbase.regionserver.periodicmemstoreflusher.rangeofdelayseconds", 4 * 60);
119    conf.setInt(HConstants.REGIONSERVER_INFO_PORT, -1);
120
121    TEST_UTIL.startMiniCluster();
122    cluster = TEST_UTIL.getHBaseCluster();
123    cluster.waitForActiveAndReadyMaster();
124    admin = TEST_UTIL.getAdmin();
125    connection = TEST_UTIL.getConnection();
126
127    while (
128      cluster.getLiveRegionServerThreads().isEmpty() && cluster.getRegionServer(0) == null
129        && rs.getMetrics() == null
130    ) {
131      Threads.sleep(100);
132    }
133    rs = cluster.getRegionServer(0);
134    metricsRegionServer = rs.getMetrics();
135    serverSource = metricsRegionServer.getMetricsSource();
136  }
137
138  @AfterClass
139  public static void after() throws Exception {
140    if (TEST_UTIL != null) {
141      TEST_UTIL.shutdownMiniCluster();
142    }
143  }
144
145  TableName tableName;
146  Table table;
147
148  @Before
149  public void beforeTestMethod() throws Exception {
150    metricsRegionServer.getRegionServerWrapper().forceRecompute();
151    tableName = TableName.valueOf(testName.getMethodName());
152    table = TEST_UTIL.createTable(tableName, cf);
153  }
154
155  @After
156  public void afterTestMethod() throws Exception {
157    admin.disableTable(tableName);
158    admin.deleteTable(tableName);
159  }
160
161  private void assertCounter(String metric, long expectedValue) {
162    metricsHelper.assertCounter(metric, expectedValue, serverSource);
163  }
164
165  private void assertGauge(String metric, long expectedValue) {
166    metricsHelper.assertGauge(metric, expectedValue, serverSource);
167  }
168
169  // Aggregates metrics from regions and assert given list of metrics and expected values.
170  private void assertRegionMetrics(String metric, long expectedValue) throws Exception {
171    try (RegionLocator locator = connection.getRegionLocator(tableName)) {
172      for (HRegionLocation location : locator.getAllRegionLocations()) {
173        RegionInfo hri = location.getRegion();
174        MetricsRegionAggregateSource agg =
175          rs.getRegion(hri.getRegionName()).getMetrics().getSource().getAggregateSource();
176        String prefix = "namespace_" + NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR + "_table_"
177          + tableName.getNameAsString() + "_region_" + hri.getEncodedName() + "_metric_";
178        metricsHelper.assertCounter(prefix + metric, expectedValue, agg);
179      }
180    }
181  }
182
183  private void doNPuts(int n, boolean batch) throws Exception {
184    if (batch) {
185      List<Put> puts = new ArrayList<>();
186      for (int i = 0; i < n; i++) {
187        Put p = new Put(Bytes.toBytes("" + i + "row")).addColumn(cf, qualifier, val);
188        puts.add(p);
189      }
190      table.put(puts);
191    } else {
192      for (int i = 0; i < n; i++) {
193        Put p = new Put(row).addColumn(cf, qualifier, val);
194        table.put(p);
195      }
196    }
197  }
198
199  private void doNGets(int n, boolean batch) throws Exception {
200    if (batch) {
201      List<Get> gets = new ArrayList<>();
202      for (int i = 0; i < n; i++) {
203        gets.add(new Get(row));
204      }
205      table.get(gets);
206    } else {
207      for (int i = 0; i < n; i++) {
208        table.get(new Get(row));
209      }
210    }
211  }
212
213  private void doScan(int n, boolean caching) throws IOException {
214    Scan scan = new Scan();
215    if (caching) {
216      scan.setCaching(n);
217    } else {
218      scan.setCaching(1);
219    }
220    ResultScanner scanner = table.getScanner(scan);
221    for (int i = 0; i < n; i++) {
222      Result res = scanner.next();
223      LOG.debug("Result row: " + Bytes.toString(res.getRow()) + ", value: "
224        + Bytes.toString(res.getValue(cf, qualifier)));
225    }
226  }
227
228  @Test
229  public void testRegionCount() throws Exception {
230    metricsHelper.assertGauge("regionCount", 2, serverSource);
231  }
232
233  @Test
234  public void testLocalFiles() throws Exception {
235    assertGauge("percentFilesLocal", 0);
236    assertGauge("percentFilesLocalSecondaryRegions", 0);
237  }
238
239  @Test
240  public void testRequestCount() throws Exception {
241    // Do a first put to be sure that the connection is established, meta is there and so on.
242    doNPuts(1, false);
243
244    metricsRegionServer.getRegionServerWrapper().forceRecompute();
245    long requests = metricsHelper.getCounter("totalRequestCount", serverSource);
246    long rowActionRequests = metricsHelper.getCounter("totalRowActionRequestCount", serverSource);
247    long readRequests = metricsHelper.getCounter("readRequestCount", serverSource);
248    long writeRequests = metricsHelper.getCounter("writeRequestCount", serverSource);
249
250    doNPuts(30, false);
251
252    metricsRegionServer.getRegionServerWrapper().forceRecompute();
253    assertCounter("totalRequestCount", requests + 30);
254    assertCounter("totalRowActionRequestCount", rowActionRequests + 30);
255    assertCounter("readRequestCount", readRequests);
256    assertCounter("writeRequestCount", writeRequests + 30);
257
258    doNGets(10, false);
259
260    metricsRegionServer.getRegionServerWrapper().forceRecompute();
261    assertCounter("totalRequestCount", requests + 40);
262    assertCounter("totalRowActionRequestCount", rowActionRequests + 40);
263    assertCounter("readRequestCount", readRequests + 10);
264    assertCounter("writeRequestCount", writeRequests + 30);
265
266    assertRegionMetrics("getCount", 10);
267    assertRegionMetrics("putCount", 31);
268
269    doNGets(10, true); // true = batch
270
271    metricsRegionServer.getRegionServerWrapper().forceRecompute();
272
273    assertCounter("writeRequestCount", writeRequests + 30);
274
275    doNPuts(30, true);
276
277    metricsRegionServer.getRegionServerWrapper().forceRecompute();
278    assertCounter("writeRequestCount", writeRequests + 60);
279
280    doScan(10, false); // test after batch put so we have enough lines
281    metricsRegionServer.getRegionServerWrapper().forceRecompute();
282    assertCounter("writeRequestCount", writeRequests + 60);
283    numScanNext += 10;
284
285    doScan(10, true); // true = caching
286    metricsRegionServer.getRegionServerWrapper().forceRecompute();
287    assertCounter("writeRequestCount", writeRequests + 60);
288    numScanNext += 1;
289  }
290
291  @Test
292  public void testGet() throws Exception {
293    // Do a first put to be sure that the connection is established, meta is there and so on.
294    doNPuts(1, false);
295    doNGets(10, false);
296    assertRegionMetrics("getCount", 10);
297    metricsHelper.assertCounterGt("Get_num_ops", 10, serverSource);
298  }
299
300  @Test
301  public void testMutationsWithoutWal() throws Exception {
302    Put p = new Put(row).addColumn(cf, qualifier, val).setDurability(Durability.SKIP_WAL);
303    table.put(p);
304
305    metricsRegionServer.getRegionServerWrapper().forceRecompute();
306    assertGauge("mutationsWithoutWALCount", 1);
307    long minLength = row.length + cf.length + qualifier.length + val.length;
308    metricsHelper.assertGaugeGt("mutationsWithoutWALSize", minLength, serverSource);
309  }
310
311  @Test
312  public void testStoreCount() throws Exception {
313    // Force a hfile.
314    doNPuts(1, false);
315    TEST_UTIL.getAdmin().flush(tableName);
316
317    metricsRegionServer.getRegionServerWrapper().forceRecompute();
318    assertGauge("storeCount", 5);
319    assertGauge("storeFileCount", 1);
320  }
321
322  @Test
323  public void testStoreFileAge() throws Exception {
324    // Force a hfile.
325    doNPuts(1, false);
326    TEST_UTIL.getAdmin().flush(tableName);
327
328    metricsRegionServer.getRegionServerWrapper().forceRecompute();
329    assertTrue(metricsHelper.getGaugeLong("maxStoreFileAge", serverSource) > 0);
330    assertTrue(metricsHelper.getGaugeLong("minStoreFileAge", serverSource) > 0);
331    assertTrue(metricsHelper.getGaugeLong("avgStoreFileAge", serverSource) > 0);
332  }
333
334  @Test
335  public void testCheckAndPutCount() throws Exception {
336    byte[] valOne = Bytes.toBytes("Value");
337    byte[] valTwo = Bytes.toBytes("ValueTwo");
338    byte[] valThree = Bytes.toBytes("ValueThree");
339
340    Put p = new Put(row);
341    p.addColumn(cf, qualifier, valOne);
342    table.put(p);
343
344    Put pTwo = new Put(row);
345    pTwo.addColumn(cf, qualifier, valTwo);
346    table.checkAndMutate(row, cf).qualifier(qualifier).ifEquals(valOne).thenPut(pTwo);
347
348    Put pThree = new Put(row);
349    pThree.addColumn(cf, qualifier, valThree);
350    table.checkAndMutate(row, cf).qualifier(qualifier).ifEquals(valOne).thenPut(pThree);
351
352    metricsRegionServer.getRegionServerWrapper().forceRecompute();
353    assertCounter("checkMutateFailedCount", 1);
354    assertCounter("checkMutatePassedCount", 1);
355  }
356
357  @Test
358  public void testIncrement() throws Exception {
359    Put p = new Put(row).addColumn(cf, qualifier, Bytes.toBytes(0L));
360    table.put(p);
361
362    for (int count = 0; count < 13; count++) {
363      Increment inc = new Increment(row);
364      inc.addColumn(cf, qualifier, 100);
365      table.increment(inc);
366    }
367
368    metricsRegionServer.getRegionServerWrapper().forceRecompute();
369    assertCounter("incrementNumOps", 13);
370  }
371
372  @Test
373  public void testAppend() throws Exception {
374    doNPuts(1, false);
375
376    for (int count = 0; count < 73; count++) {
377      Append append = new Append(row);
378      append.addColumn(cf, qualifier, Bytes.toBytes(",Test"));
379      table.append(append);
380    }
381
382    metricsRegionServer.getRegionServerWrapper().forceRecompute();
383    assertCounter("appendNumOps", 73);
384  }
385
386  @Test
387  public void testScanSize() throws Exception {
388    doNPuts(100, true); // batch put
389    Scan s = new Scan();
390    s.setBatch(1).setCaching(1).setLimit(NUM_SCAN_NEXT).setReadType(ReadType.STREAM);
391    try (ResultScanner resultScanners = table.getScanner(s)) {
392      for (int nextCount = 0; nextCount < NUM_SCAN_NEXT; nextCount++) {
393        Result result = resultScanners.next();
394        assertNotNull(result);
395        assertEquals(1, result.size());
396      }
397      numScanNext += NUM_SCAN_NEXT;
398      assertRegionMetrics("scanCount", NUM_SCAN_NEXT);
399    }
400  }
401
402  @Test
403  public void testScanTime() throws Exception {
404    doNPuts(100, true);
405    Scan s = new Scan();
406    s.setBatch(1).setCaching(1).setLimit(NUM_SCAN_NEXT);
407    try (ResultScanner resultScanners = table.getScanner(s)) {
408      for (int nextCount = 0; nextCount < NUM_SCAN_NEXT; nextCount++) {
409        Result result = resultScanners.next();
410        assertNotNull(result);
411        assertEquals(1, result.size());
412      }
413    }
414    numScanNext += NUM_SCAN_NEXT;
415    assertRegionMetrics("scanCount", NUM_SCAN_NEXT);
416  }
417
418  @Test
419  public void testScanSizeForSmallScan() throws Exception {
420    doNPuts(100, true);
421    Scan s = new Scan();
422    s.setCaching(1).setLimit(NUM_SCAN_NEXT).setReadType(ReadType.PREAD);
423    try (ResultScanner resultScanners = table.getScanner(s)) {
424      for (int nextCount = 0; nextCount < NUM_SCAN_NEXT; nextCount++) {
425        Result result = resultScanners.next();
426        assertNotNull(result);
427      }
428      assertNull(resultScanners.next());
429    }
430    numScanNext += NUM_SCAN_NEXT;
431    assertRegionMetrics("scanCount", NUM_SCAN_NEXT);
432  }
433
434  @Test
435  public void testMobMetrics() throws IOException, InterruptedException {
436    TableName tableName = TableName.valueOf("testMobMetricsLocal");
437    int numHfiles = 5;
438    TableDescriptor htd = TableDescriptorBuilder.newBuilder(tableName)
439      .setColumnFamily(
440        ColumnFamilyDescriptorBuilder.newBuilder(cf).setMobEnabled(true).setMobThreshold(0).build())
441      .build();
442    byte[] val = Bytes.toBytes("mobdata");
443    try {
444      Table table = TEST_UTIL.createTable(htd, new byte[0][0], conf);
445      HRegion region = rs.getRegions(tableName).get(0);
446      for (int insertCount = 0; insertCount < numHfiles; insertCount++) {
447        Put p = new Put(Bytes.toBytes(insertCount));
448        p.addColumn(cf, qualifier, val);
449        table.put(p);
450        admin.flush(tableName);
451      }
452      metricsRegionServer.getRegionServerWrapper().forceRecompute();
453      assertCounter("mobFlushCount", numHfiles);
454      Scan scan = new Scan().withStartRow(Bytes.toBytes(0)).withStopRow(Bytes.toBytes(numHfiles));
455      ResultScanner scanner = table.getScanner(scan);
456      scanner.next(100);
457      numScanNext++; // this is an ugly construct
458      scanner.close();
459      metricsRegionServer.getRegionServerWrapper().forceRecompute();
460      assertCounter("mobScanCellsCount", numHfiles);
461
462      setMobThreshold(region, cf, 100);
463      // metrics are reset by the region initialization
464      region.initialize();
465      // This is how we MOB compact region
466      List<HStore> stores = region.getStores();
467      for (HStore store : stores) {
468        // Force major compaction
469        store.triggerMajorCompaction();
470        Optional<CompactionContext> context = store.requestCompaction(HStore.PRIORITY_USER,
471          CompactionLifeCycleTracker.DUMMY, User.getCurrent());
472        if (!context.isPresent()) {
473          continue;
474        }
475        region.compact(context.get(), store, NoLimitThroughputController.INSTANCE,
476          User.getCurrent());
477      }
478      metricsRegionServer.getRegionServerWrapper().forceRecompute();
479      assertCounter("cellsCountCompactedFromMob", numHfiles);
480      assertCounter("cellsCountCompactedToMob", 0);
481
482      scanner = table.getScanner(scan);
483      scanner.next(100);
484      numScanNext++; // this is an ugly construct
485      metricsRegionServer.getRegionServerWrapper().forceRecompute();
486      assertCounter("mobScanCellsCount", 0);
487
488      for (int insertCount = numHfiles; insertCount < 2 * numHfiles; insertCount++) {
489        Put p = new Put(Bytes.toBytes(insertCount));
490        p.addColumn(cf, qualifier, val);
491        table.put(p);
492        admin.flush(tableName);
493      }
494      setMobThreshold(region, cf, 0);
495
496      // closing the region forces the compaction.discharger to archive the compacted hfiles
497      region.close();
498
499      // metrics are reset by the region initialization
500      region.initialize();
501      region.compact(true);
502      metricsRegionServer.getRegionServerWrapper().forceRecompute();
503      // metrics are reset by the region initialization
504      assertCounter("cellsCountCompactedFromMob", 0);
505      assertCounter("cellsCountCompactedToMob", 2 * numHfiles);
506    } finally {
507      admin.disableTable(tableName);
508      admin.deleteTable(tableName);
509    }
510  }
511
512  private static Region setMobThreshold(Region region, byte[] cfName, long modThreshold) {
513    ColumnFamilyDescriptor cfd =
514      ColumnFamilyDescriptorBuilder.newBuilder(region.getTableDescriptor().getColumnFamily(cfName))
515        .setMobThreshold(modThreshold).build();
516    TableDescriptor td = TableDescriptorBuilder.newBuilder(region.getTableDescriptor())
517      .removeColumnFamily(cfName).setColumnFamily(cfd).build();
518    ((HRegion) region).setTableDescriptor(td);
519    return region;
520  }
521
522  @Test
523  @Ignore
524  public void testRangeCountMetrics() throws Exception {
525    final long[] timeranges =
526      { 1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000, 60000, 120000, 300000, 600000 };
527    final String timeRangeType = "TimeRangeCount";
528    final String timeRangeMetricName = "Mutate";
529    boolean timeRangeCountUpdated = false;
530
531    // Do a first put to be sure that the connection is established, meta is there and so on.
532    Put p = new Put(row);
533    p.addColumn(cf, qualifier, val);
534    table.put(p);
535
536    // do some puts and gets
537    for (int i = 0; i < 10; i++) {
538      table.put(p);
539    }
540
541    Get g = new Get(row);
542    for (int i = 0; i < 10; i++) {
543      table.get(g);
544    }
545
546    metricsRegionServer.getRegionServerWrapper().forceRecompute();
547
548    // Check some time range counters were updated
549    long prior = 0;
550
551    String dynamicMetricName;
552    for (int i = 0; i < timeranges.length; i++) {
553      dynamicMetricName =
554        timeRangeMetricName + "_" + timeRangeType + "_" + prior + "-" + timeranges[i];
555      if (metricsHelper.checkCounterExists(dynamicMetricName, serverSource)) {
556        long count = metricsHelper.getGaugeLong(dynamicMetricName, serverSource);
557        if (count > 0) {
558          timeRangeCountUpdated = true;
559          break;
560        }
561      }
562      prior = timeranges[i];
563    }
564    dynamicMetricName =
565      timeRangeMetricName + "_" + timeRangeType + "_" + timeranges[timeranges.length - 1] + "-inf";
566    if (metricsHelper.checkCounterExists(dynamicMetricName, serverSource)) {
567      long count = metricsHelper.getCounter(dynamicMetricName, serverSource);
568      if (count > 0) {
569        timeRangeCountUpdated = true;
570      }
571    }
572    assertEquals(true, timeRangeCountUpdated);
573  }
574
575  @Test
576  public void testAverageRegionSize() throws Exception {
577    // Force a hfile.
578    doNPuts(1, false);
579    TEST_UTIL.getAdmin().flush(tableName);
580
581    metricsRegionServer.getRegionServerWrapper().forceRecompute();
582    assertTrue(metricsHelper.getGaugeDouble("averageRegionSize", serverSource) > 0.0);
583  }
584
585  @Test
586  public void testReadBytes() throws Exception {
587    // Do a first put to be sure that the connection is established, meta is there and so on.
588    doNPuts(1, false);
589    doNGets(10, false);
590    TEST_UTIL.getAdmin().flush(tableName);
591    metricsRegionServer.getRegionServerWrapper().forceRecompute();
592
593    assertTrue("Total read bytes should be larger than 0",
594      metricsRegionServer.getRegionServerWrapper().getTotalBytesRead() > 0);
595    assertTrue("Total local read bytes should be larger than 0",
596      metricsRegionServer.getRegionServerWrapper().getLocalBytesRead() > 0);
597    assertEquals("Total short circuit read bytes should be equal to 0", 0,
598      metricsRegionServer.getRegionServerWrapper().getShortCircuitBytesRead());
599    assertEquals("Total zero-byte read bytes should be equal to 0", 0,
600      metricsRegionServer.getRegionServerWrapper().getZeroCopyBytesRead());
601  }
602
603  @Test
604  public void testTableDescriptorHashMetric() throws Exception {
605    doNPuts(1, false);
606    metricsRegionServer.getRegionServerWrapper().forceRecompute();
607
608    HRegion region = rs.getRegions(tableName).get(0);
609    assertNotNull("Region should exist", region);
610
611    try (MetricsRegionWrapperImpl wrapper = new MetricsRegionWrapperImpl(region)) {
612      String hash = wrapper.getTableDescriptorHash();
613
614      assertNotNull("TableDescriptorHash should not be null", hash);
615      assertNotEquals("TableDescriptorHash should not be 'UNKNOWN'", "UNKNOWN", hash);
616      assertEquals("Hash should be 8 characters (CRC32 hex)", 8, hash.length());
617    }
618  }
619}