-
Notifications
You must be signed in to change notification settings - Fork 981
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Jetty HttpClient request metrics (fixes #2019)
- Loading branch information
Jon Schneider
committed
Apr 22, 2020
1 parent
c617a6e
commit b120773
Showing
5 changed files
with
492 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
105 changes: 105 additions & 0 deletions
105
...ter-core/src/main/java/io/micrometer/core/instrument/binder/jetty/JettyClientMetrics.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/** | ||
* Copyright 2020 VMware, Inc. | ||
* <p> | ||
* 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 | ||
* <p> | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* <p> | ||
* 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 io.micrometer.core.instrument.binder.jetty; | ||
|
||
import io.micrometer.core.annotation.Incubating; | ||
import io.micrometer.core.instrument.DistributionSummary; | ||
import io.micrometer.core.instrument.MeterRegistry; | ||
import io.micrometer.core.instrument.Timer; | ||
import io.micrometer.core.instrument.config.MeterFilter; | ||
import org.eclipse.jetty.client.api.Request; | ||
|
||
/** | ||
* Provides request metrics for Jetty {@link org.eclipse.jetty.client.HttpClient}. | ||
* Incubating in case there emerges a better way to handle path variable detection. | ||
* | ||
* @author Jon Schneider | ||
* @since 1.5.0 | ||
*/ | ||
@Incubating(since = "1.5.0") | ||
public class JettyClientMetrics implements Request.Listener { | ||
private final MeterRegistry registry; | ||
private final JettyClientTagsProvider tagsProvider; | ||
private final String timingMetricName; | ||
private final String contentSizeMetricName; | ||
|
||
protected JettyClientMetrics(MeterRegistry registry, JettyClientTagsProvider tagsProvider, String timingMetricName, String contentSizeMetricName, int maxUriTags) { | ||
this.registry = registry; | ||
this.tagsProvider = tagsProvider; | ||
this.timingMetricName = timingMetricName; | ||
this.contentSizeMetricName = contentSizeMetricName; | ||
|
||
registry.config().meterFilter(MeterFilter.maximumAllowableTags(this.timingMetricName, "uri", maxUriTags, MeterFilter.deny())); | ||
} | ||
|
||
@Override | ||
public void onQueued(Request request) { | ||
Timer.Sample sample = Timer.start(registry); | ||
|
||
request.onComplete(result -> { | ||
long requestLength = result.getRequest().getContent().getLength(); | ||
if (requestLength >= 0) { | ||
DistributionSummary.builder(contentSizeMetricName) | ||
.description("Content sizes for Jetty HTTP client requests") | ||
.tags(tagsProvider.httpRequestTags(result)) | ||
.register(registry) | ||
.record(requestLength); | ||
} | ||
|
||
sample.stop(Timer.builder(timingMetricName) | ||
.description("Jetty HTTP client request timing") | ||
.tags(tagsProvider.httpRequestTags(result)) | ||
.register(registry)); | ||
}); | ||
} | ||
|
||
public static Builder builder(MeterRegistry registry, JettyClientTagsProvider tagsProvider) { | ||
return new Builder(registry, tagsProvider); | ||
} | ||
|
||
public static class Builder { | ||
private final MeterRegistry registry; | ||
private final JettyClientTagsProvider tagsProvider; | ||
|
||
private String timingMetricName = "jetty.client.requests"; | ||
private String contentSizeMetricName = "jetty.client.request.size"; | ||
private int maxUriTags = 1000; | ||
|
||
Builder(MeterRegistry registry, JettyClientTagsProvider tagsProvider) { | ||
this.registry = registry; | ||
this.tagsProvider = tagsProvider; | ||
} | ||
|
||
public Builder timingMetricName(String metricName) { | ||
this.timingMetricName = metricName; | ||
return this; | ||
} | ||
|
||
public Builder contentSizeMetricName(String metricName) { | ||
this.contentSizeMetricName = metricName; | ||
return this; | ||
} | ||
|
||
public Builder maxUriTags(int maxUriTags) { | ||
this.maxUriTags = maxUriTags; | ||
return this; | ||
} | ||
|
||
public JettyClientMetrics build() { | ||
return new JettyClientMetrics(registry, tagsProvider, timingMetricName, contentSizeMetricName, maxUriTags); | ||
} | ||
} | ||
} |
169 changes: 169 additions & 0 deletions
169
...ometer-core/src/main/java/io/micrometer/core/instrument/binder/jetty/JettyClientTags.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
/** | ||
* Copyright 2020 VMware, Inc. | ||
* <p> | ||
* 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 | ||
* <p> | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* <p> | ||
* 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 io.micrometer.core.instrument.binder.jetty; | ||
|
||
import io.micrometer.core.instrument.Tag; | ||
import io.micrometer.core.instrument.util.StringUtils; | ||
import org.eclipse.jetty.client.api.Request; | ||
import org.eclipse.jetty.client.api.Response; | ||
import org.eclipse.jetty.client.api.Result; | ||
import org.eclipse.jetty.http.HttpStatus; | ||
|
||
import java.util.function.Function; | ||
import java.util.regex.Pattern; | ||
|
||
/** | ||
* Factory methods for {@link Tag Tags} associated with a request-response exchange that | ||
* is handled by Jetty {@link org.eclipse.jetty.client.HttpClient}. | ||
* | ||
* @author Jon Schneider | ||
* @since 1.5.0 | ||
*/ | ||
public final class JettyClientTags { | ||
|
||
private static final Tag URI_NOT_FOUND = Tag.of("uri", "NOT_FOUND"); | ||
|
||
private static final Tag URI_REDIRECTION = Tag.of("uri", "REDIRECTION"); | ||
|
||
private static final Tag URI_ROOT = Tag.of("uri", "root"); | ||
|
||
private static final Tag EXCEPTION_NONE = Tag.of("exception", "None"); | ||
|
||
private static final Tag STATUS_SERVER_ERROR = Tag.of("status", String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR_500)); | ||
|
||
private static final Tag OUTCOME_UNKNOWN = Tag.of("outcome", "UNKNOWN"); | ||
|
||
private static final Tag OUTCOME_INFORMATIONAL = Tag.of("outcome", "INFORMATIONAL"); | ||
|
||
private static final Tag OUTCOME_SUCCESS = Tag.of("outcome", "SUCCESS"); | ||
|
||
private static final Tag OUTCOME_REDIRECTION = Tag.of("outcome", "REDIRECTION"); | ||
|
||
private static final Tag OUTCOME_CLIENT_ERROR = Tag.of("outcome", "CLIENT_ERROR"); | ||
|
||
private static final Tag OUTCOME_SERVER_ERROR = Tag.of("outcome", "SERVER_ERROR"); | ||
|
||
private static final Tag METHOD_UNKNOWN = Tag.of("method", "UNKNOWN"); | ||
|
||
private static final Pattern TRAILING_SLASH_PATTERN = Pattern.compile("/$"); | ||
|
||
private static final Pattern MULTIPLE_SLASH_PATTERN = Pattern.compile("//+"); | ||
|
||
private JettyClientTags() { | ||
} | ||
|
||
/** | ||
* Creates a {@code method} tag based on the {@link Request#getMethod() | ||
* method} of the given {@code request}. | ||
* | ||
* @param request the container request | ||
* @return the method tag whose value is a capitalized method (e.g. GET). | ||
*/ | ||
public static Tag method(Request request) { | ||
return (request != null) ? Tag.of("method", request.getMethod()) : METHOD_UNKNOWN; | ||
} | ||
|
||
/** | ||
* Creates a {@code status} tag based on the status of the given {@code result}. | ||
* | ||
* @param result the request result | ||
* @return the status tag derived from the status of the response | ||
*/ | ||
public static Tag status(Result result) { | ||
return Tag.of("status", Integer.toString(result.getResponse().getStatus())); | ||
} | ||
|
||
/** | ||
* Creates a {@code uri} tag based on the URI of the given {@code result}. | ||
* {@code REDIRECTION} for 3xx responses, {@code NOT_FOUND} for 404 responses. | ||
* | ||
* @param result the request result | ||
* @return the uri tag derived from the request result | ||
*/ | ||
public static Tag uri(Result result, Function<Result, String> successfulUriPattern) { | ||
Response response = result.getResponse(); | ||
if (response != null) { | ||
int status = response.getStatus(); | ||
if (HttpStatus.isRedirection(status)) { | ||
return URI_REDIRECTION; | ||
} | ||
if (status == 404) { | ||
return URI_NOT_FOUND; | ||
} | ||
} | ||
|
||
String matchingPattern = successfulUriPattern.apply(result); | ||
if (matchingPattern.equals("/")) { | ||
return URI_ROOT; | ||
} | ||
|
||
matchingPattern = MULTIPLE_SLASH_PATTERN.matcher(matchingPattern).replaceAll("/"); | ||
if (!matchingPattern.equals("/")) { | ||
matchingPattern = TRAILING_SLASH_PATTERN.matcher(matchingPattern).replaceAll(""); | ||
} | ||
|
||
return Tag.of("uri", matchingPattern); | ||
} | ||
|
||
/** | ||
* Creates a {@code exception} tag based on the {@link Class#getSimpleName() simple | ||
* name} of the class of the given {@code exception}. | ||
* | ||
* @param result the request result | ||
* @return the exception tag derived from the exception | ||
*/ | ||
public static Tag exception(Result result) { | ||
Throwable exception = result.getFailure(); | ||
if (exception == null) { | ||
return EXCEPTION_NONE; | ||
} | ||
if (result.getResponse() != null) { | ||
int status = result.getResponse().getStatus(); | ||
if (status == 404 || HttpStatus.isRedirection(status)) { | ||
return EXCEPTION_NONE; | ||
} | ||
} | ||
if (exception.getCause() != null) { | ||
exception = exception.getCause(); | ||
} | ||
String simpleName = exception.getClass().getSimpleName(); | ||
return Tag.of("exception", StringUtils.isNotEmpty(simpleName) ? simpleName | ||
: exception.getClass().getName()); | ||
} | ||
|
||
/** | ||
* Creates an {@code outcome} tag based on the status of the given {@code result}. | ||
* | ||
* @param result the request result | ||
* @return the outcome tag derived from the status of the response | ||
*/ | ||
public static Tag outcome(Result result) { | ||
int status = result.getResponse().getStatus(); | ||
if (HttpStatus.isInformational(status)) { | ||
return OUTCOME_INFORMATIONAL; | ||
} else if (HttpStatus.isSuccess(status)) { | ||
return OUTCOME_SUCCESS; | ||
} else if (HttpStatus.isRedirection(status)) { | ||
return OUTCOME_REDIRECTION; | ||
} else if (HttpStatus.isClientError(status)) { | ||
return OUTCOME_CLIENT_ERROR; | ||
} else if (HttpStatus.isServerError(status)) { | ||
return OUTCOME_SERVER_ERROR; | ||
} | ||
return OUTCOME_UNKNOWN; | ||
} | ||
|
||
} |
53 changes: 53 additions & 0 deletions
53
...ore/src/main/java/io/micrometer/core/instrument/binder/jetty/JettyClientTagsProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/** | ||
* Copyright 2020 VMware, Inc. | ||
* <p> | ||
* 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 | ||
* <p> | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* <p> | ||
* 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 io.micrometer.core.instrument.binder.jetty; | ||
|
||
import io.micrometer.core.annotation.Incubating; | ||
import io.micrometer.core.instrument.Tag; | ||
import io.micrometer.core.instrument.Tags; | ||
import org.eclipse.jetty.client.api.Result; | ||
|
||
/** | ||
* Provides {@link Tag Tags} for Jetty {@link org.eclipse.jetty.client.HttpClient} request metrics. | ||
* Incubating in case there emerges a better way to handle path variable detection. | ||
* | ||
* @author Jon Schneider | ||
* @since 1.5.0 | ||
*/ | ||
@Incubating(since = "1.5.0") | ||
public interface JettyClientTagsProvider { | ||
|
||
/** | ||
* Provides tags to be associated with metrics for the given client request. | ||
* | ||
* @param result the request result | ||
* @return tags to associate with metrics recorded for the request | ||
*/ | ||
default Iterable<Tag> httpRequestTags(Result result) { | ||
return Tags.of(JettyClientTags.method(result.getRequest()), JettyClientTags.uri(result, this::uriPattern), | ||
JettyClientTags.exception(result), JettyClientTags.status(result), | ||
JettyClientTags.outcome(result)); | ||
} | ||
|
||
/** | ||
* For client metric to be usefully aggregable, we must be able to time everything that goes to a certain | ||
* endpoint, regardless of the parameters to that endpoint. | ||
* | ||
* @param result The result which also contains the original request. | ||
* @return A URI pattern with path variables and query parameter unsubstituted. | ||
*/ | ||
String uriPattern(Result result); | ||
} |
Oops, something went wrong.