-
Notifications
You must be signed in to change notification settings - Fork 208
Added Basic Client side Metrics #1574
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 15 commits
8ac4d21
f4d4e43
f722d84
4975cc3
f68430c
7f8aa0e
dc72cb5
e4608df
8794ba3
bc4a273
c9db412
79fbdfe
4e9409e
dd38542
c595a46
7bda412
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,75 @@ | ||||||
- [SDK Metrics](#SDK-Metrics) | ||||||
- [How to enable metrics](#get-client) | ||||||
- [Metrics Collected](#metrics-collected) | ||||||
|
||||||
# SDK Metrics | ||||||
|
||||||
We've integrated a robust metrics solution into the OpenSearch Java client to provide comprehensive insights into its API usage and performance. This includes the collection of vital operational metrics, such as overall throughput, request latency, and error rate. Furthermore, we're capturing more granular details like request and response payload sizes, distinct success and failure rates for operations, and real-time OpenSearch node status to provide a holistic view of client behavior and cluster health. | ||||||
|
||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe add an explicit mention here that this uses micrometer (with a link to |
||||||
## How to enable metrics | ||||||
|
||||||
We should create a MetricOptions instance and set it in the ApacheHttpClient5TransportBuilder when creating the client. Take a look at below code snippet for an example of how to create a client with metrics enabled: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit:
Suggested change
|
||||||
|
||||||
```java | ||||||
public class CreateClient { | ||||||
public static OpenSearchClient createClientWithMetrics() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { | ||||||
var env = System.getenv(); | ||||||
var https = Boolean.parseBoolean(env.getOrDefault("HTTPS", "true")); | ||||||
var hostname = env.getOrDefault("HOST", "localhost"); | ||||||
var port = Integer.parseInt(env.getOrDefault("PORT", "9200")); | ||||||
var user = env.getOrDefault("USERNAME", "admin"); | ||||||
var pass = env.getOrDefault("PASSWORD", "admin"); | ||||||
var metricEnabled = true; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit:
Suggested change
(also for the usage(s) below) |
||||||
double PERCENTILE_99 = 0.99; | ||||||
double PERCENTILE_95 = 0.95; | ||||||
var meterRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe use |
||||||
|
||||||
final var hosts = new HttpHost[]{new HttpHost(https ? "https" : "http", hostname, port)}; | ||||||
|
||||||
final var sslContext = SSLContextBuilder.create().loadTrustMaterial(null, (chains, authType) -> true).build(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can use
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @rursprung the TrustAllStrategy should not be used in production code, we should probably have a placehoder here or use the default one |
||||||
|
||||||
MetricOptions metricOptions = MetricOptions.builder() | ||||||
.setMetricsEnabled(metricEnabled) // required to turn metrics on/off | ||||||
.setMeterRegistry(meterRegistry) | ||||||
.setPercentiles(PERCENTILE_99, PERCENTILE_95) | ||||||
.setAdditionalMetricGroups(MetricGroup.NETWORK_DETAILS) | ||||||
.setExcludedTags(MetricTag.HOST_CONTACTED) | ||||||
.build(); | ||||||
|
||||||
final var transport = ApacheHttpClient5TransportBuilder.builder(hosts) | ||||||
.setMapper(new JacksonJsonpMapper()) | ||||||
.setMetricOptions(metricOptions) | ||||||
.setHttpClientConfigCallback(httpClientBuilder -> { | ||||||
final var credentialsProvider = new BasicCredentialsProvider(); | ||||||
for (final var host : hosts) { | ||||||
credentialsProvider.setCredentials(new AuthScope(host), new UsernamePasswordCredentials(user, pass.toCharArray())); | ||||||
} | ||||||
|
||||||
// Disable SSL/TLS verification as our local testing clusters use self-signed certificates | ||||||
final var tlsStrategy = ClientTlsStrategyBuilder.create() | ||||||
.setSslContext(sslContext) | ||||||
.setHostnameVerifier(NoopHostnameVerifier.INSTANCE) | ||||||
.build(); | ||||||
|
||||||
final var connectionManager = PoolingAsyncClientConnectionManagerBuilder.create().setTlsStrategy(tlsStrategy).build(); | ||||||
|
||||||
return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider).setConnectionManager(connectionManager); | ||||||
}) | ||||||
.build(); | ||||||
return new OpenSearchClient(transport); | ||||||
} | ||||||
} | ||||||
``` | ||||||
|
||||||
## Metrics Collected | ||||||
|
||||||
The OpenSearch Java client collects a variety of metrics to provide insights into its operations. Below is a summary of the key metrics collected, along with their descriptions and important dimensions, such as status code and request type, that can be used for filtering and analysis. | ||||||
|
||||||
| Metric | Description | Important Dimensions(tags) | | ||||||
|---------------------------------------|-----------------------------------------------------------------|--------------------------------| | ||||||
| os.client.request.seconds | End-to-end request execution latency | StatusCodeOrException, Request | | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit (also for the other rows): i'd mark the metric name & tags as code for better readability):
Suggested change
|
||||||
| os.client.request.seconds.count | request throughput and error rate based on status tags | StatusCodeOrException, Request | | ||||||
| os.client.request.payload.size.bytes | Request payload size in bytes | Request | | ||||||
| os.client.response.payload.size.bytes | Response payload size in bytes | Request | | ||||||
| os.client.active.nodes | Number of OpenSearch active nodes from a client's perspective | | | ||||||
| os.client.inactive.nodes | Number of OpenSearch inactive nodes from a client's perspective | | |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
*/ | ||
|
||
package org.opensearch.client.transport.client_metrics; | ||
|
||
import java.time.Duration; | ||
|
||
/** | ||
* Contains necessary information about a request execution to be used for metric recordings. | ||
*/ | ||
public abstract class ExecutionMetricContext { | ||
|
||
public static final int DEFAULT_EMPTY_STATUS_CODE = -1; | ||
private Throwable throwable = null; | ||
private int statusCode = DEFAULT_EMPTY_STATUS_CODE; | ||
private Duration executionTime = null; | ||
|
||
protected ExecutionMetricContext() {} | ||
|
||
protected ExecutionMetricContext(Throwable throwable, int statusCode, Duration executionTime) { | ||
this.throwable = throwable; | ||
this.statusCode = statusCode; | ||
this.executionTime = executionTime; | ||
} | ||
|
||
public Throwable getThrowable() { | ||
return throwable; | ||
} | ||
|
||
public int getStatusCode() { | ||
return statusCode; | ||
} | ||
|
||
public Duration getRequestExecutionTime() { | ||
return executionTime; | ||
} | ||
|
||
public void setThrowable(Throwable throwable) { | ||
this.throwable = throwable; | ||
} | ||
|
||
public void setStatusCode(int statusCode) { | ||
this.statusCode = statusCode; | ||
} | ||
|
||
public void setRequestExecutionTime(Duration executionTime) { | ||
this.executionTime = executionTime; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
*/ | ||
|
||
package org.opensearch.client.transport.client_metrics; | ||
|
||
import static org.opensearch.client.transport.client_metrics.MetricConstants.DEFAULT_EXCLUDED_TAGS; | ||
import static org.opensearch.client.transport.client_metrics.MetricConstants.DEFAULT_PERCENTILES; | ||
|
||
import io.micrometer.core.instrument.Tags; | ||
import java.util.Set; | ||
|
||
/** | ||
* Contains settings for configuring a meter | ||
*/ | ||
public class MeterOptions { | ||
private final double[] percentiles; | ||
private final Tags commonTags; | ||
private final Set<MetricTag> excludedTagNames; | ||
|
||
public MeterOptions(double[] percentiles, Tags commonTags, Set<MetricTag> excludedTagNames) { | ||
this.percentiles = percentiles == null ? DEFAULT_PERCENTILES.clone() : percentiles.clone(); | ||
this.commonTags = commonTags == null ? Tags.empty() : commonTags; | ||
this.excludedTagNames = excludedTagNames == null ? DEFAULT_EXCLUDED_TAGS : excludedTagNames; | ||
} | ||
|
||
/** | ||
* Get percentiles to publish for Timer/Distribution meters | ||
* @return a double array | ||
*/ | ||
public double[] getPercentiles() { | ||
return percentiles; | ||
} | ||
|
||
/** | ||
* Get common {@link io.micrometer.core.instrument.Tags} that this meter need to have | ||
* @return a {@link io.micrometer.core.instrument.Tags} | ||
*/ | ||
public Tags getCommonTags() { | ||
return commonTags; | ||
} | ||
|
||
/** | ||
* Get tag names that a meter are excluded. | ||
* @return a set of {@link MetricTag} | ||
*/ | ||
public Set<MetricTag> getExcludedTagNames() { | ||
return excludedTagNames; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
*/ | ||
|
||
package org.opensearch.client.transport.client_metrics; | ||
|
||
import io.micrometer.core.instrument.MeterRegistry; | ||
import io.micrometer.core.instrument.Metrics; | ||
import java.util.EnumSet; | ||
import java.util.Set; | ||
|
||
public class MetricConstants { | ||
public static final double[] DEFAULT_PERCENTILES = new double[] { 0.99, 0.95, 0.9, 0.75, 0.5 }; | ||
public static final MeterRegistry DEFAULT_REGISTRY = Metrics.globalRegistry; | ||
public static final Set<MetricTag> DEFAULT_EXCLUDED_TAGS = EnumSet.noneOf(MetricTag.class); | ||
public static final Set<MetricGroup> DEFAULT_ADDITIONAL_METRIC_GROUPS = EnumSet.noneOf(MetricGroup.class); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
*/ | ||
|
||
package org.opensearch.client.transport.client_metrics; | ||
|
||
import java.util.EnumSet; | ||
import java.util.Set; | ||
|
||
public enum MetricGroup { | ||
GENERAL, | ||
NETWORK_DETAILS; | ||
|
||
public static final Set<MetricGroup> REQUIRED_GROUPS = EnumSet.of(GENERAL); | ||
public static final Set<MetricGroup> ALL = EnumSet.allOf(MetricGroup.class); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
*/ | ||
|
||
package org.opensearch.client.transport.client_metrics; | ||
|
||
public enum MetricName { | ||
REQUEST("request"), | ||
NETWORK_REQUEST("network.request"), | ||
ACTIVE_NODES("active.nodes"), | ||
INACTIVE_NODES("inactive.nodes"), | ||
REQUEST_PAYLOAD_SIZE("request.payload.size"), | ||
RESPONSE_PAYLOAD_SIZE("response.payload.size"); | ||
|
||
private static final String PREFIX = "os.client"; | ||
private final String metricName; | ||
|
||
MetricName(String metricName) { | ||
this.metricName = metricName; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return PREFIX + "." + metricName; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i don't think that prometheus warrants explicit mention here since the integration is with micrometer and not prometheus. it's just that micrometer offers an integration with prometheus - amongst many others