Skip to content

Commit

Permalink
Add ResponseMeteredLevel
Browse files Browse the repository at this point in the history
  • Loading branch information
dennyac committed Dec 22, 2022
1 parent 1f56ed5 commit b53546a
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 106 deletions.
4 changes: 2 additions & 2 deletions docs/source/manual/jersey.rst
Expand Up @@ -56,7 +56,7 @@ application's ``ResourceConfig`` as a singleton provider for this to work.
}
@GET
@ResponseMetered
@ResponseMetered(level = ResponseMeteredLevel.ALL)
@Path("/response-metered")
public Response responseMetered(@QueryParam("invalid") @DefaultValue("false") boolean invalid) {
if (invalid) {
Expand All @@ -74,6 +74,6 @@ If the annotation is placed on the class, it will apply to all its resource meth

* ``@Timed`` adds a timer and measures time spent in that method.
* ``@Metered`` adds a meter and measures the rate at which the resource method is accessed.
* ``@ResponseMetered`` adds a meter and measures rate for each class of response codes (1xx/2xx/3xx/4xx/5xx).
* ``@ResponseMetered`` adds meters and measures rate for response codes based on the selected level.
* ``@ExceptionMetered`` adds a meter and measures how often the specified exception occurs when processing the resource.
If the ``cause`` is not specified, the default is ``Exception.class``.
Expand Up @@ -12,14 +12,14 @@
* <p>
* Given a method like this:
* <pre><code>
* {@literal @}ResponseMetered(name = "fancyName")
* {@literal @}ResponseMetered(name = "fancyName", level = ResponseMeteredLevel.ALL)
* public String fancyName(String name) {
* return "Sir Captain " + name;
* }
* </code></pre>
* <p>
* A meter for the defining class with the name {@code fancyName} will be created for every response code
* in addition to 1xx/2xx/3xx/4xx/5xx responses. Each time the {@code #fancyName(String)} method is invoked,
* Meters for the defining class with the name {@code fancyName} will be created for response codes
* based on the ResponseMeteredLevel selected. Each time the {@code #fancyName(String)} method is invoked,
* the appropriate response meter will be marked.
*/
@Inherited
Expand All @@ -37,4 +37,9 @@
* relative to the annotated class. When annotating a class, this must be {@code false}.
*/
boolean absolute() default false;

/**
* @return the ResponseMeteredLevel which decides which response code meters are marked.
*/
ResponseMeteredLevel level() default ResponseMeteredLevel.COARSE;
}
Expand Up @@ -9,6 +9,7 @@
import com.codahale.metrics.annotation.ExceptionMetered;
import com.codahale.metrics.annotation.Metered;
import com.codahale.metrics.annotation.ResponseMetered;
import com.codahale.metrics.annotation.ResponseMeteredLevel;
import com.codahale.metrics.annotation.Timed;
import org.glassfish.jersey.server.ContainerResponse;
import org.glassfish.jersey.server.model.ModelProcessor;
Expand All @@ -28,12 +29,17 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.EnumSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import static com.codahale.metrics.MetricRegistry.name;
import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;

/**
* An application event listener that listens for Jersey application initialization to
Expand Down Expand Up @@ -127,25 +133,44 @@ public ExceptionMeterMetric(final MetricRegistry registry,
* different response codes
*/
private static class ResponseMeterMetric {
private static final Set<ResponseMeteredLevel> COARSE_METER_LEVELS = EnumSet.of(COARSE, ALL);
private static final Set<ResponseMeteredLevel> DETAILED_METER_LEVELS = EnumSet.of(DETAILED, ALL);
public final List<Meter> meters;
private final ConcurrentMap<Integer, Meter> responseCodeMeters = new ConcurrentHashMap<>();
private final Map<Integer, Meter> responseCodeMeters;
private final MetricRegistry metricRegistry;
private final String metricName;
private final ResponseMeteredLevel level;

public ResponseMeterMetric(final MetricRegistry registry,
final ResourceMethod method,
final ResponseMetered responseMetered) {
this.metricName = chooseName(responseMetered.name(), responseMetered.absolute(), method);
this.meters = Collections.unmodifiableList(Arrays.asList(
this.level = responseMetered.level();
this.meters = COARSE_METER_LEVELS.contains(level) ?
Collections.unmodifiableList(Arrays.asList(
registry.meter(name(metricName, "1xx-responses")), // 1xx
registry.meter(name(metricName, "2xx-responses")), // 2xx
registry.meter(name(metricName, "3xx-responses")), // 3xx
registry.meter(name(metricName, "4xx-responses")), // 4xx
registry.meter(name(metricName, "5xx-responses")) // 5xx
));
)) : Collections.emptyList();
this.responseCodeMeters = DETAILED_METER_LEVELS.contains(level) ? new ConcurrentHashMap<>() : Collections.emptyMap();
this.metricRegistry = registry;
}

public void mark(int statusCode) {
if (DETAILED_METER_LEVELS.contains(level)) {
getResponseCodeMeter(statusCode).mark();
}

if (COARSE_METER_LEVELS.contains(level)) {
final int responseStatus = statusCode / 100;
if (responseStatus >= 1 && responseStatus <= 5) {
meters.get(responseStatus - 1).mark();
}
}
}

public Meter getResponseCodeMeter(int statusCode) {
return responseCodeMeters
.computeIfAbsent(statusCode, sc -> metricRegistry
Expand Down Expand Up @@ -281,17 +306,10 @@ public void onEvent(RequestEvent event) {

if (metric != null) {
ContainerResponse containerResponse = event.getContainerResponse();
if (containerResponse == null) {
if (event.getException() != null) {
metric.meters.get(4).mark();
metric.getResponseCodeMeter(500).mark();
}
if (containerResponse == null && event.getException() != null) {
metric.mark(500);
} else {
final int responseStatus = containerResponse.getStatus() / 100;
if (responseStatus >= 1 && responseStatus <= 5) {
metric.meters.get(responseStatus - 1).mark();
metric.getResponseCodeMeter(containerResponse.getStatus()).mark();
}
metric.mark(containerResponse.getStatus());
}
}
}
Expand Down
Expand Up @@ -95,69 +95,73 @@ public void exceptionMeteredMethodsAreExceptionMetered() {
}

@Test
public void responseMeteredMethodsAreMetered() {
public void responseMeteredMethodsAreMeteredWithCoarseLevel() {
final Meter meter2xx = registry.meter(name(InstrumentedResource.class,
"response2xxMetered",
"responseMeteredCoarse",
"2xx-responses"));
final Meter meter200 = registry.meter(name(InstrumentedResource.class,
"response2xxMetered",
"responseMeteredCoarse",
"200-responses"));
final Meter meter4xx = registry.meter(name(InstrumentedResource.class,
"response4xxMetered",
"4xx-responses"));
final Meter meter400 = registry.meter(name(InstrumentedResource.class,
"response4xxMetered",
"400-responses"));
final Meter meter401 = registry.meter(name(InstrumentedResource.class,
"response4xxMetered",
"401-responses"));
final Meter meter5xx = registry.meter(name(InstrumentedResource.class,
"response5xxMetered",
"5xx-responses"));
final Meter meter500 = registry.meter(name(InstrumentedResource.class,
"response5xxMetered",
"500-responses"));
final Meter meter503 = registry.meter(name(InstrumentedResource.class,
"response5xxMetered",
"503-responses"));

assertThat(meter2xx.getCount()).isZero();
assertThat(target("response-2xx-metered")
assertThat(meter200.getCount()).isZero();
assertThat(target("response-metered-coarse")
.request()
.get().getStatus())
.isEqualTo(200);

assertThat(meter4xx.getCount()).isZero();
assertThat(target("response-4xx-metered")
.request()
.get().getStatus())
.isEqualTo(400);
assertThat(meter2xx.getCount()).isOne();
assertThat(meter200.getCount()).isZero();
}

assertThat(target("response-4xx-metered")
.queryParam("status_code", 401)
@Test
public void responseMeteredMethodsAreMeteredWithDetailedLevel() {
final Meter meter2xx = registry.meter(name(InstrumentedResource.class,
"responseMeteredDetailed",
"2xx-responses"));
final Meter meter200 = registry.meter(name(InstrumentedResource.class,
"responseMeteredDetailed",
"200-responses"));
final Meter meter201 = registry.meter(name(InstrumentedResource.class,
"responseMeteredDetailed",
"201-responses"));

assertThat(meter2xx.getCount()).isZero();
assertThat(meter200.getCount()).isZero();
assertThat(meter201.getCount()).isZero();
assertThat(target("response-metered-detailed")
.request()
.get().getStatus())
.isEqualTo(401);

assertThat(meter5xx.getCount()).isZero();
assertThat(target("response-5xx-metered")
.isEqualTo(200);
assertThat(target("response-metered-detailed")
.queryParam("status_code", 201)
.request()
.get().getStatus())
.isEqualTo(500);
assertThat(target("response-5xx-metered")
.queryParam("status_code", 503)
.isEqualTo(201);

assertThat(meter2xx.getCount()).isZero();
assertThat(meter200.getCount()).isOne();
assertThat(meter201.getCount()).isOne();
}

@Test
public void responseMeteredMethodsAreMeteredWithAllLevel() {
final Meter meter2xx = registry.meter(name(InstrumentedResource.class,
"responseMeteredAll",
"2xx-responses"));
final Meter meter200 = registry.meter(name(InstrumentedResource.class,
"responseMeteredAll",
"200-responses"));

assertThat(meter2xx.getCount()).isZero();
assertThat(meter200.getCount()).isZero();
assertThat(target("response-metered-all")
.request()
.get().getStatus())
.isEqualTo(503);

assertThat(meter2xx.getCount()).isEqualTo(1);
assertThat(meter4xx.getCount()).isEqualTo(2);
assertThat(meter5xx.getCount()).isEqualTo(2);
assertThat(meter200.getCount()).isEqualTo(1);
assertThat(meter400.getCount()).isEqualTo(1);
assertThat(meter401.getCount()).isEqualTo(1);
assertThat(meter500.getCount()).isEqualTo(1);
assertThat(meter503.getCount()).isEqualTo(1);
.isEqualTo(200);

assertThat(meter2xx.getCount()).isOne();
assertThat(meter200.getCount()).isOne();
}

@Test
Expand Down
Expand Up @@ -62,11 +62,6 @@ public void responseMetered4xxPerClassMethodsAreMetered() {
.request()
.get().getStatus())
.isEqualTo(400);
assertThat(target("responseMetered4xxPerClass")
.queryParam("status_code", 401)
.request()
.get().getStatus())
.isEqualTo(401);
assertThat(target("responseMeteredBadRequestPerClass")
.request()
.get().getStatus())
Expand All @@ -75,15 +70,11 @@ public void responseMetered4xxPerClassMethodsAreMetered() {
final Meter meter4xx = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
"responseMetered4xxPerClass",
"4xx-responses"));
final Meter meter401 = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
"responseMetered4xxPerClass",
"401-responses"));
final Meter meterException4xx = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
"responseMeteredBadRequestPerClass",
"4xx-responses"));

assertThat(meter4xx.getCount()).isEqualTo(2);
assertThat(meter401.getCount()).isEqualTo(1);
assertThat(meter4xx.getCount()).isEqualTo(1);
assertThat(meterException4xx.getCount()).isEqualTo(1);
}

Expand All @@ -93,22 +84,12 @@ public void responseMetered5xxPerClassMethodsAreMetered() {
.request()
.get().getStatus())
.isEqualTo(500);
assertThat(target("responseMetered5xxPerClass")
.queryParam("status_code", 503)
.request()
.get().getStatus())
.isEqualTo(503);

final Meter meter5xx = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
"responseMetered5xxPerClass",
"5xx-responses"));

final Meter meter503 = registry.meter(name(InstrumentedResourceResponseMeteredPerClass.class,
"responseMetered5xxPerClass",
"503-responses"));

assertThat(meter5xx.getCount()).isEqualTo(2);
assertThat(meter503.getCount()).isEqualTo(1);
assertThat(meter5xx.getCount()).isEqualTo(1);
}

@Test
Expand Down Expand Up @@ -145,14 +126,21 @@ public void responseMeteredUnmappedExceptionPerClassMethodsAreMetered() {

@Test
public void subresourcesFromLocatorsRegisterMetrics() {
final Meter meter2xx = registry.meter(name(InstrumentedSubResourceResponseMeteredPerClass.class,
"responseMeteredPerClass",
"2xx-responses"));
final Meter meter201 = registry.meter(name(InstrumentedSubResourceResponseMeteredPerClass.class,
"responseMeteredPerClass",
"200-responses"));

assertThat(meter2xx.getCount()).isZero();
assertThat(meter2xx.getCount()).isZero();
assertThat(target("subresource/responseMeteredPerClass")
.request()
.get().getStatus())
.isEqualTo(200);

final Meter meter = registry.meter(name(InstrumentedSubResourceResponseMeteredPerClass.class,
"responseMeteredPerClass",
"2xx-responses"));
assertThat(meter.getCount()).isEqualTo(1);
assertThat(meter2xx.getCount()).isOne();
assertThat(meter2xx.getCount()).isOne();
}
}
Expand Up @@ -14,6 +14,10 @@
import javax.ws.rs.core.Response;
import java.io.IOException;

import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
import static com.codahale.metrics.annotation.ResponseMeteredLevel.DETAILED;
import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;

@Path("/")
@Produces(MediaType.TEXT_PLAIN)
public class InstrumentedResource {
Expand Down Expand Up @@ -42,23 +46,23 @@ public String exceptionMetered(@QueryParam("splode") @DefaultValue("false") bool
}

@GET
@ResponseMetered
@Path("/response-2xx-metered")
public Response response2xxMetered() {
return Response.ok().build();
@ResponseMetered(level = DETAILED)
@Path("/response-metered-detailed")
public Response responseMeteredDetailed(@QueryParam("status_code") @DefaultValue("200") int statusCode) {
return Response.status(Response.Status.fromStatusCode(statusCode)).build();
}

@GET
@ResponseMetered
@Path("/response-4xx-metered")
public Response response4xxMetered(@QueryParam("status_code") @DefaultValue("400") int statusCode) {
@ResponseMetered(level = COARSE)
@Path("/response-metered-coarse")
public Response responseMeteredCoarse(@QueryParam("status_code") @DefaultValue("200") int statusCode) {
return Response.status(Response.Status.fromStatusCode(statusCode)).build();
}

@GET
@ResponseMetered
@Path("/response-5xx-metered")
public Response response5xxMetered(@QueryParam("status_code") @DefaultValue("500") int statusCode) {
@ResponseMetered(level = ALL)
@Path("/response-metered-all")
public Response responseMeteredAll(@QueryParam("status_code") @DefaultValue("200") int statusCode) {
return Response.status(Response.Status.fromStatusCode(statusCode)).build();
}

Expand Down

0 comments on commit b53546a

Please sign in to comment.