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