Skip to content

Commit

Permalink
Add detailed response code meters for Jetty 11.x (#3175)
Browse files Browse the repository at this point in the history
Refs #3133
  • Loading branch information
joschi committed Feb 12, 2023
1 parent de59ad1 commit 777bf32
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 47 deletions.
5 changes: 4 additions & 1 deletion metrics-jetty11/pom.xml
Expand Up @@ -58,7 +58,10 @@
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-annotation</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
Expand Down
Expand Up @@ -5,6 +5,7 @@
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.RatioGauge;
import com.codahale.metrics.Timer;
import com.codahale.metrics.annotation.ResponseMeteredLevel;
import jakarta.servlet.AsyncEvent;
import jakarta.servlet.AsyncListener;
import jakarta.servlet.ServletException;
Expand All @@ -18,9 +19,19 @@
import org.eclipse.jetty.server.handler.HandlerWrapper;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

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

/**
* A Jetty {@link Handler} which records various metrics about an underlying {@link Handler}
Expand Down Expand Up @@ -55,6 +66,8 @@ public class InstrumentedHandler extends HandlerWrapper {
private static final String NAME_PERCENT_5XX_1M = "percent-5xx-1m";
private static final String NAME_PERCENT_5XX_5M = "percent-5xx-5m";
private static final String NAME_PERCENT_5XX_15M = "percent-5xx-15m";
private static final Set<ResponseMeteredLevel> COARSE_METER_LEVELS = EnumSet.of(COARSE, ALL);
private static final Set<ResponseMeteredLevel> DETAILED_METER_LEVELS = EnumSet.of(DETAILED, ALL);

private final MetricRegistry metricRegistry;

Expand Down Expand Up @@ -82,7 +95,9 @@ public class InstrumentedHandler extends HandlerWrapper {
// the number of requests that expired while suspended
private Meter asyncTimeouts;

private Meter[] responses;
private final ResponseMeteredLevel responseMeteredLevel;
private List<Meter> responses;
private Map<Integer, Meter> responseCodeMeters;

private Timer getRequests;
private Timer postRequests;
Expand Down Expand Up @@ -113,6 +128,18 @@ public InstrumentedHandler(MetricRegistry registry) {
* @param prefix the prefix to use for the metrics names
*/
public InstrumentedHandler(MetricRegistry registry, String prefix) {
this(registry, prefix, COARSE);
}

/**
* Create a new instrumented handler using a given metrics registry.
*
* @param registry the registry for the metrics
* @param prefix the prefix to use for the metrics names
* @param responseMeteredLevel the level to determine individual/aggregate response codes that are instrumented
*/
public InstrumentedHandler(MetricRegistry registry, String prefix, ResponseMeteredLevel responseMeteredLevel) {
this.responseMeteredLevel = responseMeteredLevel;
this.metricRegistry = registry;
this.prefix = prefix;
}
Expand Down Expand Up @@ -141,13 +168,15 @@ protected void doStart() throws Exception {
this.asyncDispatches = metricRegistry.meter(name(prefix, NAME_ASYNC_DISPATCHES));
this.asyncTimeouts = metricRegistry.meter(name(prefix, NAME_ASYNC_TIMEOUTS));

this.responses = new Meter[]{
metricRegistry.meter(name(prefix, NAME_1XX_RESPONSES)), // 1xx
metricRegistry.meter(name(prefix, NAME_2XX_RESPONSES)), // 2xx
metricRegistry.meter(name(prefix, NAME_3XX_RESPONSES)), // 3xx
metricRegistry.meter(name(prefix, NAME_4XX_RESPONSES)), // 4xx
metricRegistry.meter(name(prefix, NAME_5XX_RESPONSES)) // 5xx
};
this.responseCodeMeters = DETAILED_METER_LEVELS.contains(responseMeteredLevel) ? new ConcurrentHashMap<>() : Collections.emptyMap();
this.responses = COARSE_METER_LEVELS.contains(responseMeteredLevel) ?
Collections.unmodifiableList(Arrays.asList(
metricRegistry.meter(name(prefix, NAME_1XX_RESPONSES)), // 1xx
metricRegistry.meter(name(prefix, NAME_2XX_RESPONSES)), // 2xx
metricRegistry.meter(name(prefix, NAME_3XX_RESPONSES)), // 3xx
metricRegistry.meter(name(prefix, NAME_4XX_RESPONSES)), // 4xx
metricRegistry.meter(name(prefix, NAME_5XX_RESPONSES)) // 5xx
)) : Collections.emptyList();

this.getRequests = metricRegistry.timer(name(prefix, NAME_GET_REQUESTS));
this.postRequests = metricRegistry.timer(name(prefix, NAME_POST_REQUESTS));
Expand All @@ -163,47 +192,47 @@ protected void doStart() throws Exception {
metricRegistry.register(name(prefix, NAME_PERCENT_4XX_1M), new RatioGauge() {
@Override
protected Ratio getRatio() {
return Ratio.of(responses[3].getOneMinuteRate(),
return Ratio.of(responses.get(3).getOneMinuteRate(),
requests.getOneMinuteRate());
}
});

metricRegistry.register(name(prefix, NAME_PERCENT_4XX_5M), new RatioGauge() {
@Override
protected Ratio getRatio() {
return Ratio.of(responses[3].getFiveMinuteRate(),
return Ratio.of(responses.get(3).getFiveMinuteRate(),
requests.getFiveMinuteRate());
}
});

metricRegistry.register(name(prefix, NAME_PERCENT_4XX_15M), new RatioGauge() {
@Override
protected Ratio getRatio() {
return Ratio.of(responses[3].getFifteenMinuteRate(),
return Ratio.of(responses.get(3).getFifteenMinuteRate(),
requests.getFifteenMinuteRate());
}
});

metricRegistry.register(name(prefix, NAME_PERCENT_5XX_1M), new RatioGauge() {
@Override
protected Ratio getRatio() {
return Ratio.of(responses[4].getOneMinuteRate(),
return Ratio.of(responses.get(4).getOneMinuteRate(),
requests.getOneMinuteRate());
}
});

metricRegistry.register(name(prefix, NAME_PERCENT_5XX_5M), new RatioGauge() {
@Override
protected Ratio getRatio() {
return Ratio.of(responses[4].getFiveMinuteRate(),
return Ratio.of(responses.get(4).getFiveMinuteRate(),
requests.getFiveMinuteRate());
}
});

metricRegistry.register(name(prefix, NAME_PERCENT_5XX_15M), new RatioGauge() {
@Override
public Ratio getRatio() {
return Ratio.of(responses[4].getFifteenMinuteRate(),
return Ratio.of(responses.get(4).getFifteenMinuteRate(),
requests.getFifteenMinuteRate());
}
});
Expand Down Expand Up @@ -244,7 +273,9 @@ protected void doStop() throws Exception {
metricRegistry.remove(name(prefix, NAME_PERCENT_5XX_1M));
metricRegistry.remove(name(prefix, NAME_PERCENT_5XX_5M));
metricRegistry.remove(name(prefix, NAME_PERCENT_5XX_15M));

responseCodeMeters.keySet().stream()
.map(sc -> name(getMetricPrefix(), String.format("%d-responses", sc)))
.forEach(m -> metricRegistry.remove(m));
super.doStop();
}

Expand Down Expand Up @@ -321,21 +352,36 @@ private Timer requestTimer(String method) {
}

private void updateResponses(HttpServletRequest request, HttpServletResponse response, long start, boolean isHandled) {
final int responseStatus;
if (isHandled) {
responseStatus = response.getStatus() / 100;
mark(response.getStatus());
} else {
responseStatus = 4; // will end up with a 404 response sent by HttpChannel.handle
}
if (responseStatus >= 1 && responseStatus <= 5) {
responses[responseStatus - 1].mark();
mark(404);; // will end up with a 404 response sent by HttpChannel.handle
}
activeRequests.dec();
final long elapsedTime = System.currentTimeMillis() - start;
requests.update(elapsedTime, TimeUnit.MILLISECONDS);
requestTimer(request.getMethod()).update(elapsedTime, TimeUnit.MILLISECONDS);
}

private void mark(int statusCode) {
if (DETAILED_METER_LEVELS.contains(responseMeteredLevel)) {
getResponseCodeMeter(statusCode).mark();
}

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

private Meter getResponseCodeMeter(int statusCode) {
return responseCodeMeters
.computeIfAbsent(statusCode, sc -> metricRegistry
.meter(name(getMetricPrefix(), String.format("%d-responses", sc))));
}

private String getMetricPrefix() {
return this.prefix == null ? name(getHandler().getClass(), name) : name(this.prefix, name);
}
Expand Down

0 comments on commit 777bf32

Please sign in to comment.