This is an automated email from the ASF dual-hosted git repository.

cconnell pushed a commit to branch branch-3
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-3 by this push:
     new 43d7a1a9660 HBASE-29469 Add metrics with more detail for 
RpcThrottlingExceptions (#7186)
43d7a1a9660 is described below

commit 43d7a1a9660565b35c2c7ddb76401a4ccf9f57c1
Author: Siddharth Khillon <[email protected]>
AuthorDate: Mon Aug 11 08:47:41 2025 -0700

    HBASE-29469 Add metrics with more detail for RpcThrottlingExceptions (#7186)
    
    
    ---------
    
    Co-authored-by: skhillon <[email protected]>
    Signed-off by: cconnell <[email protected]>
    Reviewed by: kgeisz <[email protected]>
---
 .../hbase/quotas/RegionServerRpcQuotaManager.java  |   8 +
 .../hbase/regionserver/MetricsRegionServer.java    |  16 ++
 .../metrics/MetricsThrottleExceptions.java         |  80 ++++++
 .../regionserver/TestMetricsRegionServer.java      |  43 +++
 .../metrics/TestMetricsThrottleExceptions.java     | 294 +++++++++++++++++++++
 5 files changed, 441 insertions(+)

diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RegionServerRpcQuotaManager.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RegionServerRpcQuotaManager.java
index 03fbfde47a1..958793dcdf0 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RegionServerRpcQuotaManager.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RegionServerRpcQuotaManager.java
@@ -196,6 +196,10 @@ public class RegionServerRpcQuotaManager implements 
RpcQuotaManager, Configurati
     } catch (RpcThrottlingException e) {
       LOG.debug("Throttling exception for user=" + ugi.getUserName() + " 
table=" + table + " scan="
         + scanRequest.getScannerId() + ": " + e.getMessage());
+
+      rsServices.getMetrics().recordThrottleException(e.getType(), 
ugi.getShortUserName(),
+        table.getNameAsString());
+
       throw e;
     }
     return quota;
@@ -269,6 +273,10 @@ public class RegionServerRpcQuotaManager implements 
RpcQuotaManager, Configurati
     } catch (RpcThrottlingException e) {
       LOG.debug("Throttling exception for user=" + ugi.getUserName() + " 
table=" + table
         + " numWrites=" + numWrites + " numReads=" + numReads + ": " + 
e.getMessage());
+
+      rsServices.getMetrics().recordThrottleException(e.getType(), 
ugi.getShortUserName(),
+        table.getNameAsString());
+
       throw e;
     }
     return quota;
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsRegionServer.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsRegionServer.java
index 8afb3f31e39..4f5108769d6 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsRegionServer.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsRegionServer.java
@@ -23,6 +23,8 @@ import org.apache.hadoop.hbase.metrics.Meter;
 import org.apache.hadoop.hbase.metrics.MetricRegistries;
 import org.apache.hadoop.hbase.metrics.MetricRegistry;
 import org.apache.hadoop.hbase.metrics.Timer;
+import org.apache.hadoop.hbase.quotas.RpcThrottlingException;
+import org.apache.hadoop.hbase.regionserver.metrics.MetricsThrottleExceptions;
 import org.apache.yetus.audience.InterfaceAudience;
 import org.apache.yetus.audience.InterfaceStability;
 
