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()));
}