Skip to content

Commit 8ca05b3

Browse files
authored
Merge pull request #2093 from ClickHouse/add-micrometer-support
[client-v2] Initial metrics implementation in client v2 using micrometer
2 parents 0b2485f + 1481b7b commit 8ca05b3

File tree

6 files changed

+148
-14
lines changed

6 files changed

+148
-14
lines changed

client-v2/pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@
7474
<artifactId>jackson-core</artifactId>
7575
<version>2.17.2</version>
7676
</dependency>
77+
<dependency>
78+
<groupId>io.micrometer</groupId>
79+
<artifactId>micrometer-core</artifactId>
80+
<version>1.14.3</version>
81+
<optional>true</optional>
82+
<scope>compile</scope>
83+
</dependency>
84+
7785

7886
<!-- Test Dependencies -->
7987
<dependency>
@@ -191,6 +199,11 @@
191199
<createDependencyReducedPom>true</createDependencyReducedPom>
192200
<createSourcesJar>true</createSourcesJar>
193201
<promoteTransitiveDependencies>true</promoteTransitiveDependencies>
202+
<artifactSet>
203+
<excludes>
204+
<exclude>io.micrometer:*</exclude>
205+
</excludes>
206+
</artifactSet>
194207
<relocations>
195208
<relocation>
196209
<pattern>org.slf4j</pattern>

client-v2/src/main/java/com/clickhouse/client/api/Client.java

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,10 @@
3939
import com.clickhouse.client.config.ClickHouseClientOption;
4040
import com.clickhouse.data.ClickHouseColumn;
4141
import com.clickhouse.data.ClickHouseFormat;
42-
import org.apache.hc.client5.http.ConnectTimeoutException;
4342
import org.apache.hc.core5.concurrent.DefaultThreadFactory;
4443
import org.apache.hc.core5.http.ClassicHttpResponse;
45-
import org.apache.hc.core5.http.ConnectionRequestTimeoutException;
4644
import org.apache.hc.core5.http.HttpHeaders;
4745
import org.apache.hc.core5.http.HttpStatus;
48-
import org.apache.hc.core5.http.NoHttpResponseException;
4946
import org.slf4j.Logger;
5047
import org.slf4j.LoggerFactory;
5148