@@ -46,6 +48,7 @@ public class MetricsRegionServer {
   private final MetricsUserAggregate userAggregate;
 
   private MetricRegistry metricRegistry;
+  private MetricsThrottleExceptions throttleMetrics;
   private Timer bulkLoadTimer;
   // Incremented once for each call to Scan#nextRaw
   private Meter serverReadQueryMeter;
@@ -77,6 +80,8 @@ public class MetricsRegionServer {
       serverReadQueryMeter = metricRegistry.meter("ServerReadQueryPerSecond");
       serverWriteQueryMeter = 
metricRegistry.meter("ServerWriteQueryPerSecond");
     }
+
+    throttleMetrics = new MetricsThrottleExceptions(metricRegistry);
   }
 
   MetricsRegionServer(MetricsRegionServerWrapper regionServerWrapper,
@@ -295,4 +300,15 @@ public class MetricsRegionServer {
     serverSource.incrScannerLeaseExpired();
   }
 
+  /**
+   * Record a throttle exception with contextual information.
+   * @param throttleType the type of throttle exception from 
RpcThrottlingException.Type enum
+   * @param user         the user who triggered the throttle
+   * @param table        the table that was being accessed
+   */
+  public void recordThrottleException(RpcThrottlingException.Type 
throttleType, String user,
+    String table) {
+    throttleMetrics.recordThrottleException(throttleType, user, table);
+  }
+
 }
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/metrics/MetricsThrottleExceptions.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/metrics/MetricsThrottleExceptions.java
new file mode 100644
index 00000000000..ddf451ab2e2
--- /dev/null
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/metrics/MetricsThrottleExceptions.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.hadoop.hbase.regionserver.metrics;
+
+import org.apache.hadoop.hbase.metrics.MetricRegistry;
+import org.apache.hadoop.hbase.quotas.RpcThrottlingException;
+import org.apache.yetus.audience.InterfaceAudience;
+
[email protected]
+public class MetricsThrottleExceptions {
+
+  /**
+   * The name of the metrics
+   */
+  private static final String METRICS_NAME = "ThrottleExceptions";
+
+  /**
+   * The name of the metrics context that metrics will be under.
+   */
+  private static final String METRICS_CONTEXT = "regionserver";
+
+  /**
+   * Description
+   */
+  private static final String METRICS_DESCRIPTION = "Metrics about RPC 
throttling exceptions";
+
+  /**
+   * The name of the metrics context that metrics will be under in jmx
+   */
+  private static final String METRICS_JMX_CONTEXT = "RegionServer,sub=" + 
METRICS_NAME;
+
+  private final MetricRegistry registry;
+
+  public MetricsThrottleExceptions(MetricRegistry sharedRegistry) {
+    registry = sharedRegistry;
+  }
+
+  /**
+   * Record a throttle exception with contextual information.
+   * @param throttleType the type of throttle exception
+   * @param user         the user who triggered the throttle
+   * @param table        the table that was being accessed
+   */
+  public void recordThrottleException(RpcThrottlingException.Type 
throttleType, String user,
+    String table) {
+    String metricName = qualifyThrottleMetric(throttleType, user, table);
+    registry.counter(metricName).increment();
+  }
+
+  private static String qualifyThrottleMetric(RpcThrottlingException.Type 
throttleType, String user,
+    String table) {
+    return String.format("RpcThrottlingException_Type_%s_User_%s_Table_%s", 
throttleType.name(),
+      sanitizeMetricName(user), sanitizeMetricName(table));
+  }
+
+  private static String sanitizeMetricName(String name) {
+    if (name == null) {
+      return "unknown";
+    }
+    // Only replace characters that are problematic for JMX ObjectNames
+    // Keep meaningful characters like hyphens, periods, etc.
+    return name.replaceAll("[,=:*?\"\\n]", "_");
+  }
+
+}
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestMetricsRegionServer.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestMetricsRegionServer.java
index e1cc080f753..aac2a5922b9 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestMetricsRegionServer.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestMetricsRegionServer.java
@@ -26,11 +26,14 @@ import static org.mockito.Mockito.when;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.CompatibilityFactory;
 import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.metrics.MetricRegistries;
+import org.apache.hadoop.hbase.quotas.RpcThrottlingException;
 import org.apache.hadoop.hbase.regionserver.metrics.MetricsTableRequests;
 import org.apache.hadoop.hbase.test.MetricsAssertHelper;
 import org.apache.hadoop.hbase.testclassification.RegionServerTests;
 import org.apache.hadoop.hbase.testclassification.SmallTests;
 import org.apache.hadoop.hbase.util.JvmPauseMonitor;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
@@ -66,6 +69,12 @@ public class TestMetricsRegionServer {
     serverSource = rsm.getMetricsSource();
   }
 
