Skip to content
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

Improvements to the HttpMeterFilterFactory #766

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.annotation.Value;
import io.micronaut.core.util.ArrayUtils;
import jakarta.inject.Singleton;

import static io.micronaut.configuration.metrics.binder.web.WebMetricsPublisher.METRIC_HTTP_CLIENT_REQUESTS;
import static io.micronaut.configuration.metrics.binder.web.WebMetricsPublisher.METRIC_HTTP_SERVER_REQUESTS;
import java.util.Arrays;
import java.util.Objects;

import static io.micronaut.configuration.metrics.micrometer.MeterRegistryFactory.MICRONAUT_METRICS_BINDERS;
import static io.micronaut.core.util.StringUtils.FALSE;

Expand All @@ -41,41 +41,74 @@
@Requires(property = WebMetricsPublisher.ENABLED, notEquals = FALSE)
public class HttpMeterFilterFactory {

public static final double SECONDS_TO_NANOS = 1_000_000_000d;

/**
* Configure new MeterFilter for http.server.requests metrics.
*
* @param percentiles The percentiles
* @param histogram If a histogram should be published
* @param min the minimum time (in s) value expected.
* @param max the maximum time (in s) value expected.
* @param slos the user-defined service levels objectives (in s) to create.
* @return A MeterFilter
*/
@Bean
@Singleton
@Requires(property = MICRONAUT_METRICS_BINDERS + ".web.server.percentiles")
MeterFilter addServerPercentileMeterFilter(@Value("${" + MICRONAUT_METRICS_BINDERS + ".web.server.percentiles}") Double[] percentiles) {
return getMeterFilter(percentiles, METRIC_HTTP_SERVER_REQUESTS);
@Requires(property = MICRONAUT_METRICS_BINDERS + ".web.server")
MeterFilter addServerPercentileMeterFilter(
@Value("${" + MICRONAUT_METRICS_BINDERS + ".web.server.percentiles:}") Double[] percentiles,
@Value("${" + MICRONAUT_METRICS_BINDERS + ".web.server.histogram:false}") Boolean histogram,
@Value("${" + MICRONAUT_METRICS_BINDERS + ".web.server.min:-1}") Double min,
@Value("${" + MICRONAUT_METRICS_BINDERS + ".web.server.max:-1}") Double max,
@Value("${" + MICRONAUT_METRICS_BINDERS + ".web.server.slos:}") Double[] slos
lcavadas marked this conversation as resolved.
Show resolved Hide resolved
) {
return getMeterFilter(percentiles, histogram, min, max, slos, WebMetricsPublisher.METRIC_HTTP_SERVER_REQUESTS);
}

/**
* Configure new MeterFilter for http.client.requests metrics.
*
* @param percentiles The percentiles
* @param histogram If a histogram should be published
* @param min the minimum time (in s) value expected.
* @param max the maximum time (in s) value expected.
* @param slos the user-defined service levels objectives (in s) to create.
* @return A MeterFilter
*/
@Bean
@Singleton
@Requires(property = MICRONAUT_METRICS_BINDERS + ".web.client.percentiles")
MeterFilter addClientPercentileMeterFilter(@Value("${" + MICRONAUT_METRICS_BINDERS + ".web.client.percentiles}") Double[] percentiles) {
return getMeterFilter(percentiles, METRIC_HTTP_CLIENT_REQUESTS);
@Requires(property = MICRONAUT_METRICS_BINDERS + ".web.client")
MeterFilter addClientPercentileMeterFilter(
@Value("${" + MICRONAUT_METRICS_BINDERS + ".web.client.percentiles:}") Double[] percentiles,
@Value("${" + MICRONAUT_METRICS_BINDERS + ".web.client.histogram:false}") Boolean histogram,
@Value("${" + MICRONAUT_METRICS_BINDERS + ".web.client.min:-1}") Double min,
@Value("${" + MICRONAUT_METRICS_BINDERS + ".web.client.max:-1}") Double max,
@Value("${" + MICRONAUT_METRICS_BINDERS + ".web.client.slos:}") Double[] slos
) {
return getMeterFilter(percentiles, histogram, min, max, slos, WebMetricsPublisher.METRIC_HTTP_CLIENT_REQUESTS);
}

private MeterFilter getMeterFilter(Double[] percentiles, String metricNamePrefix) {
private MeterFilter getMeterFilter(Double[] percentiles, Boolean histogram, Double minMs, Double maxMs, Double[] slos, String metricNamePrefix) {
return new MeterFilter() {
@Override
public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) {
if (id.getName().startsWith(metricNamePrefix)) {
return DistributionStatisticConfig.builder()
.percentiles((double[]) ArrayUtils.toPrimitiveArray(percentiles))
.build()
.merge(config);
var builder = DistributionStatisticConfig.builder()
.percentiles()
.percentiles(Arrays.stream(percentiles).filter(Objects::nonNull).mapToDouble(Double::doubleValue).toArray())
.serviceLevelObjectives(Arrays.stream(slos).filter(Objects::nonNull).mapToDouble(d -> d * SECONDS_TO_NANOS).toArray())
.percentilesHistogram(histogram);

if (minMs != -1) {
builder.minimumExpectedValue(minMs * SECONDS_TO_NANOS);
}

if (maxMs != -1) {
builder.maximumExpectedValue(maxMs * SECONDS_TO_NANOS);
}

return builder.build().merge(config);
}
return config;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import groovy.transform.InheritConstructors
import io.micrometer.common.lang.NonNull
import io.micrometer.core.instrument.MeterRegistry
import io.micrometer.core.instrument.Timer
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig
import io.micrometer.core.instrument.distribution.HistogramSnapshot
import io.micrometer.core.instrument.search.MeterNotFoundException
import io.micronaut.context.ApplicationContext
Expand Down Expand Up @@ -49,14 +50,27 @@ class HttpMetricsSpec extends Specification {
Timer clientTimer = registry.get(WebMetricsPublisher.METRIC_HTTP_CLIENT_REQUESTS).tags('uri', '/test-http-metrics').timer()
HistogramSnapshot serverSnapshot = serverTimer.takeSnapshot()
HistogramSnapshot clientSnapshot = clientTimer.takeSnapshot()
DistributionStatisticConfig serverDistributionConfig = serverTimer.getMetaPropertyValues().find { it.name.equals('distributionStatisticConfig') }.value as DistributionStatisticConfig
DistributionStatisticConfig clientDistributionConfig = clientTimer.getMetaPropertyValues().find { it.name.equals('distributionStatisticConfig') }.value as DistributionStatisticConfig

then:
serverTimer.count() == 1
clientTimer.count() == 1

serverSnapshot.percentileValues().length == serverPercentilesCount
clientSnapshot.percentileValues().length == clientPercentilesCount
serverTimer.getMetaPropertyValues().find { it.name.equals('distributionStatisticConfig') }.value.percentileHistogram == serverHistogram
clientTimer.getMetaPropertyValues().find { it.name.equals('distributionStatisticConfig') }.value.percentileHistogram == clientHistogram

serverDistributionConfig.percentileHistogram == serverHistogram
clientDistributionConfig.percentileHistogram == clientHistogram

serverDistributionConfig.getServiceLevelObjectiveBoundaries()?.length == serverSlosCount
clientDistributionConfig.getServiceLevelObjectiveBoundaries()?.length == clientSlosCount

serverDistributionConfig.minimumExpectedValueAsDouble == serverMin
serverDistributionConfig.maximumExpectedValueAsDouble == serverMax

clientDistributionConfig.minimumExpectedValueAsDouble == clientMin
clientDistributionConfig.maximumExpectedValueAsDouble == clientMax

when: "A request is sent to the root route"

Expand Down Expand Up @@ -137,13 +151,21 @@ class HttpMetricsSpec extends Specification {
embeddedServer.close()

where:
cfg | setting | serverPercentilesCount | clientPercentilesCount| serverHistogram | clientHistogram
MICRONAUT_METRICS_BINDERS + ".web.client.percentiles" | "0.95,0.99" | 0 | 2 | null | null
MICRONAUT_METRICS_BINDERS + ".web.server.percentiles" | "0.95,0.99" | 2 | 0 | null | null
MICRONAUT_METRICS_BINDERS + ".web.server.histogram" | "true" | 0 | 0 | true | null
MICRONAUT_METRICS_BINDERS + ".web.server.histogram" | "false" | 0 | 0 | false | null
MICRONAUT_METRICS_BINDERS + ".web.client.histogram" | "true" | 0 | 0 | null | true
MICRONAUT_METRICS_BINDERS + ".web.client.histogram" | "false" | 0 | 0 | null | false
cfg | setting | serverPercentilesCount | clientPercentilesCount | serverSlosCount | clientSlosCount | serverHistogram | clientHistogram | serverMin | serverMax | clientMin | clientMax
// Server
MICRONAUT_METRICS_BINDERS + ".web.server.percentiles" | "0.95,0.99" | 2 | 0 | 0 | null | false | null | 1000000d | 3.0E10 | 1000000d | 3.0E10
MICRONAUT_METRICS_BINDERS + ".web.server.histogram" | "true" | 0 | 0 | 0 | null | true | null | 1000000d | 3.0E10 | 1000000d | 3.0E10
MICRONAUT_METRICS_BINDERS + ".web.server.histogram" | "false" | 0 | 0 | 0 | null | false | null | 1000000d | 3.0E10 | 1000000d | 3.0E10
MICRONAUT_METRICS_BINDERS + ".web.server.min" | 0.1 | 0 | 0 | 0 | null | false | null | 1.0E8 | 3.0E10 | 1000000d | 3.0E10
MICRONAUT_METRICS_BINDERS + ".web.server.max" | 60 | 0 | 0 | 0 | null | false | null | 1000000d | 6.0E10 | 1000000d | 3.0E10
MICRONAUT_METRICS_BINDERS + ".web.server.slos" | "0.1,0.2,0.5" | 0 | 0 | 3 | null | false | null | 1000000d | 3.0E10 | 1000000d | 3.0E10
// Client
MICRONAUT_METRICS_BINDERS + ".web.client.percentiles" | "0.95,0.99" | 0 | 2 | null | 0 | null | false | 1000000d | 3.0E10 | 1000000d | 3.0E10
MICRONAUT_METRICS_BINDERS + ".web.client.histogram" | "true" | 0 | 0 | null | 0 | null | true | 1000000d | 3.0E10 | 1000000d | 3.0E10
MICRONAUT_METRICS_BINDERS + ".web.client.histogram" | "false" | 0 | 0 | null | 0 | null | false | 1000000d | 3.0E10 | 1000000d | 3.0E10
MICRONAUT_METRICS_BINDERS + ".web.client.min" | 0.1 | 0 | 0 | null | 0 | null | false | 1000000d | 3.0E10 | 1.0E8 | 3.0E10
MICRONAUT_METRICS_BINDERS + ".web.client.max" | 60 | 0 | 0 | null | 0 | null | false | 1000000d | 3.0E10 | 1000000d | 6.0E10
MICRONAUT_METRICS_BINDERS + ".web.client.slos" | "0.1,0.2,0.5" | 0 | 0 | null | 3 | null | false | 1000000d | 3.0E10 | 1000000d | 3.0E10
}

void "test client / server metrics ignored uris for client errors"() {
Expand Down
15 changes: 13 additions & 2 deletions src/main/docs/guide/metricsConcepts.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,12 @@ By default, client URIs are captured when an error is handled.
This can cause trouble in some monitoring tools due to a large amount of distinct URIs captured.
This URI capturing can be disabled by setting `micronaut.metrics.binders.web.client-errors-uris.enabled` to `false`.

.Enabling Percentiles
.Customizing

Percentile configurations can be added to HTTP server and client metrics. Percentiles can be provided via a CSV as shown below.
The web metrics can be configured to enable percentiles, histogram, service level objectives and the expected min and max duration of the requests.
All configurations can be added to HTTP server and client metrics. All time configurations are specified in seconds.

A complete example:

[configuration]
----
Expand All @@ -100,8 +103,16 @@ micronaut:
web:
server:
percentiles: "0.95,0.99"
histogram: true
slos: "0.1,0.4,0.5,2"
min: 0.1
max: 60
client:
percentiles: "0.95,0.99"
histogram: true
slos: "0.1,0.4,0.5,2"
min: 0.1
max: 60
----

.Metrics provided
Expand Down
Loading