mirror of
https://github.com/jlengrand/quarkus.git
synced 2026-03-10 08:41:22 +00:00
JAX-RS metrics
This commit is contained in:
@@ -34,6 +34,10 @@
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-resteasy-server-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-undertow-spi</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -16,6 +16,8 @@ import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
|
||||
import org.jboss.jandex.AnnotationInstance;
|
||||
import org.jboss.jandex.AnnotationTarget;
|
||||
import org.jboss.jandex.AnnotationTarget.Kind;
|
||||
@@ -51,6 +53,7 @@ import io.quarkus.arc.processor.AnnotationsTransformer;
|
||||
import io.quarkus.arc.processor.BuiltinScope;
|
||||
import io.quarkus.arc.processor.DotNames;
|
||||
import io.quarkus.arc.processor.Transformation;
|
||||
import io.quarkus.deployment.Capabilities;
|
||||
import io.quarkus.deployment.annotations.BuildProducer;
|
||||
import io.quarkus.deployment.annotations.BuildStep;
|
||||
import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem;
|
||||
@@ -66,11 +69,13 @@ import io.quarkus.resteasy.common.deployment.JaxrsProvidersToRegisterBuildItem;
|
||||
import io.quarkus.resteasy.common.deployment.ResteasyCommonProcessor.ResteasyCommonConfig;
|
||||
import io.quarkus.resteasy.common.deployment.ResteasyDotNames;
|
||||
import io.quarkus.resteasy.common.runtime.QuarkusInjectorFactory;
|
||||
import io.quarkus.resteasy.common.spi.ResteasyJaxrsProviderBuildItem;
|
||||
import io.quarkus.resteasy.server.common.spi.AdditionalJaxRsResourceDefiningAnnotationBuildItem;
|
||||
import io.quarkus.resteasy.server.common.spi.AdditionalJaxRsResourceMethodAnnotationsBuildItem;
|
||||
import io.quarkus.resteasy.server.common.spi.AdditionalJaxRsResourceMethodParamAnnotations;
|
||||
import io.quarkus.runtime.annotations.ConfigItem;
|
||||
import io.quarkus.runtime.annotations.ConfigRoot;
|
||||
import io.quarkus.undertow.deployment.FilterBuildItem;
|
||||
|
||||
/**
|
||||
* Processor that builds the RESTEasy server configuration.
|
||||
@@ -135,6 +140,13 @@ public class ResteasyServerCommonProcessor {
|
||||
*/
|
||||
@ConfigItem(defaultValue = "/")
|
||||
String path;
|
||||
|
||||
/**
|
||||
* Whether or not JAX-RS metrics should be enabled if the Metrics capability is present and Vert.x is being used.
|
||||
*/
|
||||
@ConfigItem(name = "metrics.enabled", defaultValue = "false")
|
||||
public boolean metricsEnabled;
|
||||
|
||||
}
|
||||
|
||||
@BuildStep
|
||||
@@ -412,6 +424,33 @@ public class ResteasyServerCommonProcessor {
|
||||
BuiltinScope.SINGLETON.getName()));
|
||||
}
|
||||
|
||||
@BuildStep
|
||||
void enableMetrics(ResteasyConfig buildConfig,
|
||||
BuildProducer<ResteasyJaxrsProviderBuildItem> jaxRsProviders,
|
||||
BuildProducer<FilterBuildItem> servletFilters,
|
||||
Capabilities capabilities) {
|
||||
if (buildConfig.metricsEnabled && capabilities.isCapabilityPresent(Capabilities.METRICS)) {
|
||||
if (capabilities.isCapabilityPresent(Capabilities.SERVLET)) {
|
||||
// if running with servlet, use the MetricsFilter implementation from SmallRye
|
||||
jaxRsProviders.produce(
|
||||
new ResteasyJaxrsProviderBuildItem("io.smallrye.metrics.jaxrs.JaxRsMetricsFilter"));
|
||||
servletFilters.produce(
|
||||
FilterBuildItem.builder("metricsFilter", "io.smallrye.metrics.jaxrs.JaxRsMetricsServletFilter")
|
||||
.setAsyncSupported(true)
|
||||
.addFilterUrlMapping("*", DispatcherType.FORWARD)
|
||||
.addFilterUrlMapping("*", DispatcherType.INCLUDE)
|
||||
.addFilterUrlMapping("*", DispatcherType.REQUEST)
|
||||
.addFilterUrlMapping("*", DispatcherType.ASYNC)
|
||||
.addFilterUrlMapping("*", DispatcherType.ERROR)
|
||||
.build());
|
||||
} else {
|
||||
// if running with vert.x, use the MetricsFilter implementation from Quarkus codebase
|
||||
jaxRsProviders.produce(
|
||||
new ResteasyJaxrsProviderBuildItem("io.quarkus.smallrye.metrics.runtime.QuarkusJaxRsMetricsFilter"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasAutoInjectAnnotation(Set<DotName> autoInjectAnnotationNames, ClassInfo clazz) {
|
||||
for (DotName name : autoInjectAnnotationNames) {
|
||||
List<AnnotationInstance> instances = clazz.annotations().get(name);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.quarkus.smallrye.metrics.deployment;
|
||||
|
||||
import static io.restassured.RestAssured.when;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.GET;
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package io.quarkus.smallrye.metrics.jaxrs;
|
||||
|
||||
import static io.restassured.RestAssured.when;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.eclipse.microprofile.metrics.MetricRegistry;
|
||||
import org.eclipse.microprofile.metrics.SimpleTimer;
|
||||
import org.eclipse.microprofile.metrics.Tag;
|
||||
import org.eclipse.microprofile.metrics.annotation.RegistryType;
|
||||
import org.jboss.shrinkwrap.api.ShrinkWrap;
|
||||
import org.jboss.shrinkwrap.api.asset.StringAsset;
|
||||
import org.jboss.shrinkwrap.api.spec.JavaArchive;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
import io.quarkus.test.QuarkusUnitTest;
|
||||
|
||||
public class JaxRsMetricsTestCase {
|
||||
|
||||
final String METRIC_RESOURCE_CLASS_NAME = MetricsResource.class.getName();
|
||||
|
||||
@RegisterExtension
|
||||
static QuarkusUnitTest TEST = new QuarkusUnitTest()
|
||||
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
|
||||
.addAsResource(new StringAsset("quarkus.resteasy.metrics.enabled=true"),
|
||||
"application.properties")
|
||||
.addClasses(MetricsResource.class));
|
||||
|
||||
@Inject
|
||||
@RegistryType(type = MetricRegistry.Type.BASE)
|
||||
MetricRegistry metricRegistry;
|
||||
|
||||
@Test
|
||||
public void testBasic() {
|
||||
when()
|
||||
.get("/hello/joe")
|
||||
.then()
|
||||
.statusCode(200);
|
||||
SimpleTimer metric = metricRegistry.simpleTimer("REST.request",
|
||||
new Tag("class", METRIC_RESOURCE_CLASS_NAME),
|
||||
new Tag("method", "hello_java.lang.String"));
|
||||
assertEquals(1, metric.getCount());
|
||||
assertTrue(metric.getElapsedTime().toNanos() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodReturningServerError() throws InterruptedException {
|
||||
when()
|
||||
.get("/error")
|
||||
.then()
|
||||
.statusCode(500);
|
||||
SimpleTimer metric = metricRegistry.simpleTimer("REST.request",
|
||||
new Tag("class", METRIC_RESOURCE_CLASS_NAME),
|
||||
new Tag("method", "error"));
|
||||
assertEquals(1, metric.getCount());
|
||||
assertTrue(metric.getElapsedTime().toNanos() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodThrowingException() {
|
||||
when()
|
||||
.get("/exception")
|
||||
.then()
|
||||
.statusCode(500);
|
||||
SimpleTimer metric = metricRegistry.simpleTimer("REST.request",
|
||||
new Tag("class", METRIC_RESOURCE_CLASS_NAME),
|
||||
new Tag("method", "exception"));
|
||||
assertEquals(1, metric.getCount());
|
||||
assertTrue(metric.getElapsedTime().toNanos() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodTakingList() {
|
||||
when()
|
||||
.get("/a/b/c/list")
|
||||
.then()
|
||||
.statusCode(200);
|
||||
SimpleTimer metric = metricRegistry.simpleTimer("REST.request",
|
||||
new Tag("class", METRIC_RESOURCE_CLASS_NAME),
|
||||
new Tag("method", "list_java.util.List"));
|
||||
assertEquals(1, metric.getCount());
|
||||
assertTrue(metric.getElapsedTime().toNanos() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodTakingArray() {
|
||||
when()
|
||||
.get("/a/b/c/array")
|
||||
.then()
|
||||
.statusCode(200);
|
||||
SimpleTimer metric = metricRegistry.simpleTimer("REST.request",
|
||||
new Tag("class", METRIC_RESOURCE_CLASS_NAME),
|
||||
new Tag("method", "array_javax.ws.rs.core.PathSegment[]"));
|
||||
assertEquals(1, metric.getCount());
|
||||
assertTrue(metric.getElapsedTime().toNanos() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodTakingVarargs() {
|
||||
when()
|
||||
.get("/a/b/c/varargs")
|
||||
.then()
|
||||
.statusCode(200);
|
||||
SimpleTimer metric = metricRegistry.simpleTimer("REST.request",
|
||||
new Tag("class", METRIC_RESOURCE_CLASS_NAME),
|
||||
new Tag("method", "varargs_javax.ws.rs.core.PathSegment[]"));
|
||||
assertEquals(1, metric.getCount());
|
||||
assertTrue(metric.getElapsedTime().toNanos() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncMethod() {
|
||||
when()
|
||||
.get("/async")
|
||||
.then()
|
||||
.statusCode(200);
|
||||
SimpleTimer metric = metricRegistry.simpleTimer("REST.request",
|
||||
new Tag("class", METRIC_RESOURCE_CLASS_NAME),
|
||||
new Tag("method", "async"));
|
||||
assertEquals(1, metric.getCount());
|
||||
assertTrue(metric.getElapsedTime().toNanos() > 0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package io.quarkus.smallrye.metrics.jaxrs;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.core.PathSegment;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
@Path("/")
|
||||
public class MetricsResource {
|
||||
|
||||
@Path("/hello/{name}")
|
||||
@GET
|
||||
public String hello(@PathParam("name") String name) {
|
||||
return "hello " + name;
|
||||
}
|
||||
|
||||
@Path("/error")
|
||||
@GET
|
||||
public Response error() {
|
||||
return Response.serverError().build();
|
||||
}
|
||||
|
||||
@Path("/exception")
|
||||
@GET
|
||||
public Long exception() {
|
||||
throw new RuntimeException("!!!");
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{segment}/{other}/{segment}/list")
|
||||
public Response list(@PathParam("segment") List<PathSegment> segments) {
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{segment}/{other}/{segment}/array")
|
||||
public Response array(@PathParam("segment") PathSegment[] segments) {
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{segment}/{other}/{segment}/varargs")
|
||||
public Response varargs(@PathParam("segment") PathSegment... segments) {
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@Path("/async")
|
||||
@GET
|
||||
public CompletionStage<String> async() {
|
||||
return CompletableFuture.supplyAsync(() -> "Hello");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,6 +39,11 @@
|
||||
<groupId>org.eclipse.microprofile.metrics</groupId>
|
||||
<artifactId>microprofile-metrics-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-resteasy</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2019 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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.quarkus.smallrye.metrics.runtime;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.enterprise.inject.spi.CDI;
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
import javax.ws.rs.container.ContainerRequestFilter;
|
||||
import javax.ws.rs.container.ResourceInfo;
|
||||
import javax.ws.rs.core.Context;
|
||||
|
||||
import org.eclipse.microprofile.metrics.Metadata;
|
||||
import org.eclipse.microprofile.metrics.MetricID;
|
||||
import org.eclipse.microprofile.metrics.MetricRegistry;
|
||||
import org.eclipse.microprofile.metrics.MetricUnits;
|
||||
import org.eclipse.microprofile.metrics.Tag;
|
||||
|
||||
import io.quarkus.vertx.http.runtime.CurrentVertxRequest;
|
||||
import io.smallrye.metrics.MetricRegistries;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
|
||||
/**
|
||||
* A JAX-RS filter that computes the REST.request metrics from REST traffic over time.
|
||||
* This one depends on Vert.x to be able to hook into response even in cases when the request ended with an unmapped exception.
|
||||
*/
|
||||
public class QuarkusJaxRsMetricsFilter implements ContainerRequestFilter {
|
||||
|
||||
@Context
|
||||
ResourceInfo resourceInfo;
|
||||
|
||||
@Override
|
||||
public void filter(final ContainerRequestContext requestContext) {
|
||||
Long start = System.nanoTime();
|
||||
final Class<?> resourceClass = resourceInfo.getResourceClass();
|
||||
final Method resourceMethod = resourceInfo.getResourceMethod();
|
||||
/*
|
||||
* The reason for using a Vert.x handler instead of ContainerResponseFilter is that
|
||||
* RESTEasy does not call the response filter for requests that ended up with an unmapped exception.
|
||||
* This way we can capture these responses as well and update the metrics accordingly.
|
||||
*/
|
||||
RoutingContext routingContext = CDI.current().select(CurrentVertxRequest.class).get().getCurrent();
|
||||
routingContext.addBodyEndHandler(
|
||||
event -> finishRequest(start, resourceClass, resourceMethod));
|
||||
}
|
||||
|
||||
private void finishRequest(Long start, Class<?> resourceClass, Method resourceMethod) {
|
||||
long value = System.nanoTime() - start;
|
||||
MetricID metricID = getMetricID(resourceClass, resourceMethod);
|
||||
|
||||
MetricRegistry registry = MetricRegistries.get(MetricRegistry.Type.BASE);
|
||||
if (!registry.getMetadata().containsKey(metricID.getName())) {
|
||||
// if no metric with this name exists yet, register it
|
||||
Metadata metadata = Metadata.builder()
|
||||
.withName(metricID.getName())
|
||||
.withDescription(
|
||||
"The number of invocations and total response time of this RESTful resource method since the start of the server.")
|
||||
.withUnit(MetricUnits.NANOSECONDS)
|
||||
.build();
|
||||
registry.simpleTimer(metadata, metricID.getTagsAsArray());
|
||||
}
|
||||
registry.simpleTimer(metricID.getName(), metricID.getTagsAsArray())
|
||||
.update(Duration.ofNanos(value));
|
||||
}
|
||||
|
||||
private MetricID getMetricID(Class<?> resourceClass, Method resourceMethod) {
|
||||
Tag classTag = new Tag("class", resourceClass.getName());
|
||||
String methodName = resourceMethod.getName();
|
||||
String encodedParameterNames = Arrays.stream(resourceMethod.getParameterTypes())
|
||||
.map(clazz -> {
|
||||
if (clazz.isArray()) {
|
||||
return clazz.getComponentType().getName() + "[]";
|
||||
} else {
|
||||
return clazz.getName();
|
||||
}
|
||||
})
|
||||
.collect(Collectors.joining("_"));
|
||||
String methodTagValue = encodedParameterNames.isEmpty() ? methodName : methodName + "_" + encodedParameterNames;
|
||||
Tag methodTag = new Tag("method", methodTagValue);
|
||||
return new MetricID("REST.request", classTag, methodTag);
|
||||
}
|
||||
|
||||
}
|
||||
74
tcks/microprofile-metrics/optional/pom.xml
Normal file
74
tcks/microprofile-metrics/optional/pom.xml
Normal file
@@ -0,0 +1,74 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>quarkus-tck-microprofile-metrics-parent</artifactId>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<version>999-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>quarkus-tck-microprofile-metrics-optional</artifactId>
|
||||
<name>Quarkus - TCK - MicroProfile Metrics Optional tests</name>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<!-- Disable quarkus optimization -->
|
||||
<quarkus.arc.remove-unused-beans>false</quarkus.arc.remove-unused-beans>
|
||||
<quarkus.resteasy.metrics.enabled>true</quarkus.resteasy.metrics.enabled>
|
||||
<context.root/>
|
||||
</systemPropertyVariables>
|
||||
<!-- This workaround allows us to run a single test using
|
||||
the "test" system property -->
|
||||
<!-- https://issues.apache.org/jira/browse/SUREFIRE-569 -->
|
||||
<dependenciesToScan>
|
||||
<dependency>org.eclipse.microprofile.metrics:microprofile-metrics-optional-tck</dependency>
|
||||
</dependenciesToScan>
|
||||
<environmentVariables>
|
||||
<MP_METRICS_TAGS>tier=integration</MP_METRICS_TAGS>
|
||||
</environmentVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-arquillian</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-smallrye-metrics</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.microprofile.metrics</groupId>
|
||||
<artifactId>microprofile-metrics-optional-tck</artifactId>
|
||||
<version>${microprofile-metrics-api.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.jboss.shrinkwrap.resolver</groupId>
|
||||
<artifactId>shrinkwrap-resolver-impl-maven</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>jakarta.xml.bind</groupId>
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.xml.bind</groupId>
|
||||
<artifactId>jboss-jaxb-api_2.3_spec</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-resteasy</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -16,6 +16,7 @@
|
||||
<modules>
|
||||
<module>api</module>
|
||||
<module>rest</module>
|
||||
<module>optional</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
|
||||
Reference in New Issue
Block a user