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

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


The following commit(s) were added to refs/heads/branch-2 by this push:
     new 3cffe5d4528 HBASE-29289: Make hbase master ui show cluster client 
connections info (#8058)
3cffe5d4528 is described below

commit 3cffe5d452855f2d4b06cdd3265d8a781ce7ac2b
Author: Dev Hingu <[email protected]>
AuthorDate: Mon Jun 22 16:04:44 2026 +0530

    HBASE-29289: Make hbase master ui show cluster client connections info 
(#8058)
    
    Signed-off-by: Dávid Paksy <[email protected]>
    Signed-off-by: Wellington Chevreuil <[email protected]>
---
 .../java/org/apache/hadoop/hbase/UserMetrics.java  |  8 +++
 .../apache/hadoop/hbase/UserMetricsBuilder.java    | 40 +++++++++++-
 .../hadoop/hbase/TestUserMetricsBuilder.java       | 54 ++++++++++++++++
 .../hbase/regionserver/MetricsUserSource.java      | 11 +++-
 .../hbase/regionserver/MetricsUserSourceImpl.java  | 36 ++++++++++-
 .../org/apache/hadoop/hbase/hbtop/field/Field.java |  6 +-
 .../hbase/hbtop/mode/ClientModeStrategy.java       |  8 ++-
 .../org/apache/hadoop/hbase/hbtop/TestUtils.java   | 63 ++++++++++++------
 .../src/main/protobuf/ClusterStatus.proto          | 12 ++++
 .../org/apache/hadoop/hbase/ipc/RpcServer.java     | 18 ++++++
 .../org/apache/hadoop/hbase/ipc/ServerCall.java    |  8 +++
 .../hadoop/hbase/ipc/ServerRpcConnection.java      |  6 +-
 .../apache/hadoop/hbase/ipc/SimpleServerCall.java  |  3 +-
 .../hadoop/hbase/regionserver/HRegionServer.java   |  6 +-
 .../regionserver/MetricsUserAggregateImpl.java     | 62 +++++++++++++++++-
 .../hbase-webapps/master/regionServerList.jsp      |  6 ++
 .../regionServerListClientConnectionsStats.jsp     | 74 ++++++++++++++++++++++
 .../hbase/master/http/TestMasterStatusPage.java    |  9 +++
 18 files changed, 395 insertions(+), 35 deletions(-)

diff --git 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/UserMetrics.java 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/UserMetrics.java
index 681b1f416c7..69095a898dc 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/UserMetrics.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/UserMetrics.java
@@ -38,6 +38,14 @@ public interface UserMetrics {
     long getWriteRequestsCount();
 
     long getFilteredReadRequestsCount();
+
+    String getHostAddress();
+
+    String getUserName();
+
+    String getClientVersion();
+
+    String getServiceName();
   }
 
   /** Returns the user name */
diff --git 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/UserMetricsBuilder.java 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/UserMetricsBuilder.java
index 4a66283146d..9d2b54c6f78 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/UserMetricsBuilder.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/UserMetricsBuilder.java
@@ -36,7 +36,9 @@ public final class UserMetricsBuilder {
     userLoad.getClientMetricsList().stream()
       .map(clientMetrics -> new ClientMetricsImpl(clientMetrics.getHostName(),
         clientMetrics.getReadRequestsCount(), 
clientMetrics.getWriteRequestsCount(),
-        clientMetrics.getFilteredRequestsCount()))
+        clientMetrics.getFilteredRequestsCount(), 
clientMetrics.getHostAddress(),
+        clientMetrics.getUserName(), clientMetrics.getServiceName(),
+        clientMetrics.getClientVersion()))
       .forEach(builder::addClientMetris);
     return builder.build();
   }
@@ -49,7 +51,10 @@ public final class UserMetricsBuilder {
         .setHostName(clientMetrics.getHostName())
         .setWriteRequestsCount(clientMetrics.getWriteRequestsCount())
         .setReadRequestsCount(clientMetrics.getReadRequestsCount())
-        
.setFilteredRequestsCount(clientMetrics.getFilteredReadRequestsCount()).build())
+        .setFilteredRequestsCount(clientMetrics.getFilteredReadRequestsCount())
+        
.setHostAddress(clientMetrics.getHostAddress()).setUserName(clientMetrics.getUserName())
+        .setServiceName(clientMetrics.getServiceName())
+        .setClientVersion(clientMetrics.getClientVersion()).build())
       .forEach(builder::addClientMetrics);
     return builder.build();
   }
