diff --git a/collector-monitor/pom.xml b/collector-monitor/pom.xml
index 06c5b48993b28..cdf01f6b13aee 100644
--- a/collector-monitor/pom.xml
+++ b/collector-monitor/pom.xml
@@ -56,4 +56,6 @@
+
+
diff --git a/collector-monitor/src/main/java/com/navercorp/pinpoint/collector/monitor/config/HbaseConnectionReflects.java b/collector-monitor/src/main/java/com/navercorp/pinpoint/collector/monitor/config/HbaseConnectionReflects.java
new file mode 100644
index 0000000000000..d3ac21c743b24
--- /dev/null
+++ b/collector-monitor/src/main/java/com/navercorp/pinpoint/collector/monitor/config/HbaseConnectionReflects.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed 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 com.navercorp.pinpoint.collector.monitor.config;
+
+import org.apache.hadoop.hbase.client.AsyncConnectionImpl;
+import org.apache.hadoop.hbase.client.ClusterConnection;
+import org.apache.hadoop.hbase.client.MetricsConnection;
+import org.apache.hadoop.hbase.shaded.com.codahale.metrics.MetricRegistry;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+
+/**
+ * @author intr3p1d
+ */
+public class HbaseConnectionReflects {
+
+ private static final Logger logger = LogManager.getLogger(HbaseConnectionReflects.class);
+
+
+ static List getRegistriesFromConnections(ClusterConnection conn, AsyncConnectionImpl asyncConn) {
+ List registries = new ArrayList<>();
+
+ addMetricRegistryFromConnection(registries, conn);
+ addMetricRegistryFromAsyncConnection(registries, asyncConn);
+ return registries;
+ }
+
+ static void addMetricRegistryFromConnection(List registries, ClusterConnection conn) {
+ MetricRegistry metricRegistry = getMetricRegistry(getMetricsConnection(conn));
+ if (metricRegistry != null) {
+ registries.add(metricRegistry);
+ }
+ }
+
+ static void addMetricRegistryFromAsyncConnection(List registries, AsyncConnectionImpl asyncConn) {
+ MetricsConnection metricsConnection = getMetricsConnection(asyncConn)
+ .orElseThrow(() -> new NoSuchElementException("MetricsConnection not present"));
+ MetricRegistry metricRegistry = getMetricRegistry(metricsConnection);
+ if (metricRegistry != null) {
+ registries.add(metricRegistry);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ static Optional getMetricsConnection(AsyncConnectionImpl asyncConnection) {
+ try {
+ Method method = asyncConnection.getClass().getDeclaredMethod("getConnectionMetrics");
+ method.setAccessible(true);
+ return (Optional) method.invoke(asyncConnection);
+ } catch (Exception e) {
+ logger.warn(e);
+ return Optional.empty();
+ }
+ }
+
+ static MetricsConnection getMetricsConnection(ClusterConnection connectionImplementation) {
+ try {
+ Method method = connectionImplementation.getClass().getDeclaredMethod("getConnectionMetrics");
+ method.setAccessible(true);
+ return (MetricsConnection) method.invoke(connectionImplementation);
+ } catch (Exception e) {
+ logger.warn(e);
+ return null;
+ }
+ }
+
+ static MetricRegistry getMetricRegistry(MetricsConnection metricsConnection) {
+ try {
+ Method method = metricsConnection.getClass().getDeclaredMethod("getMetricRegistry");
+ method.setAccessible(true);
+ return (MetricRegistry) method.invoke(metricsConnection);
+ } catch (Exception e) {
+ logger.warn(e);
+ return null;
+ }
+ }
+}
diff --git a/collector-monitor/src/main/java/com/navercorp/pinpoint/collector/monitor/config/HbaseMetricsConfiguration.java b/collector-monitor/src/main/java/com/navercorp/pinpoint/collector/monitor/config/HbaseMetricsConfiguration.java
new file mode 100644
index 0000000000000..e7b074d49d301
--- /dev/null
+++ b/collector-monitor/src/main/java/com/navercorp/pinpoint/collector/monitor/config/HbaseMetricsConfiguration.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed 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 com.navercorp.pinpoint.collector.monitor.config;
+
+import com.navercorp.pinpoint.collector.monitor.dao.hbase.HBaseMetricsAdapter;
+import io.micrometer.core.instrument.MeterRegistry;
+import org.apache.hadoop.hbase.client.AsyncConnection;
+import org.apache.hadoop.hbase.client.AsyncConnectionImpl;
+import org.apache.hadoop.hbase.client.ClusterConnection;
+import org.apache.hadoop.hbase.client.Connection;
+import org.apache.hadoop.hbase.shaded.com.codahale.metrics.MetricRegistry;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+import static com.navercorp.pinpoint.collector.monitor.config.HbaseConnectionReflects.getRegistriesFromConnections;
+
+/**
+ * @author intr3p1d
+ */
+@Configuration
+@ConditionalOnProperty(value = "pinpoint.modules.collector.hbase-client-metric", havingValue = "true")
+public class HbaseMetricsConfiguration {
+
+ private final Logger logger = LogManager.getLogger(HbaseMetricsConfiguration.class);
+
+ public HbaseMetricsConfiguration() {
+ logger.info("Install {}", HbaseMetricsConfiguration.class.getSimpleName());
+ }
+
+ @Bean
+ public HBaseMetricsAdapter collectHBaseMetrics(
+ MeterRegistry meterRegistry,
+ @Qualifier("hbaseConnection")
+ FactoryBean connectionFactoryBean,
+ @Qualifier("hbaseAsyncConnection")
+ FactoryBean asyncConnectionFactoryBean
+ ) {
+ try {
+ ClusterConnection conn = (ClusterConnection) connectionFactoryBean.getObject();
+ AsyncConnectionImpl asyncConn = (AsyncConnectionImpl) asyncConnectionFactoryBean.getObject();
+ List registries = getRegistriesFromConnections(conn, asyncConn);
+
+ return new HBaseMetricsAdapter(
+ meterRegistry, registries
+ );
+ } catch (Exception e) {
+ logger.error("HbaseMetrics Error: ", e);
+ }
+ return null;
+ }
+
+}
diff --git a/collector-monitor/src/main/java/com/navercorp/pinpoint/collector/monitor/dao/hbase/HBaseMetricsAdapter.java b/collector-monitor/src/main/java/com/navercorp/pinpoint/collector/monitor/dao/hbase/HBaseMetricsAdapter.java
new file mode 100644
index 0000000000000..d66cddba0085b
--- /dev/null
+++ b/collector-monitor/src/main/java/com/navercorp/pinpoint/collector/monitor/dao/hbase/HBaseMetricsAdapter.java
@@ -0,0 +1,92 @@
+package com.navercorp.pinpoint.collector.monitor.dao.hbase;
+
+import io.micrometer.core.instrument.DistributionSummary;
+import io.micrometer.core.instrument.MeterRegistry;
+import org.apache.hadoop.hbase.shaded.com.codahale.metrics.Counter;
+import org.apache.hadoop.hbase.shaded.com.codahale.metrics.Gauge;
+import org.apache.hadoop.hbase.shaded.com.codahale.metrics.Histogram;
+import org.apache.hadoop.hbase.shaded.com.codahale.metrics.MetricRegistry;
+import org.apache.hadoop.hbase.shaded.com.codahale.metrics.Timer;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.Collection;
+
+import static com.navercorp.pinpoint.collector.monitor.dao.hbase.MetricNameExtractor.extractName;
+import static com.navercorp.pinpoint.collector.monitor.dao.hbase.MetricNameExtractor.extractTags;
+
+
+public class HBaseMetricsAdapter {
+ private final Logger logger = LogManager.getLogger(HBaseMetricsAdapter.class);
+ private final MeterRegistry meterRegistry;
+ private final Collection metricRegistries;
+
+ public HBaseMetricsAdapter(MeterRegistry meterRegistry, Collection metricRegistries) {
+ this.meterRegistry = meterRegistry;
+ this.metricRegistries = metricRegistries;
+ initialize();
+ }
+
+ private void initialize() {
+ logger.info("initialize metricRegistries: {}", metricRegistries);
+
+ for (MetricRegistry metricRegistry : metricRegistries) {
+ if (metricRegistry != null) {
+ logger.info(metricRegistry);
+ metricRegistry.getMetrics().forEach((name, metric) -> {
+ if (metric instanceof Counter counter) {
+ registerCounterMetric(name, counter);
+ } else if (metric instanceof Timer timer) {
+ registerTimerMetric(name, timer);
+ } else if (metric instanceof Gauge> gauge) {
+ registerGaugeMetric(name, gauge);
+ } else if (metric instanceof Histogram histogram) {
+ registerHistogramMetric(name, histogram);
+ }
+ });
+ }
+ }
+ }
+
+ private void registerCounterMetric(String name, Counter counter) {
+ io.micrometer.core.instrument.Gauge.builder(extractName(name), counter, Counter::getCount)
+ .tags(extractTags(name))
+ .register(meterRegistry);
+ }
+
+ private void registerTimerMetric(String name, Timer timer) {
+ io.micrometer.core.instrument.Gauge.builder(extractName(name), timer, Timer::getCount)
+ .tags(extractTags(name))
+ .register(meterRegistry);
+ }
+
+ private void registerGaugeMetric(String name, Gauge> gauge) {
+ io.micrometer.core.instrument.Gauge.builder(extractName(name), gauge, HBaseMetricsAdapter::doubleValue)
+ .tags(extractTags(name))
+ .register(meterRegistry);
+ }
+
+ private void registerHistogramMetric(String name, Histogram histogram) {
+ DistributionSummary.builder(extractName(name))
+ .tags(extractTags(name))
+ .register(meterRegistry);
+ }
+
+ public static double doubleValue(Gauge> gauge) {
+ if (gauge == null || gauge.getValue() == null) {
+ return Double.NaN;
+ }
+ Object value = gauge.getValue();
+ return Double.parseDouble(value.toString());
+ }
+
+
+ @Override
+ public String toString() {
+ return "HBaseMetricsAdapter{" +
+ "logger=" + logger +
+ ", meterRegistry=" + meterRegistry +
+ ", metricRegistries=" + metricRegistries +
+ '}';
+ }
+}
diff --git a/collector-monitor/src/main/java/com/navercorp/pinpoint/collector/monitor/dao/hbase/MetricNameExtractor.java b/collector-monitor/src/main/java/com/navercorp/pinpoint/collector/monitor/dao/hbase/MetricNameExtractor.java
new file mode 100644
index 0000000000000..1cc985f334e9f
--- /dev/null
+++ b/collector-monitor/src/main/java/com/navercorp/pinpoint/collector/monitor/dao/hbase/MetricNameExtractor.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed 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 com.navercorp.pinpoint.collector.monitor.dao.hbase;
+
+import io.micrometer.core.instrument.Tag;
+import io.micrometer.core.instrument.Tags;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author intr3p1d
+ */
+public class MetricNameExtractor {
+
+ static String extractName(String name) {
+ int atIndex = name.lastIndexOf('.');
+ if (atIndex != -1) {
+ return name.substring(0, atIndex);
+ } else {
+ return name;
+ }
+ }
+
+ static Tags extractTags(String name) {
+ String regex = ".*\\.([0-9a-fA-F\\-]{36})@([0-9a-fA-F]+)$";
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(name);
+
+ if (matcher.matches()) {
+ String uuid = matcher.group(1);
+ String hash = matcher.group(2);
+
+ return Tags.of(
+ Tag.of("clusterId", uuid),
+ Tag.of("connectionHash", hash)
+ );
+ } else {
+ return Tags.empty();
+ }
+ }
+
+}
diff --git a/collector-monitor/src/test/java/com/navercorp/pinpoint/collector/monitor/dao/hbase/MetricNameExtractorTest.java b/collector-monitor/src/test/java/com/navercorp/pinpoint/collector/monitor/dao/hbase/MetricNameExtractorTest.java
new file mode 100644
index 0000000000000..497f30eb60532
--- /dev/null
+++ b/collector-monitor/src/test/java/com/navercorp/pinpoint/collector/monitor/dao/hbase/MetricNameExtractorTest.java
@@ -0,0 +1,37 @@
+package com.navercorp.pinpoint.collector.monitor.dao.hbase;
+
+import io.micrometer.core.instrument.Tag;
+import io.micrometer.core.instrument.Tags;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author intr3p1d
+ */
+class MetricNameExtractorTest {
+
+ static final String METRIC_NAME = "org.apache.hadoop.hbase.client.MetricsConnection.executorPoolActiveThreads";
+ static final String DUMMY_CLUSTER_ID = "f72a0b6a-8141-4df9-96a3-754aac08e173";
+ static final String DUMMY_HASH = "10579683cf";
+
+ @Test
+ public void testCustomName() {
+ String example = METRIC_NAME + "." + DUMMY_CLUSTER_ID + "@" + DUMMY_HASH;
+ String actual = MetricNameExtractor.extractName(example);
+
+ Assertions.assertEquals(METRIC_NAME, actual);
+ }
+
+ @Test
+ public void testExtractTags() {
+ String example = METRIC_NAME + "." + DUMMY_CLUSTER_ID + "@" + DUMMY_HASH;
+ Tags expected = Tags.of(
+ Tag.of("clusterId", DUMMY_CLUSTER_ID),
+ Tag.of("connectionHash", DUMMY_HASH)
+ );
+ Tags actual = MetricNameExtractor.extractTags(example);
+
+ Assertions.assertEquals(expected, actual);
+ }
+
+}
\ No newline at end of file
diff --git a/collector/src/main/java/com/navercorp/pinpoint/collector/grpc/CollectorGrpcConfiguration.java b/collector/src/main/java/com/navercorp/pinpoint/collector/grpc/CollectorGrpcConfiguration.java
index f28a290968fa5..94dc1efc727a2 100644
--- a/collector/src/main/java/com/navercorp/pinpoint/collector/grpc/CollectorGrpcConfiguration.java
+++ b/collector/src/main/java/com/navercorp/pinpoint/collector/grpc/CollectorGrpcConfiguration.java
@@ -27,6 +27,7 @@
import com.navercorp.pinpoint.collector.grpc.config.GrpcStatConfiguration;
import com.navercorp.pinpoint.collector.grpc.config.GrpcStatReceiverConfiguration;
import com.navercorp.pinpoint.collector.monitor.MonitoredThreadPoolExecutorFactoryProvider;
+import com.navercorp.pinpoint.collector.monitor.config.HbaseMetricsConfiguration;
import com.navercorp.pinpoint.collector.monitor.config.MicrometerConfiguration;
import com.navercorp.pinpoint.collector.monitor.MonitoringExecutors;
import com.navercorp.pinpoint.common.server.executor.ExecutorCustomizer;
@@ -55,6 +56,7 @@
GrpcKeepAliveScheduler.class,
MicrometerConfiguration.class,
+ HbaseMetricsConfiguration.class,
ChannelzConfiguration.class
})