Skip to content

Commit

Permalink
Add HttpServer#metrics(boolean, Function<String, String>, Function<St…
Browse files Browse the repository at this point in the history
…ring, String>)

Add HttpServer#metrics(boolean, Supplier<? extends ChannelMetricsRecorder>, Function<String, String>, Function<String, String>)
  • Loading branch information
violetagg committed Nov 13, 2023
1 parent aebdd91 commit e4a486b
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,13 @@ abstract class AbstractHttpServerMetricsHandler extends ChannelDuplexHandler {

long dataSentTime;

final Function<String, String> methodTagValue;
final Function<String, String> uriTagValue;

protected AbstractHttpServerMetricsHandler(@Nullable Function<String, String> uriTagValue) {
protected AbstractHttpServerMetricsHandler(
@Nullable Function<String, String> methodTagValue,
@Nullable Function<String, String> uriTagValue) {
this.methodTagValue = methodTagValue == null ? Function.identity() : methodTagValue;
this.uriTagValue = uriTagValue;
}

Expand All @@ -72,6 +76,7 @@ protected AbstractHttpServerMetricsHandler(AbstractHttpServerMetricsHandler copy
this.dataReceivedTime = copy.dataReceivedTime;
this.dataSent = copy.dataSent;
this.dataSentTime = copy.dataSentTime;
this.methodTagValue = copy.methodTagValue;
this.uriTagValue = copy.uriTagValue;
}

Expand Down Expand Up @@ -140,7 +145,7 @@ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
HttpServerOperations ops = (HttpServerOperations) channelOps;
try {
recordWrite(ops, uriTagValue == null ? ops.path : uriTagValue.apply(ops.path),
ops.method().name(), ops.status().codeAsText().toString());
methodTagValue.apply(ops.method().name()), ops.status().codeAsText().toString());
}
catch (RuntimeException e) {
// Allow request-response exchange to continue, unaffected by metrics problem
Expand Down Expand Up @@ -191,7 +196,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) {
ChannelOperations<?, ?> channelOps = ChannelOperations.get(ctx.channel());
if (channelOps instanceof HttpServerOperations) {
HttpServerOperations ops = (HttpServerOperations) channelOps;
recordRead(ops, uriTagValue == null ? ops.path : uriTagValue.apply(ops.path), ops.method().name());
recordRead(ops, uriTagValue == null ? ops.path : uriTagValue.apply(ops.path), methodTagValue.apply(ops.method().name()));
}

dataReceived = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2022 VMware, Inc. or its affiliates, All Rights Reserved.
* Copyright (c) 2021-2023 VMware, Inc. or its affiliates, All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -29,9 +29,11 @@ final class ContextAwareHttpServerMetricsHandler extends AbstractHttpServerMetri

final ContextAwareHttpServerMetricsRecorder recorder;

ContextAwareHttpServerMetricsHandler(ContextAwareHttpServerMetricsRecorder recorder,
ContextAwareHttpServerMetricsHandler(
ContextAwareHttpServerMetricsRecorder recorder,
@Nullable Function<String, String> methodTagValue,
@Nullable Function<String, String> uriTagValue) {
super(uriTagValue);
super(methodTagValue, uriTagValue);
this.recorder = recorder;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,73 @@ else if (configuration().metricsRecorder() != null) {
}
}

/**
* Whether to enable metrics to be collected and registered in Micrometer's
* {@link io.micrometer.core.instrument.Metrics#globalRegistry globalRegistry}
* under the name {@link reactor.netty.Metrics#HTTP_SERVER_PREFIX}.
* <p>{@code uriTagValue} function receives the actual uri and returns the uri tag value
* that will be used for the metrics with {@link reactor.netty.Metrics#URI} tag.
* For example instead of using the actual uri {@code "/users/1"} as uri tag value, templated uri
* {@code "/users/{id}"} can be used.
* <p><strong>Note:</strong>
* It is strongly recommended to provide template-like form for the URIs. Without a conversion to a template-like form,
* each distinct URI leads to the creation of a distinct tag, which takes a lot of memory for the metrics.
* <p><strong>Note:</strong>
* It is strongly recommended applications to configure an upper limit for the number of the URI tags.
* For example:
* <pre class="code">
* Metrics.globalRegistry
* .config()
* .meterFilter(MeterFilter.maximumAllowableTags(HTTP_SERVER_PREFIX, URI, 100, MeterFilter.deny()));
* </pre>
* <p>{@code methodTagValue} function receives the actual method name and returns the method tag value
* that will be used for the metrics with {@link reactor.netty.Metrics#METHOD} tag.
* <p>By default metrics are not enabled.
*
* @param enable true enables metrics collection; false disables it
* @param uriTagValue a function that receives the actual uri and returns the uri tag value
* that will be used for the metrics with {@link reactor.netty.Metrics#URI} tag
* @param methodTagValue a function that receives the actual method name and returns the method tag value
* that will be used for the metrics with {@link reactor.netty.Metrics#METHOD} tag
* @return a new {@link HttpServer}
* @since 1.0.39
*/
public final HttpServer metrics(boolean enable, Function<String, String> uriTagValue, Function<String, String> methodTagValue) {
if (enable) {
Objects.requireNonNull(methodTagValue, "methodTagValue");
if (!Metrics.isInstrumentationAvailable()) {
throw new UnsupportedOperationException(
"To enable metrics, you must add the dependency `io.micrometer:micrometer-core`" +
" to the class path first");
}
if (uriTagValue == Function.<String>identity()) {
log.debug("Metrics are enabled with [uriTagValue=Function#identity]. " +
"It is strongly recommended to provide template-like form for the URIs. " +
"Without a conversion to a template-like form, each distinct URI leads " +
"to the creation of a distinct tag, which takes a lot of memory for the metrics.");
}
if (methodTagValue == Function.<String>identity()) {
log.debug("Metrics are enabled with [methodTagValue=Function#identity]. " +
"It is strongly recommended to provide a function for transforming the method names.");
}
HttpServer dup = duplicate();
dup.configuration().metricsRecorder(() -> configuration().defaultMetricsRecorder());
dup.configuration().methodTagValue = methodTagValue;
dup.configuration().uriTagValue = uriTagValue;
return dup;
}
else if (configuration().metricsRecorder() != null) {
HttpServer dup = duplicate();
dup.configuration().metricsRecorder(null);
dup.configuration().methodTagValue = null;
dup.configuration().uriTagValue = null;
return dup;
}
else {
return this;
}
}

@Override
public final HttpServer metrics(boolean enable, Supplier<? extends ChannelMetricsRecorder> recorder) {
return super.metrics(enable, recorder);
Expand Down Expand Up @@ -663,6 +730,49 @@ else if (configuration().metricsRecorder() != null) {
}
}

/**
* Specifies whether the metrics are enabled on the {@link HttpServer}.
* All generated metrics are provided to the specified recorder which is only
* instantiated if metrics are being enabled (the instantiation is not lazy,
* but happens immediately, while configuring the {@link HttpServer}).
* <p>{@code uriValue} function receives the actual uri and returns the uri value
* that will be used when the metrics are propagated to the recorder.
* For example instead of using the actual uri {@code "/users/1"} as uri value, templated uri
* {@code "/users/{id}"} can be used.
* <p>{@code methodValue} function receives the actual method name and returns the method value
* that will be used when the metrics are propagated to the recorder.
*
* @param enable true enables metrics collection; false disables it
* @param recorder a supplier for the metrics recorder that receives the collected metrics
* @param uriValue a function that receives the actual uri and returns the uri value
* that will be used when the metrics are propagated to the recorder.
* @param methodValue a function that receives the actual method name and returns the method value
* that will be used when the metrics are propagated to the recorder.
* @return a new {@link HttpServer}
* @since 1.0.39
*/
public final HttpServer metrics(boolean enable, Supplier<? extends ChannelMetricsRecorder> recorder,
Function<String, String> uriValue, Function<String, String> methodValue) {
if (enable) {
Objects.requireNonNull(methodValue, "methodTagValue");
HttpServer dup = duplicate();
dup.configuration().metricsRecorder(recorder);
dup.configuration().methodTagValue = methodValue;
dup.configuration().uriTagValue = uriValue;
return dup;
}
else if (configuration().metricsRecorder() != null) {
HttpServer dup = duplicate();
dup.configuration().metricsRecorder(null);
dup.configuration().methodTagValue = null;
dup.configuration().uriTagValue = null;
return dup;
}
else {
return this;
}
}

/**
* Removes any previously applied SSL configuration customization.
*
Expand Down

0 comments on commit e4a486b

Please sign in to comment.