@@ -79,13 +84,22 @@ public final class UserMetricsBuilder {
     private final String hostName;
     private final long readRequestCount;
     private final long writeRequestCount;
+    private final String hostAddress;
+    private final String userName;
+    private final String serviceName;
+    private final String clientVersion;
 
     public ClientMetricsImpl(String hostName, long readRequest, long 
writeRequest,
-      long filteredReadRequestsCount) {
+      long filteredReadRequestsCount, String hostAddress, String userName, 
String serviceName,
+      String clientVersion) {
       this.hostName = hostName;
       this.readRequestCount = readRequest;
       this.writeRequestCount = writeRequest;
       this.filteredReadRequestsCount = filteredReadRequestsCount;
+      this.hostAddress = hostAddress != null ? hostAddress : "Unknown";
+      this.userName = userName != null ? userName : "Unknown";
+      this.serviceName = serviceName != null ? serviceName : "Unknown";
+      this.clientVersion = clientVersion != null ? clientVersion : "Unknown";
     }
 
     @Override
@@ -107,6 +121,26 @@ public final class UserMetricsBuilder {
     public long getFilteredReadRequestsCount() {
       return filteredReadRequestsCount;
     }
+
+    @Override
+    public String getHostAddress() {
+      return hostAddress;
+    }
+
+    @Override
+    public String getUserName() {
+      return userName;
+    }
+
+    @Override
+    public String getServiceName() {
+      return serviceName;
+    }
+
+    @Override
+    public String getClientVersion() {
+      return clientVersion;
+    }
   }
 
   private static class UserMetricsImpl implements UserMetrics {
diff --git 
a/hbase-client/src/test/java/org/apache/hadoop/hbase/TestUserMetricsBuilder.java
 
b/hbase-client/src/test/java/org/apache/hadoop/hbase/TestUserMetricsBuilder.java
new file mode 100644
index 00000000000..eb56f9a7568
--- /dev/null
+++ 
b/hbase-client/src/test/java/org/apache/hadoop/hbase/TestUserMetricsBuilder.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.apache.hadoop.hbase.testclassification.ClientTests;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import org.apache.hadoop.hbase.shaded.protobuf.generated.ClusterStatusProtos;
+
+@Tag(ClientTests.TAG)
+@Tag(SmallTests.TAG)
+public class TestUserMetricsBuilder {
+
+  @Test
+  public void testRoundTripPreservesClientConnectionFields() {
+    // Build a user metrics snapshot with all client-connection identity 
fields populated.
+    UserMetrics userMetrics = 
UserMetricsBuilder.newBuilder(Bytes.toBytes("alice"))
+      .addClientMetris(new UserMetricsBuilder.ClientMetricsImpl("clientHost", 
11L, 22L, 3L,
+        "10.1.1.1", "alice", "ClientService", "1.2.3"))
+      .build();
+
+    // Convert to protobuf and back; UI/server paths rely on this round-trip 
preserving fields.
+    ClusterStatusProtos.UserLoad userLoad = 
UserMetricsBuilder.toUserMetrics(userMetrics);
+    UserMetrics converted = UserMetricsBuilder.toUserMetrics(userLoad);
+
+    UserMetrics.ClientMetrics clientMetrics = 
converted.getClientMetrics().get("clientHost");
+    assertNotNull(clientMetrics);
+    assertEquals("10.1.1.1", clientMetrics.getHostAddress());
+    assertEquals("alice", clientMetrics.getUserName());
+    assertEquals("ClientService", clientMetrics.getServiceName());
+    assertEquals("1.2.3", clientMetrics.getClientVersion());
+  }
+}
diff --git 
a/hbase-hadoop-compat/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsUserSource.java
 
b/hbase-hadoop-compat/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsUserSource.java
index feb173a94af..da8ee8c3972 100644
--- 
a/hbase-hadoop-compat/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsUserSource.java
+++ 
b/hbase-hadoop-compat/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsUserSource.java
@@ -39,6 +39,14 @@ public interface MetricsUserSource extends 
Comparable<MetricsUserSource> {
     void incrementFilteredReadRequests();
 
     long getFilteredReadRequests();
+
+    String getHostAddress();
+
+    String getUserName();
+
+    String getClientVersion();
+
+    String getServiceName();
   }
 
   String getUser();
@@ -77,5 +85,6 @@ public interface MetricsUserSource extends 
Comparable<MetricsUserSource> {
    * @param hostName hostname of the client
    * @return Instance of ClientMetrics
    */
-  ClientMetrics getOrCreateMetricsClient(String hostName);
+  ClientMetrics getOrCreateMetricsClient(String hostName, String hostAddress, 
String userName,
+    String clientVersion, String serviceName);
 }
diff --git 
a/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsUserSourceImpl.java
 
b/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsUserSourceImpl.java
index 65dfaafe522..41527c01f99 100644
--- 
a/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsUserSourceImpl.java
+++ 
b/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsUserSourceImpl.java
@@ -69,9 +69,18 @@ public class MetricsUserSourceImpl implements 
MetricsUserSource {
     final LongAdder readRequestsCount = new LongAdder();
     final LongAdder writeRequestsCount = new LongAdder();
     final LongAdder filteredRequestsCount = new LongAdder();
+    private final String hostAddress;
+    private final String userName;
+    private final String clientVersion;
+    private final String serviceName;
 
-    public ClientMetricsImpl(String hostName) {
+    public ClientMetricsImpl(String hostName, String hostAddress, String 
userName,
+      String clientVersion, String serviceName) {
       this.hostName = hostName;
+      this.hostAddress = hostAddress != null ? hostAddress : "Unknown";
+      this.userName = userName != null ? userName : "Unknown";
+      this.clientVersion = clientVersion != null ? clientVersion : "Unknown";
+      this.serviceName = serviceName != null ? serviceName : "Unknown";
     }
 
     @Override
@@ -109,6 +118,26 @@ public class MetricsUserSourceImpl implements 
MetricsUserSource {
     public long getFilteredReadRequests() {
       return filteredRequestsCount.sum();
     }
+
+    @Override
+    public String getHostAddress() {
+      return hostAddress;
+    }
+
+    @Override
+    public String getUserName() {
+      return userName;
+    }
+
+    @Override
+    public String getClientVersion() {
+      return clientVersion;
+    }
+
+    @Override
+    public String getServiceName() {
+      return serviceName;
+    }
   }
 
   public MetricsUserSourceImpl(String user, MetricsUserAggregateSourceImpl 
agg) {
@@ -278,12 +307,13 @@ public class MetricsUserSourceImpl implements 
MetricsUserSource {
   }
 
   @Override
-  public ClientMetrics getOrCreateMetricsClient(String client) {
+  public ClientMetrics getOrCreateMetricsClient(String client, String 
hostAddress, String userName,
+    String clientVersion, String serviceName) {
     ClientMetrics source = clientMetricsMap.get(client);
     if (source != null) {
       return source;
     }
-    source = new ClientMetricsImpl(client);
+    source = new ClientMetricsImpl(client, hostAddress, userName, 
clientVersion, serviceName);
     ClientMetrics prev = clientMetricsMap.putIfAbsent(client, source);
     if (prev != null) {
       return prev;
diff --git 
a/hbase-hbtop/src/main/java/org/apache/hadoop/hbase/hbtop/field/Field.java 
b/hbase-hbtop/src/main/java/org/apache/hadoop/hbase/hbtop/field/Field.java
index ab776cf0336..cf36da8dfc9 100644
--- a/hbase-hbtop/src/main/java/org/apache/hadoop/hbase/hbtop/field/Field.java
+++ b/hbase-hbtop/src/main/java/org/apache/hadoop/hbase/hbtop/field/Field.java
@@ -58,7 +58,11 @@ public enum Field {
   MAX_HEAP_SIZE("MHEAP", "Max Heap Size", false, false, FieldValueType.SIZE),
   CLIENT_COUNT("#CLIENT", "Client Count", false, false, 
FieldValueType.INTEGER),
   USER_COUNT("#USER", "User Count", false, false, FieldValueType.INTEGER),
-  CLIENT("CLIENT", "Client Hostname", true, true, FieldValueType.STRING);
+  CLIENT("CLIENT", "Client Hostname", true, true, FieldValueType.STRING),
+  HOST_ADDRESS("HADDR", "Client Host Address", true, true, 
FieldValueType.STRING),
+  CLIENT_VERSION("CVER", "Client Version", true, true, FieldValueType.STRING),
+  USER_NAME("USER", "User Name", true, true, FieldValueType.STRING),
+  SERVICE_NAME("SVC", "Service Name", true, true, FieldValueType.STRING);
 
   private final String header;
   private final String description;
diff --git 
a/hbase-hbtop/src/main/java/org/apache/hadoop/hbase/hbtop/mode/ClientModeStrategy.java
 
b/hbase-hbtop/src/main/java/org/apache/hadoop/hbase/hbtop/mode/ClientModeStrategy.java
index 8327b1425cf..075cede17f9 100644
--- 
a/hbase-hbtop/src/main/java/org/apache/hadoop/hbase/hbtop/mode/ClientModeStrategy.java
+++ 
b/hbase-hbtop/src/main/java/org/apache/hadoop/hbase/hbtop/mode/ClientModeStrategy.java
@@ -48,7 +48,9 @@ public final class ClientModeStrategy implements ModeStrategy 
{
       new FieldInfo(Field.REQUEST_COUNT_PER_SECOND, 10, true),
       new FieldInfo(Field.READ_REQUEST_COUNT_PER_SECOND, 10, true),
       new FieldInfo(Field.WRITE_REQUEST_COUNT_PER_SECOND, 10, true),
-      new FieldInfo(Field.FILTERED_READ_REQUEST_COUNT_PER_SECOND, 10, true));
+      new FieldInfo(Field.FILTERED_READ_REQUEST_COUNT_PER_SECOND, 10, true),
+      new FieldInfo(Field.HOST_ADDRESS, 0, true), new 
FieldInfo(Field.USER_NAME, 0, true),
+      new FieldInfo(Field.CLIENT_VERSION, 0, true), new 
FieldInfo(Field.SERVICE_NAME, 0, true));
   private final Map<String, RequestCountPerSecond> requestCountPerSecondMap = 
new HashMap<>();
 
   ClientModeStrategy() {
@@ -145,6 +147,10 @@ public final class ClientModeStrategy implements 
ModeStrategy {
       requestCountPerSecond.getWriteRequestCountPerSecond());
     builder.put(Field.FILTERED_READ_REQUEST_COUNT_PER_SECOND,
       requestCountPerSecond.getFilteredReadRequestCountPerSecond());
+    builder.put(Field.HOST_ADDRESS, clientMetrics.getHostAddress());
+    builder.put(Field.USER_NAME, clientMetrics.getUserName());
+    builder.put(Field.CLIENT_VERSION, clientMetrics.getClientVersion());
+    builder.put(Field.SERVICE_NAME, clientMetrics.getServiceName());
     builder.put(Field.USER, user);
     return builder.build();
   }
diff --git 
a/hbase-hbtop/src/test/java/org/apache/hadoop/hbase/hbtop/TestUtils.java 
b/hbase-hbtop/src/test/java/org/apache/hadoop/hbase/hbtop/TestUtils.java
index 6866337082e..d9f8ecab5e9 100644
--- a/hbase-hbtop/src/test/java/org/apache/hadoop/hbase/hbtop/TestUtils.java
+++ b/hbase-hbtop/src/test/java/org/apache/hadoop/hbase/hbtop/TestUtils.java
@@ -50,14 +50,23 @@ public final class TestUtils {
   private TestUtils() {
   }
 
+  private static final String TEST_USER_FOO = "FOO";
+  private static final String TEST_USER_BAR = "BAR";
+  private static final String TEST_HOST_ADDRESS_A = "10.0.0.1";
+  private static final String TEST_HOST_ADDRESS_B = "10.0.0.2";
+  private static final String TEST_SERVICE_NAME = "ClientService";
+  private static final String TEST_CLIENT_VERSION = "test-client-version";
+
   public static ClusterMetrics createDummyClusterMetrics() {
     Map<ServerName, ServerMetrics> serverMetricsMap = new HashMap<>();
 
     // host1
     List<RegionMetrics> regionMetricsList = new ArrayList<>();
     List<UserMetrics> userMetricsList = new ArrayList<>();
-    userMetricsList.add(createUserMetrics("FOO", 1, 2, 4));
-    userMetricsList.add(createUserMetrics("BAR", 2, 3, 3));
+    userMetricsList.add(createUserMetrics(TEST_USER_FOO, 1, 2, 4, 
TEST_HOST_ADDRESS_A,
+      TEST_HOST_ADDRESS_B, TEST_CLIENT_VERSION, TEST_SERVICE_NAME));
+    userMetricsList.add(createUserMetrics(TEST_USER_BAR, 2, 3, 3, 
TEST_HOST_ADDRESS_A,
+      TEST_HOST_ADDRESS_B, TEST_CLIENT_VERSION, TEST_SERVICE_NAME));
     
regionMetricsList.add(createRegionMetrics("table1,,1.00000000000000000000000000000000.",
 100,
       50, 100, new Size(100, Size.Unit.MEGABYTE), new Size(200, 
Size.Unit.MEGABYTE), 1,
       new Size(100, Size.Unit.MEGABYTE), 0.1f, 100, 100, "2019-07-22 
00:00:00"));
@@ -76,8 +85,10 @@ public final class TestUtils {
     // host2
     regionMetricsList.clear();
     userMetricsList.clear();
-    userMetricsList.add(createUserMetrics("FOO", 5, 7, 3));
-    userMetricsList.add(createUserMetrics("BAR", 4, 8, 4));
+    userMetricsList.add(createUserMetrics(TEST_USER_FOO, 5, 7, 3, 
TEST_HOST_ADDRESS_A,
+      TEST_HOST_ADDRESS_B, TEST_CLIENT_VERSION, TEST_SERVICE_NAME));
+    userMetricsList.add(createUserMetrics(TEST_USER_BAR, 4, 8, 4, 
TEST_HOST_ADDRESS_A,
+      TEST_HOST_ADDRESS_B, TEST_CLIENT_VERSION, TEST_SERVICE_NAME));
     
regionMetricsList.add(createRegionMetrics("table1,1,4.00000000000000000000000000000003.",
 100,
       50, 100, new Size(100, Size.Unit.MEGABYTE), new Size(200, 
Size.Unit.MEGABYTE), 1,
       new Size(100, Size.Unit.MEGABYTE), 0.4f, 50, 100, "2019-07-22 
00:00:03"));
@@ -105,12 +116,15 @@ public final class TestUtils {
   }
 
   private static UserMetrics createUserMetrics(String user, long 
readRequestCount,
-    long writeRequestCount, long filteredReadRequestsCount) {
+    long writeRequestCount, long filteredReadRequestsCount, String 
hostAddressA,
+    String hostAddressB, String clientVersion, String serviceName) {
     return UserMetricsBuilder.newBuilder(Bytes.toBytes(user))
       .addClientMetris(new UserMetricsBuilder.ClientMetricsImpl("CLIENT_A_" + 
user,
-        readRequestCount, writeRequestCount, filteredReadRequestsCount))
+        readRequestCount, writeRequestCount, filteredReadRequestsCount, 
hostAddressA, user,
+        serviceName, clientVersion))
       .addClientMetris(new UserMetricsBuilder.ClientMetricsImpl("CLIENT_B_" + 
user,
-        readRequestCount, writeRequestCount, filteredReadRequestsCount))
+        readRequestCount, writeRequestCount, filteredReadRequestsCount, 
hostAddressB, user,
+        serviceName, clientVersion))
       .build();
   }
 
@@ -312,10 +326,10 @@ public final class TestUtils {
       switch (user) {
         // readRequestPerSecond and writeRequestPerSecond will be zero
         // because there is no change or new metrics during refresh
-        case "FOO":
+        case TEST_USER_FOO:
           assertRecordInUserMode(record, 0L, 0L, 0L);
           break;
-        case "BAR":
+        case TEST_USER_BAR:
           assertRecordInUserMode(record, 0L, 0L, 0L);
           break;
         default:
@@ -331,17 +345,21 @@ public final class TestUtils {
       switch (client) {
         // readRequestPerSecond and writeRequestPerSecond will be zero
         // because there is no change or new metrics during refresh
-        case "CLIENT_A_FOO":
-          assertRecordInClientMode(record, 0L, 0L, 0L);
+        case "CLIENT_A_" + TEST_USER_FOO:
+          assertRecordInClientMode(record, 0L, 0L, 0L, TEST_HOST_ADDRESS_A, 
TEST_USER_FOO,
+            TEST_CLIENT_VERSION, TEST_SERVICE_NAME);
           break;
-        case "CLIENT_A_BAR":
-          assertRecordInClientMode(record, 0L, 0L, 0L);
+        case "CLIENT_A_" + TEST_USER_BAR:
+          assertRecordInClientMode(record, 0L, 0L, 0L, TEST_HOST_ADDRESS_A, 
TEST_USER_BAR,
+            TEST_CLIENT_VERSION, TEST_SERVICE_NAME);
           break;
-        case "CLIENT_B_FOO":
-          assertRecordInClientMode(record, 0L, 0L, 0L);
+        case "CLIENT_B_" + TEST_USER_FOO:
+          assertRecordInClientMode(record, 0L, 0L, 0L, TEST_HOST_ADDRESS_B, 
TEST_USER_FOO,
+            TEST_CLIENT_VERSION, TEST_SERVICE_NAME);
           break;
-        case "CLIENT_B_BAR":
-          assertRecordInClientMode(record, 0L, 0L, 0L);
+        case "CLIENT_B_" + TEST_USER_BAR:
+          assertRecordInClientMode(record, 0L, 0L, 0L, TEST_HOST_ADDRESS_B, 
TEST_USER_BAR,
+            TEST_CLIENT_VERSION, TEST_SERVICE_NAME);
           break;
         default:
           fail();
@@ -351,7 +369,7 @@ public final class TestUtils {
 
   private static void assertRecordInUserMode(Record record, long 
readRequestCountPerSecond,
     long writeCountRequestPerSecond, long filteredReadRequestsCount) {
-    assertThat(record.size(), is(6));
+    assertThat(record.size(), is(10));
     assertThat(record.get(Field.READ_REQUEST_COUNT_PER_SECOND).asLong(),
       is(readRequestCountPerSecond));
     assertThat(record.get(Field.WRITE_REQUEST_COUNT_PER_SECOND).asLong(),
@@ -362,14 +380,19 @@ public final class TestUtils {
   }
 
   private static void assertRecordInClientMode(Record record, long 
readRequestCountPerSecond,
-    long writeCountRequestPerSecond, long filteredReadRequestsCount) {
-    assertThat(record.size(), is(6));
+    long writeCountRequestPerSecond, long filteredReadRequestsCount, String 
hostAddress,
+    String userName, String clientVersion, String serviceName) {
+    assertThat(record.size(), is(10));
     assertThat(record.get(Field.READ_REQUEST_COUNT_PER_SECOND).asLong(),
       is(readRequestCountPerSecond));
     assertThat(record.get(Field.WRITE_REQUEST_COUNT_PER_SECOND).asLong(),
       is(writeCountRequestPerSecond));
     
assertThat(record.get(Field.FILTERED_READ_REQUEST_COUNT_PER_SECOND).asLong(),
       is(filteredReadRequestsCount));
+    assertThat(record.get(Field.HOST_ADDRESS).asString(), is(hostAddress));
+    assertThat(record.get(Field.USER_NAME).asString(), is(userName));
+    assertThat(record.get(Field.CLIENT_VERSION).asString(), is(clientVersion));
+    assertThat(record.get(Field.SERVICE_NAME).asString(), is(serviceName));
     assertThat(record.get(Field.USER_COUNT).asInt(), is(1));
   }
 
diff --git a/hbase-protocol-shaded/src/main/protobuf/ClusterStatus.proto 
b/hbase-protocol-shaded/src/main/protobuf/ClusterStatus.proto
index 154bd5c5d45..c54b092aa7a 100644
--- a/hbase-protocol-shaded/src/main/protobuf/ClusterStatus.proto
+++ b/hbase-protocol-shaded/src/main/protobuf/ClusterStatus.proto
@@ -212,6 +212,18 @@ message ClientMetrics {
 
   /** the current total filtered requests made from a client */
   optional uint64 filtered_requests_count = 4;
+
+  /** the IP address of the client */
+  optional string host_address = 5;
+
+  /** the name of the user making requests from this client */
+  optional string user_name = 6;
+
+  /** the version of the client */
+  optional string client_version = 7;
+
+  /** the protobuf RPC service name from the client connection header */
+  optional string service_name = 8;
 }
 
 /* Server-level protobufs */
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java
index c00717c69c8..ab41b503f71 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java
@@ -799,6 +799,24 @@ public abstract class RpcServer implements 
RpcServerInterface, ConfigurationObse
     return getCurrentCall().map(RpcCall::getRemoteAddress);
   }
 
+  /**
+   * Returns the ServerRpcConnection for the current RPC request or not 
present if no connection is
+   * available. This allows access to connection-level information such as the 
client's IP address,
+   * authentication details, codec configuration, etc.
+   * <p>
+   * This method should only be called from within an RPC handler thread 
context.
+   * @return the current ServerRpcConnection, or Optional.empty() if called 
outside of an RPC
+   *         context
+   */
+  public static Optional<ServerRpcConnection> getCurrentServerRpcConnection() {
+    Optional<RpcCall> call = getCurrentCall();
+    if (call.isPresent() && call.get() instanceof ServerCall) {
+      ServerCall<?> serverCall = (ServerCall<?>) call.get();
+      return Optional.ofNullable(serverCall.getConnection());
+    }
+    return Optional.empty();
+  }
+
   /**
    * @param serviceName Some arbitrary string that represents a 'service'.
    * @param services    Available service instances
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerCall.java 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerCall.java
index 8702980a10d..af4041d5380 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerCall.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerCall.java
@@ -256,6 +256,14 @@ public abstract class ServerCall<T extends 
ServerRpcConnection> implements RpcCa
     return this.header.getPriority();
   }
 
+  /**
+   * Get the ServerRpcConnection associated with this call.
+   * @return the connection object
+   */
+  public T getConnection() {
+    return this.connection;
+  }
+
   /*
    * Short string representation without param info because param itself could 
be huge depends on
    * the payload of a command
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java
index 104c5486951..c01345acfa4 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java
@@ -96,7 +96,7 @@ import 
org.apache.hadoop.hbase.shaded.protobuf.generated.TracingProtos.RPCTInfo;
 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = 
"VO_VOLATILE_INCREMENT",
     justification = "False positive according to 
http://sourceforge.net/p/findbugs/bugs/1032/";)
 @InterfaceAudience.Private
-abstract class ServerRpcConnection implements Closeable {
+public abstract class ServerRpcConnection implements Closeable {
 
   private static final TextMapGetter<RPCTInfo> getter = new RPCTInfoGetter();
 
@@ -484,6 +484,10 @@ abstract class ServerRpcConnection implements Closeable {
       this.hostAddress, this.remotePort, version, this.useSasl, this.ugi, 
serviceName);
   }
 
+  public ConnectionHeader getConnectionHeader() {
+    return connectionHeader;
+  }
+
   /**
    * Send the response for connection header
    */
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/SimpleServerCall.java 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/SimpleServerCall.java
index 5c5e9102115..ff0ab8067f7 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/SimpleServerCall.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/SimpleServerCall.java
@@ -70,7 +70,8 @@ class SimpleServerCall extends 
ServerCall<SimpleServerRpcConnection> {
     this.responder.doRespond(getConnection(), this);
   }
 
-  SimpleServerRpcConnection getConnection() {
+  @Override
+  public SimpleServerRpcConnection getConnection() {
     return this.connection;
   }
 }
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
index 4bd6e9f67f8..65ac9e333d4 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
@@ -1981,7 +1981,11 @@ public class HRegionServer extends Thread
         .setHostName(clientMetrics.getHostName())
         .setWriteRequestsCount(clientMetrics.getWriteRequestsCount())
         .setFilteredRequestsCount(clientMetrics.getFilteredReadRequests())
-        .setReadRequestsCount(clientMetrics.getReadRequestsCount()).build())
+        .setReadRequestsCount(clientMetrics.getReadRequestsCount())
+        
.setHostAddress(clientMetrics.getHostAddress()).setUserName(clientMetrics.getUserName())
+        .setClientVersion(clientMetrics.getClientVersion())
+        .setServiceName(clientMetrics.getServiceName())
+        .setClientVersion(clientMetrics.getClientVersion()).build())
       .forEach(userLoadBldr::addClientMetrics);
     return userLoadBldr.build();
   }
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsUserAggregateImpl.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsUserAggregateImpl.java
index 4856105f4e8..dc981706710 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsUserAggregateImpl.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsUserAggregateImpl.java
@@ -23,11 +23,14 @@ import java.util.Optional;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.CompatibilitySingletonFactory;
 import org.apache.hadoop.hbase.ipc.RpcServer;
+import org.apache.hadoop.hbase.ipc.ServerRpcConnection;
 import org.apache.hadoop.hbase.security.User;
 import org.apache.hadoop.hbase.security.UserProvider;
 import org.apache.hadoop.hbase.util.LossyCounting;
 import org.apache.yetus.audience.InterfaceAudience;
 
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos;
+
 @InterfaceAudience.Private
 public class MetricsUserAggregateImpl implements MetricsUserAggregate {
 
@@ -83,23 +86,29 @@ public class MetricsUserAggregateImpl implements 
MetricsUserAggregate {
   }
 
   private void incrementClientReadMetrics(MetricsUserSource userSource) {
+    ClientConnectionContext ctx = getClientConnectionContext();
     String client = getClient();
     if (client != null && userSource != null) {
-      userSource.getOrCreateMetricsClient(client).incrementReadRequest();
+      userSource.getOrCreateMetricsClient(client, ctx.hostAddress, 
ctx.userName, ctx.clientVersion,
+        ctx.serviceName).incrementReadRequest();
     }
   }
 
   private void incrementFilteredReadRequests(MetricsUserSource userSource) {
+    ClientConnectionContext ctx = getClientConnectionContext();
     String client = getClient();
     if (client != null && userSource != null) {
-      
userSource.getOrCreateMetricsClient(client).incrementFilteredReadRequests();
+      userSource.getOrCreateMetricsClient(client, ctx.hostAddress, 
ctx.userName, ctx.clientVersion,
+        ctx.serviceName).incrementFilteredReadRequests();
     }
   }
 
   private void incrementClientWriteMetrics(MetricsUserSource userSource) {
+    ClientConnectionContext ctx = getClientConnectionContext();
     String client = getClient();
     if (client != null && userSource != null) {
-      userSource.getOrCreateMetricsClient(client).incrementWriteRequest();
+      userSource.getOrCreateMetricsClient(client, ctx.hostAddress, 
ctx.userName, ctx.clientVersion,
+        ctx.serviceName).incrementWriteRequest();
     }
   }
 
@@ -193,4 +202,51 @@ public class MetricsUserAggregateImpl implements 
MetricsUserAggregate {
     userMetricLossyCounting.add(userSource);
     return userSource;
   }
+
+  private ClientConnectionContext getClientConnectionContext() {
+    String hostAddress = null;
+    String userName = "Unknown";
+    String clientVersion = "Unknown";
+    String serviceName = "Unknown";
+
+    Optional<ServerRpcConnection> rpcConnectionOptional = 
RpcServer.getCurrentServerRpcConnection();
+    if (rpcConnectionOptional.isPresent()) {
+      ServerRpcConnection rpcConnection = rpcConnectionOptional.get();
+      hostAddress = rpcConnection.getHostAddress();
+      RPCProtos.ConnectionHeader connectionHeader = 
rpcConnection.getConnectionHeader();
+      if (connectionHeader.hasUserInfo()) {
+        RPCProtos.UserInformation userInfoProto = 
connectionHeader.getUserInfo();
+        if (userInfoProto.hasEffectiveUser()) {
+          userName = userInfoProto.getEffectiveUser();
+        }
+      }
+      if (connectionHeader.hasVersionInfo()) {
+        clientVersion = connectionHeader.getVersionInfo().getVersion();
+      }
+      if (connectionHeader.hasServiceName()) {
+        serviceName = connectionHeader.getServiceName();
+      }
+    }
+    return new ClientConnectionContext(hostAddress, userName, clientVersion, 
serviceName);
+  }
+
+  private static class ClientConnectionContext {
+    final String hostAddress;
+    final String userName;
+    final String clientVersion;
+    final String serviceName;
+
+    ClientConnectionContext(String hostAddress, String userName, String 
clientVersion,
+      String serviceName) {
+      this.hostAddress = hostAddress;
+      this.userName = userName;
+      this.clientVersion = clientVersion;
+      this.serviceName = serviceName;
+    }
+
+    public String toString() {
+      return "ClientConnectionContext{hostAddress=" + hostAddress + ", 
userName=" + userName
+        + ", clientVersion=" + clientVersion + ", serviceName=" + serviceName 
+ "}";
+    }
+  }
 }
diff --git 
a/hbase-server/src/main/resources/hbase-webapps/master/regionServerList.jsp 
b/hbase-server/src/main/resources/hbase-webapps/master/regionServerList.jsp
index 9fc0adda00c..87f61115b05 100644
--- a/hbase-server/src/main/resources/hbase-webapps/master/regionServerList.jsp
+++ b/hbase-server/src/main/resources/hbase-webapps/master/regionServerList.jsp
@@ -63,6 +63,9 @@
     <li class="nav-item">
       <a class="nav-link" href="#tab_replicationStats" data-bs-toggle="tab" 
role="tab">Replications</a>
     </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#tab_clientConnectionsStats" 
data-bs-toggle="tab" role="tab">Client Connections</a>
+    </li>
   </ul>
   <div class="tab-content">
     <% request.setAttribute("serverNames", serverNames); %>
@@ -84,6 +87,9 @@
     <div class="tab-pane" id="tab_replicationStats" role="tabpanel">
       <jsp:include page="regionServerListReplicationStats.jsp" />
     </div>
+    <div class="tab-pane" id="tab_clientConnectionsStats" role="tabpanel">
+      <jsp:include page="regionServerListClientConnectionsStats.jsp" />
+    </div>
   </div>
 </div>
 
diff --git 
a/hbase-server/src/main/resources/hbase-webapps/master/regionServerListClientConnectionsStats.jsp
 
b/hbase-server/src/main/resources/hbase-webapps/master/regionServerListClientConnectionsStats.jsp
new file mode 100644
index 00000000000..d559e29e3c2
--- /dev/null
+++ 
b/hbase-server/src/main/resources/hbase-webapps/master/regionServerListClientConnectionsStats.jsp
@@ -0,0 +1,74 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="org.apache.hadoop.hbase.ServerName"
+         import="org.apache.hadoop.hbase.master.HMaster"
+         import="org.apache.hadoop.hbase.ServerMetrics"
+         import="org.apache.hadoop.hbase.master.ServerManager"
+         import="org.apache.hadoop.hbase.UserMetrics"
+         import="java.util.Map"
+         import="org.apache.commons.lang3.StringEscapeUtils" %>
+
+<%
+  HMaster master = (HMaster) getServletContext().getAttribute(HMaster.MASTER);
+  ServerName[] serverNames = (ServerName[]) 
request.getAttribute("serverNames");
+  ServerManager serverManager = master.getServerManager();
+%>
+
+<table id="clientConnectionsStatsTable" class="tablesorter table 
table-striped">
+  <thead>
+  <tr>
+    <th class="cls_separator">ClientIP</th>
+    <th class="cls_separator">UserName</th>
+    <th class="cls_separator">ClientVersion</th>
+    <th class="cls_separator">ServiceName</th>
+    <th class="cls_separator">ServerInfo</th>
+  </tr>
+  </thead>
+  <tbody>
+  <%
+    for (ServerName serverName: serverNames) {
+      ServerMetrics serverMetrics = serverManager.getLoad(serverName);
+      if(serverMetrics != null) {
+
+        Map<byte[], UserMetrics> userMetricsMap = 
serverMetrics.getUserMetrics();
+        for(Map.Entry<byte[], UserMetrics> entry : userMetricsMap.entrySet()) {
+          UserMetrics userMetrics = entry.getValue();
+          Map<String, UserMetrics.ClientMetrics> clientMetricsMap = 
userMetrics.getClientMetrics();
+
+          for(Map.Entry<String, UserMetrics.ClientMetrics> clientEntry : 
clientMetricsMap.entrySet()) {
+            UserMetrics.ClientMetrics clientConnection = 
clientEntry.getValue();
+
+  %>
+  <tr>
+    <td><%= StringEscapeUtils.escapeHtml4(clientConnection.getHostAddress()) 
%></td>
+    <td><%= StringEscapeUtils.escapeHtml4(clientConnection.getUserName()) 
%></td>
+    <td><%= StringEscapeUtils.escapeHtml4(clientConnection.getClientVersion()) 
%></td>
+    <td><%= StringEscapeUtils.escapeHtml4(clientConnection.getServiceName()) 
%></td>
+    <td><%= StringEscapeUtils.escapeHtml4(serverName.getServerName()) %></td>
+  </tr>
+  <%
+          }
+        }
+      }
+    }
+  %>
+  </tbody>
+</table>
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestMasterStatusPage.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestMasterStatusPage.java
index cc8a4fb448e..5fe5a29e053 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestMasterStatusPage.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestMasterStatusPage.java
@@ -114,6 +114,15 @@ public class TestMasterStatusPage {
     assertTrue(page.contains("<h2><a name=\"tasks\">Tasks</a></h2>"));
     assertTrue(page.contains("<h2><a name=\"attributes\">Software 
Attributes</a></h2>"));
 
+    assertTrue(page.contains("Client Connections"));
+    assertTrue(page.contains("tab_clientConnectionsStats"));
+    assertTrue(page.contains("clientConnectionsStatsTable"));
+    assertTrue(page.contains("ClientIP"));
+    assertTrue(page.contains("UserName"));
+    assertTrue(page.contains("ClientVersion"));
+    assertTrue(page.contains("ServiceName"));
+    assertTrue(page.contains("ServerInfo"));
+
     assertTrue(page.contains(VersionInfo.getVersion()));
   }
 


Reply via email to