+  @After
+  public void tearDown() {
+    // Clean up global registries after each test to avoid interference
+    MetricRegistries.global().clear();
+  }
+
   @Test
   public void testWrapperSource() {
     HELPER.assertTag("serverName", "test", serverSource);
@@ -313,4 +322,38 @@ public class TestMetricsRegionServer {
     rsm.updateWriteQueryMeter(region, 500L);
     HELPER.assertGauge("ServerWriteQueryPerSecond_count", 500L, serverSource);
   }
+
+  @Test
+  public void testThrottleExceptionMetricsIntegration() {
+    // Record different types of throttle exceptions
+    
rsm.recordThrottleException(RpcThrottlingException.Type.NumRequestsExceeded, 
"alice", "users");
+    rsm.recordThrottleException(RpcThrottlingException.Type.WriteSizeExceeded, 
"bob", "logs");
+    rsm.recordThrottleException(RpcThrottlingException.Type.ReadSizeExceeded, 
"charlie",
+      "metadata");
+
+    // Record the same exception multiple times to test increment
+    
rsm.recordThrottleException(RpcThrottlingException.Type.NumRequestsExceeded, 
"alice", "users");
+    
rsm.recordThrottleException(RpcThrottlingException.Type.NumRequestsExceeded, 
"alice", "users");
+
+    // Verify the specific counters were created and have correct values using 
HELPER
+    
HELPER.assertCounter("RpcThrottlingException_Type_NumRequestsExceeded_User_alice_Table_users",
+      3L, serverSource);
+    
HELPER.assertCounter("RpcThrottlingException_Type_WriteSizeExceeded_User_bob_Table_logs",
 1L,
+      serverSource);
+    
HELPER.assertCounter("RpcThrottlingException_Type_ReadSizeExceeded_User_charlie_Table_metadata",
+      1L, serverSource);
+
+    // Test metric name sanitization through the integration
+    
rsm.recordThrottleException(RpcThrottlingException.Type.RequestSizeExceeded,
+      "user.with@special", "table:with,problematic=chars");
+    HELPER.assertCounter(
+      
"RpcThrottlingException_Type_RequestSizeExceeded_User_user.with@special_Table_table_with_problematic_chars",
+      1L, serverSource);
+
+    // Test null handling through the integration
+    
rsm.recordThrottleException(RpcThrottlingException.Type.ReadCapacityUnitExceeded,
 null, null);
+    HELPER.assertCounter(
+      
"RpcThrottlingException_Type_ReadCapacityUnitExceeded_User_unknown_Table_unknown",
 1L,
+      serverSource);
+  }
 }
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/metrics/TestMetricsThrottleExceptions.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/metrics/TestMetricsThrottleExceptions.java
new file mode 100644
index 00000000000..0fa02c42a32
--- /dev/null
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/metrics/TestMetricsThrottleExceptions.java
@@ -0,0 +1,294 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.hadoop.hbase.regionserver.metrics;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.metrics.Counter;
+import org.apache.hadoop.hbase.metrics.Metric;
+import org.apache.hadoop.hbase.metrics.MetricRegistries;
+import org.apache.hadoop.hbase.metrics.MetricRegistry;
+import org.apache.hadoop.hbase.metrics.MetricRegistryInfo;
+import org.apache.hadoop.hbase.quotas.RpcThrottlingException;
+import org.apache.hadoop.hbase.testclassification.RegionServerTests;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.junit.After;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category({ RegionServerTests.class, SmallTests.class })
+public class TestMetricsThrottleExceptions {
+
+  @ClassRule
+  public static final HBaseClassTestRule CLASS_RULE =
+    HBaseClassTestRule.forClass(TestMetricsThrottleExceptions.class);
+
+  private MetricRegistry testRegistry;
+  private MetricsThrottleExceptions throttleMetrics;
+
+  @After
+  public void cleanup() {
+    // Clean up global registries after each test to avoid interference
+    MetricRegistries.global().clear();
+  }
+
+  @Test
+  public void testBasicThrottleMetricsRecording() {
+    setupTestMetrics();
+
+    // Record a throttle exception
+    
throttleMetrics.recordThrottleException(RpcThrottlingException.Type.NumRequestsExceeded,
+      "alice", "users");
+
+    // Verify the counter exists and has correct value
+    Optional<Metric> metric =
+      
testRegistry.get("RpcThrottlingException_Type_NumRequestsExceeded_User_alice_Table_users");
+    assertTrue("Counter metric should be present", metric.isPresent());
+    assertTrue("Metric should be a counter", metric.get() instanceof Counter);
+
+    Counter counter = (Counter) metric.get();
+    assertEquals("Counter should have count of 1", 1, counter.getCount());
+  }
+
+  @Test
+  public void testMultipleThrottleTypes() {
+    setupTestMetrics();
+
+    // Record different types of throttle exceptions
+    
throttleMetrics.recordThrottleException(RpcThrottlingException.Type.NumRequestsExceeded,
+      "alice", "users");
+    
throttleMetrics.recordThrottleException(RpcThrottlingException.Type.WriteSizeExceeded,
 "bob",
+      "logs");
+    
throttleMetrics.recordThrottleException(RpcThrottlingException.Type.ReadSizeExceeded,
 "charlie",
+      "metadata");
+
+    // Verify all three counters were created
+    verifyCounter(testRegistry,
+      
"RpcThrottlingException_Type_NumRequestsExceeded_User_alice_Table_users", 1);
+    verifyCounter(testRegistry, 
"RpcThrottlingException_Type_WriteSizeExceeded_User_bob_Table_logs",
+      1);
+    verifyCounter(testRegistry,
+      
"RpcThrottlingException_Type_ReadSizeExceeded_User_charlie_Table_metadata", 1);
+  }
+
+  @Test
+  public void testCounterIncrement() {
+    setupTestMetrics();
+
+    // Record the same throttle exception multiple times
+    String metricName = 
"RpcThrottlingException_Type_NumRequestsExceeded_User_alice_Table_users";
+    
throttleMetrics.recordThrottleException(RpcThrottlingException.Type.NumRequestsExceeded,
+      "alice", "users");
+    
throttleMetrics.recordThrottleException(RpcThrottlingException.Type.NumRequestsExceeded,
+      "alice", "users");
+    
throttleMetrics.recordThrottleException(RpcThrottlingException.Type.NumRequestsExceeded,
+      "alice", "users");
+
+    // Verify the counter incremented correctly
+    verifyCounter(testRegistry, metricName, 3);
+  }
+
+  @Test
+  public void testMetricNameSanitization() {
+    setupTestMetrics();
+
+    // Test that meaningful characters are preserved (hyphens, periods, etc.)
+    
throttleMetrics.recordThrottleException(RpcThrottlingException.Type.WriteSizeExceeded,
+      "user.name@company", "my-table-prod");
+
+    // Verify meaningful characters are preserved, only JMX-problematic chars 
are replaced
+    String expectedMetricName =
+      
"RpcThrottlingException_Type_WriteSizeExceeded_User_user.name@company_Table_my-table-prod";
+    verifyCounter(testRegistry, expectedMetricName, 1);
+
+    // Test that JMX-problematic characters are sanitized
+    
throttleMetrics.recordThrottleException(RpcThrottlingException.Type.ReadSizeExceeded,
+      "user,with=bad:chars*", "table?with\"quotes");
+    String problematicMetricName =
+      
"RpcThrottlingException_Type_ReadSizeExceeded_User_user_with_bad_chars__Table_table_with_quotes";
+    verifyCounter(testRegistry, problematicMetricName, 1);
+  }
+
+  @Test
+  public void testNullHandling() {
+    setupTestMetrics();
+
+    // Test null user and table names
+    
throttleMetrics.recordThrottleException(RpcThrottlingException.Type.NumRequestsExceeded,
 null,
+      null);
+    
throttleMetrics.recordThrottleException(RpcThrottlingException.Type.WriteSizeExceeded,
 "alice",
+      null);
+    
throttleMetrics.recordThrottleException(RpcThrottlingException.Type.ReadSizeExceeded,
 null,
+      "users");
+
+    // Verify null values are replaced with "unknown"
+    verifyCounter(testRegistry,
+      
"RpcThrottlingException_Type_NumRequestsExceeded_User_unknown_Table_unknown", 
1);
+    verifyCounter(testRegistry,
+      
"RpcThrottlingException_Type_WriteSizeExceeded_User_alice_Table_unknown", 1);
+    verifyCounter(testRegistry,
+      "RpcThrottlingException_Type_ReadSizeExceeded_User_unknown_Table_users", 
1);
+  }
+
+  @Test
+  public void testConcurrentAccess() throws InterruptedException {
+    setupTestMetrics();
+
+    int numThreads = 10;
+    int incrementsPerThread = 100;
+
+    ExecutorService executor = Executors.newFixedThreadPool(numThreads);
+    CountDownLatch startLatch = new CountDownLatch(1);
+    CountDownLatch doneLatch = new CountDownLatch(numThreads);
+    AtomicInteger exceptions = new AtomicInteger(0);
+
+    // Create multiple threads that increment the same counter concurrently
+    for (int i = 0; i < numThreads; i++) {
+      executor.submit(() -> {
+        try {
+          startLatch.await();
+          for (int j = 0; j < incrementsPerThread; j++) {
+            
throttleMetrics.recordThrottleException(RpcThrottlingException.Type.NumRequestsExceeded,
+              "alice", "users");
+          }
+        } catch (Exception e) {
+          exceptions.incrementAndGet();
+        } finally {
+          doneLatch.countDown();
+        }
+      });
+    }
+
+    // Start all threads at once
+    startLatch.countDown();
+
+    // Wait for all threads to complete
+    boolean completed = doneLatch.await(30, TimeUnit.SECONDS);
+    assertTrue("All threads should complete within timeout", completed);
+    assertEquals("No exceptions should occur during concurrent access", 0, 
exceptions.get());
+
+    // Verify the final counter value
+    verifyCounter(testRegistry,
+      "RpcThrottlingException_Type_NumRequestsExceeded_User_alice_Table_users",
+      numThreads * incrementsPerThread);
+
+    executor.shutdown();
+  }
+
+  @Test
+  public void testCommonTableNamePatterns() {
+    setupTestMetrics();
+
+    // Test common HBase table name patterns that should be preserved
+    
throttleMetrics.recordThrottleException(RpcThrottlingException.Type.NumRequestsExceeded,
+      "service-user", "my-app-logs");
+    
throttleMetrics.recordThrottleException(RpcThrottlingException.Type.WriteSizeExceeded,
+      "batch.process", "namespace:table-name");
+    
throttleMetrics.recordThrottleException(RpcThrottlingException.Type.ReadSizeExceeded,
+      "user_123", "test_table_v2");
+
+    // Verify common patterns are preserved correctly (note: colon gets 
replaced with underscore)
+    verifyCounter(testRegistry,
+      
"RpcThrottlingException_Type_NumRequestsExceeded_User_service-user_Table_my-app-logs",
 1);
+    verifyCounter(testRegistry,
+      
"RpcThrottlingException_Type_WriteSizeExceeded_User_batch.process_Table_namespace_table-name",
+      1);
+    verifyCounter(testRegistry,
+      
"RpcThrottlingException_Type_ReadSizeExceeded_User_user_123_Table_test_table_v2",
 1);
+  }
+
+  @Test
+  public void testAllThrottleExceptionTypes() {
+    setupTestMetrics();
+
+    // Test all 13 throttle exception types from RpcThrottlingException.Type 
enum
+    RpcThrottlingException.Type[] throttleTypes = 
RpcThrottlingException.Type.values();
+
+    // Record one exception for each type
+    for (RpcThrottlingException.Type throttleType : throttleTypes) {
+      throttleMetrics.recordThrottleException(throttleType, "testuser", 
"testtable");
+    }
+
+    // Verify all counters were created with correct values
+    for (RpcThrottlingException.Type throttleType : throttleTypes) {
+      String expectedMetricName =
+        "RpcThrottlingException_Type_" + throttleType.name() + 
"_User_testuser_Table_testtable";
+      verifyCounter(testRegistry, expectedMetricName, 1);
+    }
+  }
+
+  @Test
+  public void testMultipleInstances() {
+    setupTestMetrics();
+
+    // Test that multiple instances of MetricsThrottleExceptions work with the 
same registry
+    MetricsThrottleExceptions metrics1 = new 
MetricsThrottleExceptions(testRegistry);
+    MetricsThrottleExceptions metrics2 = new 
MetricsThrottleExceptions(testRegistry);
+
+    // Record different exceptions on each instance
+    
metrics1.recordThrottleException(RpcThrottlingException.Type.NumRequestsExceeded,
 "alice",
+      "table1");
+    
metrics2.recordThrottleException(RpcThrottlingException.Type.WriteSizeExceeded, 
"bob",
+      "table2");
+
+    // Verify both counters exist in the shared registry
+    verifyCounter(testRegistry,
+      
"RpcThrottlingException_Type_NumRequestsExceeded_User_alice_Table_table1", 1);
+    verifyCounter(testRegistry,
+      "RpcThrottlingException_Type_WriteSizeExceeded_User_bob_Table_table2", 
1);
+  }
+
+  /**
+   * Helper method to set up test metrics registry and instance
+   */
+  private void setupTestMetrics() {
+    MetricRegistryInfo registryInfo = getRegistryInfo();
+    testRegistry = MetricRegistries.global().create(registryInfo);
+    throttleMetrics = new MetricsThrottleExceptions(testRegistry);
+  }
+
+  /**
+   * Helper method to verify a counter exists and has the expected value
+   */
+  private void verifyCounter(MetricRegistry registry, String metricName, long 
expectedCount) {
+    Optional<Metric> metric = registry.get(metricName);
+    assertTrue("Counter metric '" + metricName + "' should be present", 
metric.isPresent());
+    assertTrue("Metric should be a counter", metric.get() instanceof Counter);
+
+    Counter counter = (Counter) metric.get();
+    assertEquals("Counter '" + metricName + "' should have expected count", 
expectedCount,
+      counter.getCount());
+  }
+
+  /**
+   * Helper method to create the expected MetricRegistryInfo for 
ThrottleExceptions
+   */
+  private MetricRegistryInfo getRegistryInfo() {
+    return new MetricRegistryInfo("ThrottleExceptions", "Metrics about RPC 
throttling exceptions",
+      "RegionServer,sub=ThrottleExceptions", "regionserver", false);
+  }
+}

Reply via email to