@@ -54,8 +51,6 @@
5451
import java.io.OutputStream;
5552
import java.lang.reflect.InvocationTargetException;
5653
import java.lang.reflect.Method;
57-
import java.net.ConnectException;
58-
import java.net.SocketTimeoutException;
5954
import java.net.URL;
6055
import java.nio.charset.StandardCharsets;
6156
import java.time.Duration;
@@ -146,15 +141,22 @@ public class Client implements AutoCloseable {
146141

147142
// Server context
148143
private String serverVersion;
144+
private Object metrics;
149145

150146
private Client(Set<String> endpoints, Map<String,String> configuration, boolean useNewImplementation,
151147
ExecutorService sharedOperationExecutor, ColumnToMethodMatchingStrategy columnToMethodMatchingStrategy) {
148+
this(endpoints, configuration, useNewImplementation, sharedOperationExecutor, columnToMethodMatchingStrategy, null);
149+
}
150+
151+
private Client(Set<String> endpoints, Map<String,String> configuration, boolean useNewImplementation,
152+
ExecutorService sharedOperationExecutor, ColumnToMethodMatchingStrategy columnToMethodMatchingStrategy, Object metrics) {
152153
this.endpoints = endpoints;
153154
this.configuration = configuration;
154155
this.readOnlyConfig = Collections.unmodifiableMap(this.configuration);
155156
this.endpoints.forEach(endpoint -> {
156157
this.serverNodes.add(ClickHouseNode.of(endpoint, this.configuration));
157158
});
159+
this.metrics = metrics;
158160
this.serializers = new ConcurrentHashMap<>();
159161
this.deserializers = new ConcurrentHashMap<>();
160162

@@ -166,7 +168,7 @@ private Client(Set<String> endpoints, Map<String,String> configuration, boolean
166168
this.isSharedOpExecuterorOwned = false;
167169
this.sharedOperationExecutor = sharedOperationExecutor;
168170
}
169-
this.httpClientHelper = new HttpAPIClientHelper(configuration);
171+
this.httpClientHelper = new HttpAPIClientHelper(configuration, metrics);
170172
this.columnToMethodMatchingStrategy = columnToMethodMatchingStrategy;
171173
updateServerContext();
172174
}
@@ -233,7 +235,7 @@ public static class Builder {
233235

234236
private ExecutorService sharedOperationExecutor = null;
235237
private ColumnToMethodMatchingStrategy columnToMethodMatchingStrategy;
236-
238+
private Object metric = null;
237239
public Builder() {
238240
this.endpoints = new HashSet<>();
239241
this.configuration = new HashMap<String, String>();
@@ -761,7 +763,7 @@ public Builder useAsyncRequests(boolean async) {
761763
* Executor will stay running after {@code Client#close() } is called. It is application responsibility to close
762764
* the executor.
763765
* @param executorService - executor service for async operations
764-
* @return
766+
* @return
765767
*/
766768
public Builder setSharedOperationExecutor(ExecutorService executorService) {
767769
this.sharedOperationExecutor = executorService;
@@ -950,6 +952,19 @@ public Builder useBearerTokenAuth(String bearerToken) {
950952
return this;
951953
}
952954

955+
/**
956+
* Registers http client metrics with MeterRegistry.
957+
*
958+
* @param registry - metrics registry
959+
* @param name - name of metrics group
960+
* @return same instance of the builder
961+
*/
962+
public Builder registerClientMetrics(Object registry, String name) {
963+
this.metric = registry;
964+
this.configuration.put(ClientConfigProperties.METRICS_GROUP_NAME.getKey(), name);
965+
return this;
966+
}
967+
953968
public Client build() {
954969
setDefaults();
955970

@@ -1009,7 +1024,7 @@ public Client build() {
10091024
}
10101025

10111026
return new Client(this.endpoints, this.configuration, this.useNewImplementation, this.sharedOperationExecutor,
1012-
this.columnToMethodMatchingStrategy);
1027+
this.columnToMethodMatchingStrategy, this.metric);
10131028
}
10141029

10151030

client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,10 @@ public enum ClientConfigProperties {
127127
* Indicates that data provided for write operation is compressed by application.
128128
*/
129129
APP_COMPRESSED_DATA("app_compressed_data"),
130-
130+
/**
131+
*
132+
*/
133+
METRICS_GROUP_NAME("metrics_name"),
131134
;
132135

133136
private String key;

client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
2222
import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager;
2323
import org.apache.hc.client5.http.impl.io.ManagedHttpClientConnectionFactory;
24+
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
2425
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
2526
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
2627
import org.apache.hc.client5.http.protocol.HttpClientContext;
@@ -57,6 +58,7 @@
5758
import java.io.IOException;
5859
import java.io.InputStream;
5960
import java.io.OutputStream;
61+
import java.lang.reflect.Method;
6062
import java.net.ConnectException;
6163
import java.net.InetSocketAddress;
6264
import java.net.NoRouteToHostException;
@@ -94,9 +96,10 @@ public class HttpAPIClientHelper {
9496
private final Set<ClientFaultCause> defaultRetryCauses;
9597

9698
private String defaultUserAgent;
97-
98-
public HttpAPIClientHelper(Map<String, String> configuration) {
99+
private Object metricsRegistry;
100+
public HttpAPIClientHelper(Map<String, String> configuration, Object metricsRegistry) {
99101
this.chConfiguration = configuration;
102+
this.metricsRegistry = metricsRegistry;
100103
this.httpClient = createHttpClient();
101104

102105
RequestConfig.Builder reqConfBuilder = RequestConfig.custom();
@@ -189,7 +192,6 @@ private HttpClientConnectionManager poolConnectionManager(SSLContext sslContext,
189192
PoolingHttpClientConnectionManagerBuilder connMgrBuilder = PoolingHttpClientConnectionManagerBuilder.create()
190193
.setPoolConcurrencyPolicy(PoolConcurrencyPolicy.LAX);
191194

192-
193195
ConnectionReuseStrategy connectionReuseStrategy =
194196
ConnectionReuseStrategy.valueOf(chConfiguration.get("connection_reuse_strategy"));
195197
switch (connectionReuseStrategy) {
@@ -221,7 +223,19 @@ private HttpClientConnectionManager poolConnectionManager(SSLContext sslContext,
221223
connMgrBuilder.setConnectionFactory(connectionFactory);
222224
connMgrBuilder.setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext));
223225
connMgrBuilder.setDefaultSocketConfig(socketConfig);
224-
return connMgrBuilder.build();
226+
PoolingHttpClientConnectionManager phccm = connMgrBuilder.build();
227+
if (metricsRegistry != null ) {
228+
try {
229+
String mGroupName = chConfiguration.getOrDefault(ClientConfigProperties.METRICS_GROUP_NAME.getKey(),
230+
"ch-http-pool");
231+
Class<?> micrometerLoader = getClass().getClassLoader().loadClass("com.clickhouse.client.api.metrics.MicrometerLoader");
232+
Method applyMethod = micrometerLoader.getDeclaredMethod("applyPoolingMetricsBinder", Object.class, String.class, PoolingHttpClientConnectionManager.class);
233+
applyMethod.invoke(micrometerLoader, metricsRegistry, mGroupName, phccm);
234+
} catch (Exception e) {
235+
LOG.error("Failed to register metrics", e);
236+
}
237+
}
238+
return phccm;
225239
}
226240

227241
public CloseableHttpClient createHttpClient() {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.clickhouse.client.api.metrics;
2+
3+
import com.clickhouse.client.api.ClientMisconfigurationException;
4+
import io.micrometer.core.instrument.MeterRegistry;
5+
import io.micrometer.core.instrument.binder.httpcomponents.hc5.PoolingHttpClientConnectionManagerMetricsBinder;
6+
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
7+
8+
public class MicrometerLoader {
9+
10+
public static void applyPoolingMetricsBinder(Object registry, String metricsGroupName, PoolingHttpClientConnectionManager phccm) {
11+
if (registry instanceof MeterRegistry) {
12+
new PoolingHttpClientConnectionManagerMetricsBinder(phccm, metricsGroupName).bindTo((MeterRegistry) registry);
13+
} else {
14+
throw new ClientMisconfigurationException("Unsupported registry type." + registry.getClass());
15+
}
16+
}
17+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.clickhouse.client.metrics;
2+
3+
import com.clickhouse.client.BaseIntegrationTest;
4+
import com.clickhouse.client.ClickHouseNode;
5+
import com.clickhouse.client.ClickHouseProtocol;
6+
import com.clickhouse.client.ClickHouseServerForTest;
7+
import com.clickhouse.client.api.Client;
8+
import com.clickhouse.client.api.ClientConfigProperties;
9+
import com.clickhouse.client.api.enums.Protocol;
10+
import com.clickhouse.client.api.internal.ServerSettings;
11+
import com.clickhouse.client.api.query.QueryResponse;
12+
import io.micrometer.core.instrument.Gauge;
13+
import io.micrometer.core.instrument.MeterRegistry;
14+
import io.micrometer.core.instrument.Metrics;
15+
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
16+
import org.testng.Assert;
17+
import org.testng.annotations.AfterMethod;
18+
import org.testng.annotations.BeforeMethod;
19+
import org.testng.annotations.Test;
20+
21+
import static org.testng.Assert.assertEquals;
22+
23+
public class MetricsTest extends BaseIntegrationTest {
24+
private MeterRegistry meterRegistry;
25+
@BeforeMethod(groups = {"integration"})
26+
void setUp() {
27+
meterRegistry = new SimpleMeterRegistry();
28+
Metrics.globalRegistry.add(meterRegistry);
29+
}
30+
31+
@AfterMethod(groups = {"integration"})
32+
void tearDown() {
33+
meterRegistry.clear();
34+
Metrics.globalRegistry.clear();
35+
}
36+
37+
@Test(groups = { "integration" }, enabled = true)
38+
public void testRegisterMetrics() throws Exception {
39+
ClickHouseNode node = getServer(ClickHouseProtocol.HTTP);
40+
boolean isSecure = isCloud();
41+
42+
try (Client client = new Client.Builder()
43+
.addEndpoint(Protocol.HTTP, node.getHost(), node.getPort(), isSecure)
44+
.setUsername("default")
45+
.setPassword(ClickHouseServerForTest.getPassword())
46+
.setDefaultDatabase(ClickHouseServerForTest.getDatabase())
47+
.serverSetting(ServerSettings.ASYNC_INSERT, "0")
48+
.serverSetting(ServerSettings.WAIT_END_OF_QUERY, "1")
49+
.registerClientMetrics(meterRegistry, "pool-test")
50+
.build()) {
51+
52+
Gauge totalMax = meterRegistry.get("httpcomponents.httpclient.pool.total.max").gauge();
53+
Gauge available = meterRegistry.get("httpcomponents.httpclient.pool.total.connections").tags("state", "available").gauge();
54+
Gauge leased = meterRegistry.get("httpcomponents.httpclient.pool.total.connections").tags("state", "leased").gauge();
55+
56+
System.out.println("totalMax:" + totalMax.value() + ", available: " + available.value() + ", leased: " + leased.value());
57+
Assert.assertEquals((int)totalMax.value(), Integer.parseInt(ClientConfigProperties.HTTP_MAX_OPEN_CONNECTIONS.getDefaultValue()));
58+
Assert.assertEquals((int)available.value(), 1);
59+
Assert.assertEquals((int)leased.value(), 0);
60+
61+
try (QueryResponse response = client.query("SELECT 1").get()) {
62+
Assert.assertEquals((int)available.value(), 0);
63+
Assert.assertEquals((int)leased.value(), 1);
64+
}
65+
66+
Assert.assertEquals((int)available.value(), 1);
67+
Assert.assertEquals((int)leased.value(), 0);
68+
}
69+
// currently there are only 5 metrics that are monitored by micrometer (out of the box)
70+
assertEquals(meterRegistry.getMeters().size(), 5);
71+
}
72+
}

0 commit comments

Comments
 (0)