io.dropwizard.metrics
metrics-jmx
diff --git a/metrics-jetty12-ee10/pom.xml b/metrics-jetty12-ee10/pom.xml
new file mode 100644
index 0000000000..3c2893b69d
--- /dev/null
+++ b/metrics-jetty12-ee10/pom.xml
@@ -0,0 +1,138 @@
+
+
+ 4.0.0
+
+
+ io.dropwizard.metrics
+ metrics-parent
+ 4.2.20-SNAPSHOT
+
+
+ metrics-jetty12-ee10
+ Metrics Integration for Jetty 12.x and higher with Jakarta EE 10
+ bundle
+
+ A set of extensions for Jetty 12.x and higher which provide instrumentation of thread pools, connector
+ metrics, and application latency and utilization. This module uses the Servlet API from Jakarta EE 10.
+
+
+
+ io.dropwizard.metrics.jetty12.ee10
+
+ 17
+
+ 2.0.7
+
+
+
+
+
+ io.dropwizard.metrics
+ metrics-bom
+ ${project.version}
+ pom
+ import
+
+
+ org.eclipse.jetty
+ jetty-bom
+ ${jetty12.version}
+ pom
+ import
+
+
+ org.eclipse.jetty.ee10
+ jetty-ee10-bom
+ ${jetty12.version}
+ pom
+ import
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+ net.bytebuddy
+ byte-buddy
+ ${byte-buddy.version}
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ ${servlet6.version}
+
+
+
+
+
+
+ io.dropwizard.metrics
+ metrics-core
+
+
+ io.dropwizard.metrics
+ metrics-annotation
+
+
+ io.dropwizard.metrics
+ metrics-jetty12
+
+
+ org.eclipse.jetty
+ jetty-server
+
+
+ org.eclipse.jetty
+ jetty-util
+
+
+ org.eclipse.jetty.ee10
+ jetty-ee10-servlet
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+ runtime
+
+
+ junit
+ junit
+ ${junit.version}
+ test
+
+
+ org.assertj
+ assertj-core
+ ${assertj.version}
+ test
+
+
+ org.mockito
+ mockito-core
+ ${mockito.version}
+ test
+
+
+ org.eclipse.jetty
+ jetty-client
+ test
+
+
+ org.eclipse.jetty
+ jetty-http
+ test
+
+
+ org.slf4j
+ slf4j-simple
+ ${slf4j.version}
+ test
+
+
+
diff --git a/metrics-jetty12-ee10/src/main/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10Handler.java b/metrics-jetty12-ee10/src/main/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10Handler.java
new file mode 100644
index 0000000000..58a271703b
--- /dev/null
+++ b/metrics-jetty12-ee10/src/main/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10Handler.java
@@ -0,0 +1,173 @@
+package io.dropwizard.metrics.jetty12.ee10;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.annotation.ResponseMeteredLevel;
+import io.dropwizard.metrics.jetty12.AbstractInstrumentedHandler;
+import jakarta.servlet.AsyncEvent;
+import jakarta.servlet.AsyncListener;
+import org.eclipse.jetty.ee10.servlet.AsyncContextState;
+import org.eclipse.jetty.ee10.servlet.ServletApiRequest;
+import org.eclipse.jetty.ee10.servlet.ServletApiResponse;
+import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
+import org.eclipse.jetty.ee10.servlet.ServletRequestState;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.Callback;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE;
+
+/**
+ * A Jetty {@link Handler} which records various metrics about an underlying {@link Handler}
+ * instance. This {@link Handler} requires a {@link org.eclipse.jetty.ee10.servlet.ServletContextHandler} to be present.
+ * For correct behaviour, the {@link org.eclipse.jetty.ee10.servlet.ServletContextHandler} should be before this handler
+ * in the handler chain. To achieve this, one can use
+ * {@link org.eclipse.jetty.ee10.servlet.ServletContextHandler#insertHandler(Singleton)}.
+ */
+public class InstrumentedEE10Handler extends AbstractInstrumentedHandler {
+ private AsyncListener listener;
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ */
+ public InstrumentedEE10Handler(MetricRegistry registry) {
+ super(registry, null);
+ }
+
+ /**
+ * 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
+ */
+ public InstrumentedEE10Handler(MetricRegistry registry, String prefix) {
+ super(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 InstrumentedEE10Handler(MetricRegistry registry, String prefix, ResponseMeteredLevel responseMeteredLevel) {
+ super(registry, prefix, responseMeteredLevel);
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ super.doStart();
+
+ this.listener = new AsyncAttachingListener();
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ super.doStop();
+ }
+
+ @Override
+ public boolean handle(Request request, Response response, Callback callback) throws Exception {
+ ServletContextRequest servletContextRequest = Request.as(request, ServletContextRequest.class);
+
+ // only handle servlet requests with the InstrumentedHandler
+ // because it depends on the ServletRequestState
+ if (servletContextRequest == null) {
+ return super.handle(request, response, callback);
+ }
+
+ activeDispatches.inc();
+
+ final long start;
+ final ServletRequestState state = servletContextRequest.getServletRequestState();
+ if (state.isInitial()) {
+ // new request
+ activeRequests.inc();
+ start = Request.getTimeStamp(request);
+ state.addListener(listener);
+ } else {
+ // resumed request
+ start = System.currentTimeMillis();
+ activeSuspended.dec();
+ if (state.getState() == ServletRequestState.State.HANDLING) {
+ asyncDispatches.mark();
+ }
+ }
+
+ boolean handled = false;
+
+ try {
+ handled = super.handle(request, response, callback);
+ } finally {
+ final long now = System.currentTimeMillis();
+ final long dispatched = now - start;
+
+ activeDispatches.dec();
+ dispatches.update(dispatched, TimeUnit.MILLISECONDS);
+
+ if (state.isSuspended()) {
+ activeSuspended.inc();
+ } else if (state.isInitial()) {
+ updateResponses(request, response, start, handled);
+ }
+ // else onCompletion will handle it.
+ }
+
+ return handled;
+ }
+
+ private class AsyncAttachingListener implements AsyncListener {
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException {}
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException {
+ event.getAsyncContext().addListener(new InstrumentedAsyncListener());
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException {}
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException {}
+ };
+
+ private class InstrumentedAsyncListener implements AsyncListener {
+ private final long startTime;
+
+ InstrumentedAsyncListener() {
+ this.startTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException {
+ asyncTimeouts.mark();
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException {
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException {
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException {
+ final AsyncContextState state = (AsyncContextState) event.getAsyncContext();
+ final ServletApiRequest request = (ServletApiRequest) state.getRequest();
+ final ServletApiResponse response = (ServletApiResponse) state.getResponse();
+ updateResponses(request.getRequest(), response.getResponse(), startTime, true);
+ if (!state.getServletChannelState().isSuspended()) {
+ activeSuspended.dec();
+ }
+ }
+ }
+}
diff --git a/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10HandlerTest.java b/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10HandlerTest.java
new file mode 100644
index 0000000000..37ff03d654
--- /dev/null
+++ b/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10HandlerTest.java
@@ -0,0 +1,251 @@
+package io.dropwizard.metrics.jetty12.ee10;
+
+import com.codahale.metrics.MetricRegistry;
+import org.eclipse.jetty.client.ContentResponse;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.ee10.servlet.DefaultServlet;
+import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
+import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
+import org.eclipse.jetty.ee10.servlet.ServletHandler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.Callback;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import jakarta.servlet.AsyncContext;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+
+import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+public class InstrumentedEE10HandlerTest {
+ private final HttpClient client = new HttpClient();
+ private final MetricRegistry registry = new MetricRegistry();
+ private final Server server = new Server();
+ private final ServerConnector connector = new ServerConnector(server);
+ private final InstrumentedEE10Handler handler = new InstrumentedEE10Handler(registry, null, ALL);
+
+ @Before
+ public void setUp() throws Exception {
+ handler.setName("handler");
+
+ TestHandler testHandler = new TestHandler();
+ // a servlet handler needs a servlet mapping, else the request will be short-circuited
+ // so use the DefaultServlet here
+ testHandler.addServletWithMapping(DefaultServlet.class, "/");
+
+ // builds the following handler chain:
+ // ServletContextHandler -> InstrumentedHandler -> TestHandler
+ // the ServletContextHandler is needed to utilize servlet related classes
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setHandler(testHandler);
+ servletContextHandler.insertHandler(handler);
+ server.setHandler(servletContextHandler);
+
+ server.addConnector(connector);
+ server.start();
+ client.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ server.stop();
+ client.stop();
+ }
+
+ @Test
+ public void hasAName() throws Exception {
+ assertThat(handler.getName())
+ .isEqualTo("handler");
+ }
+
+ @Test
+ public void createsAndRemovesMetricsForTheHandler() throws Exception {
+ final ContentResponse response = client.GET(uri("/hello"));
+
+ assertThat(response.getStatus())
+ .isEqualTo(404);
+
+ assertThat(registry.getNames())
+ .containsOnly(
+ MetricRegistry.name(TestHandler.class, "handler.1xx-responses"),
+ MetricRegistry.name(TestHandler.class, "handler.2xx-responses"),
+ MetricRegistry.name(TestHandler.class, "handler.3xx-responses"),
+ MetricRegistry.name(TestHandler.class, "handler.4xx-responses"),
+ MetricRegistry.name(TestHandler.class, "handler.404-responses"),
+ MetricRegistry.name(TestHandler.class, "handler.5xx-responses"),
+ MetricRegistry.name(TestHandler.class, "handler.percent-4xx-1m"),
+ MetricRegistry.name(TestHandler.class, "handler.percent-4xx-5m"),
+ MetricRegistry.name(TestHandler.class, "handler.percent-4xx-15m"),
+ MetricRegistry.name(TestHandler.class, "handler.percent-5xx-1m"),
+ MetricRegistry.name(TestHandler.class, "handler.percent-5xx-5m"),
+ MetricRegistry.name(TestHandler.class, "handler.percent-5xx-15m"),
+ MetricRegistry.name(TestHandler.class, "handler.requests"),
+ MetricRegistry.name(TestHandler.class, "handler.active-suspended"),
+ MetricRegistry.name(TestHandler.class, "handler.async-dispatches"),
+ MetricRegistry.name(TestHandler.class, "handler.async-timeouts"),
+ MetricRegistry.name(TestHandler.class, "handler.get-requests"),
+ MetricRegistry.name(TestHandler.class, "handler.put-requests"),
+ MetricRegistry.name(TestHandler.class, "handler.active-dispatches"),
+ MetricRegistry.name(TestHandler.class, "handler.trace-requests"),
+ MetricRegistry.name(TestHandler.class, "handler.other-requests"),
+ MetricRegistry.name(TestHandler.class, "handler.connect-requests"),
+ MetricRegistry.name(TestHandler.class, "handler.dispatches"),
+ MetricRegistry.name(TestHandler.class, "handler.head-requests"),
+ MetricRegistry.name(TestHandler.class, "handler.post-requests"),
+ MetricRegistry.name(TestHandler.class, "handler.options-requests"),
+ MetricRegistry.name(TestHandler.class, "handler.active-requests"),
+ MetricRegistry.name(TestHandler.class, "handler.delete-requests"),
+ MetricRegistry.name(TestHandler.class, "handler.move-requests")
+ );
+
+ server.stop();
+
+ assertThat(registry.getNames())
+ .isEmpty();
+ }
+
+ @Test
+ public void responseTimesAreRecordedForBlockingResponses() throws Exception {
+
+ final ContentResponse response = client.GET(uri("/blocking"));
+
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+
+ assertResponseTimesValid();
+ }
+
+ @Test
+ public void doStopDoesNotThrowNPE() throws Exception {
+ InstrumentedEE10Handler handler = new InstrumentedEE10Handler(registry, null, ALL);
+ handler.setHandler(new TestHandler());
+
+ assertThatCode(handler::doStop).doesNotThrowAnyException();
+ }
+
+ @Test
+ @Ignore("flaky on virtual machines")
+ public void responseTimesAreRecordedForAsyncResponses() throws Exception {
+
+ final ContentResponse response = client.GET(uri("/async"));
+
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+
+ assertResponseTimesValid();
+ }
+
+ private void assertResponseTimesValid() {
+ assertThat(registry.getMeters().get(metricName() + ".2xx-responses")
+ .getCount()).isGreaterThan(0L);
+ assertThat(registry.getMeters().get(metricName() + ".200-responses")
+ .getCount()).isGreaterThan(0L);
+
+
+ assertThat(registry.getTimers().get(metricName() + ".get-requests")
+ .getSnapshot().getMedian()).isGreaterThan(0.0).isLessThan(TimeUnit.SECONDS.toNanos(1));
+
+ assertThat(registry.getTimers().get(metricName() + ".requests")
+ .getSnapshot().getMedian()).isGreaterThan(0.0).isLessThan(TimeUnit.SECONDS.toNanos(1));
+ }
+
+ private String uri(String path) {
+ return "http://localhost:" + connector.getLocalPort() + path;
+ }
+
+ private String metricName() {
+ return MetricRegistry.name(TestHandler.class.getName(), "handler");
+ }
+
+ /**
+ * test handler.
+ *
+ * Supports
+ *
+ * /blocking - uses the standard servlet api
+ * /async - uses the 3.1 async api to complete the request
+ *
+ * all other requests will return 404
+ */
+ private static class TestHandler extends ServletHandler {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback) throws Exception {
+ ServletContextRequest servletContextRequest = Request.as(request, ServletContextRequest.class);
+ if (servletContextRequest == null) {
+ return false;
+ }
+
+ HttpServletRequest httpServletRequest = servletContextRequest.getServletApiRequest();
+ HttpServletResponse httpServletResponse = servletContextRequest.getHttpServletResponse();
+
+ String path = request.getHttpURI().getPath();
+ switch (path) {
+ case "/blocking":
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ httpServletResponse.setStatus(200);
+ httpServletResponse.setContentType("text/plain");
+ httpServletResponse.getWriter().write("some content from the blocking request\n");
+ callback.succeeded();
+ return true;
+ case "/async":
+ servletContextRequest.getState().handling();
+ final AsyncContext context = httpServletRequest.startAsync();
+ Thread t = new Thread(() -> {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ httpServletResponse.setStatus(200);
+ httpServletResponse.setContentType("text/plain");
+ final ServletOutputStream servletOutputStream;
+ try {
+ servletOutputStream = httpServletResponse.getOutputStream();
+ servletOutputStream.setWriteListener(
+ new WriteListener() {
+ @Override
+ public void onWritePossible() throws IOException {
+ servletOutputStream.write("some content from the async\n"
+ .getBytes(StandardCharsets.UTF_8));
+ context.complete();
+ servletContextRequest.getServletChannel().sendResponseAndComplete();
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ context.complete();
+ servletContextRequest.getServletChannel().sendResponseAndComplete();
+ }
+ }
+ );
+ servletContextRequest.getHttpOutput().run();
+ } catch (IOException e) {
+ context.complete();
+ servletContextRequest.getServletChannel().sendResponseAndComplete();
+ }
+ });
+ t.start();
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+}
diff --git a/metrics-jetty12/pom.xml b/metrics-jetty12/pom.xml
new file mode 100644
index 0000000000..1f31705e72
--- /dev/null
+++ b/metrics-jetty12/pom.xml
@@ -0,0 +1,117 @@
+
+
+ 4.0.0
+
+
+ io.dropwizard.metrics
+ metrics-parent
+ 4.2.20-SNAPSHOT
+
+
+ metrics-jetty12
+ Metrics Integration for Jetty 12.x and higher
+ bundle
+
+ A set of extensions for Jetty 12.x and higher which provide instrumentation of thread pools, connector
+ metrics, and application latency and utilization.
+
+
+
+ io.dropwizard.metrics.jetty12
+
+ 17
+
+ 2.0.7
+
+
+
+
+
+ io.dropwizard.metrics
+ metrics-bom
+ ${project.version}
+ pom
+ import
+
+
+ org.eclipse.jetty
+ jetty-bom
+ ${jetty12.version}
+ pom
+ import
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+ net.bytebuddy
+ byte-buddy
+ ${byte-buddy.version}
+
+
+
+
+
+
+ io.dropwizard.metrics
+ metrics-core
+
+
+ io.dropwizard.metrics
+ metrics-annotation
+
+
+ org.eclipse.jetty
+ jetty-server
+
+
+ org.eclipse.jetty
+ jetty-http
+
+
+ org.eclipse.jetty
+ jetty-io
+
+
+ org.eclipse.jetty
+ jetty-util
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+ runtime
+
+
+ junit
+ junit
+ ${junit.version}
+ test
+
+
+ org.assertj
+ assertj-core
+ ${assertj.version}
+ test
+
+
+ org.mockito
+ mockito-core
+ ${mockito.version}
+ test
+
+
+ org.eclipse.jetty
+ jetty-client
+ test
+
+
+ org.slf4j
+ slf4j-simple
+ ${slf4j.version}
+ test
+
+
+
diff --git a/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/AbstractInstrumentedHandler.java b/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/AbstractInstrumentedHandler.java
new file mode 100644
index 0000000000..9cbaeac725
--- /dev/null
+++ b/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/AbstractInstrumentedHandler.java
@@ -0,0 +1,336 @@
+package io.dropwizard.metrics.jetty12;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.RatioGauge;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.annotation.ResponseMeteredLevel;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+
+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;
+
+/**
+ * An abstract base class of a Jetty {@link Handler} which records various metrics about an underlying {@link Handler}
+ * instance.
+ */
+public abstract class AbstractInstrumentedHandler extends Handler.Wrapper {
+ protected static final String NAME_REQUESTS = "requests";
+ protected static final String NAME_DISPATCHES = "dispatches";
+ protected static final String NAME_ACTIVE_REQUESTS = "active-requests";
+ protected static final String NAME_ACTIVE_DISPATCHES = "active-dispatches";
+ protected static final String NAME_ACTIVE_SUSPENDED = "active-suspended";
+ protected static final String NAME_ASYNC_DISPATCHES = "async-dispatches";
+ protected static final String NAME_ASYNC_TIMEOUTS = "async-timeouts";
+ protected static final String NAME_1XX_RESPONSES = "1xx-responses";
+ protected static final String NAME_2XX_RESPONSES = "2xx-responses";
+ protected static final String NAME_3XX_RESPONSES = "3xx-responses";
+ protected static final String NAME_4XX_RESPONSES = "4xx-responses";
+ protected static final String NAME_5XX_RESPONSES = "5xx-responses";
+ protected static final String NAME_GET_REQUESTS = "get-requests";
+ protected static final String NAME_POST_REQUESTS = "post-requests";
+ protected static final String NAME_HEAD_REQUESTS = "head-requests";
+ protected static final String NAME_PUT_REQUESTS = "put-requests";
+ protected static final String NAME_DELETE_REQUESTS = "delete-requests";
+ protected static final String NAME_OPTIONS_REQUESTS = "options-requests";
+ protected static final String NAME_TRACE_REQUESTS = "trace-requests";
+ protected static final String NAME_CONNECT_REQUESTS = "connect-requests";
+ protected static final String NAME_MOVE_REQUESTS = "move-requests";
+ protected static final String NAME_OTHER_REQUESTS = "other-requests";
+ protected static final String NAME_PERCENT_4XX_1M = "percent-4xx-1m";
+ protected static final String NAME_PERCENT_4XX_5M = "percent-4xx-5m";
+ protected static final String NAME_PERCENT_4XX_15M = "percent-4xx-15m";
+ protected static final String NAME_PERCENT_5XX_1M = "percent-5xx-1m";
+ protected static final String NAME_PERCENT_5XX_5M = "percent-5xx-5m";
+ protected static final String NAME_PERCENT_5XX_15M = "percent-5xx-15m";
+ protected static final Set COARSE_METER_LEVELS = EnumSet.of(COARSE, ALL);
+ protected static final Set DETAILED_METER_LEVELS = EnumSet.of(DETAILED, ALL);
+
+ protected final MetricRegistry metricRegistry;
+
+ private String name;
+ protected final String prefix;
+
+ // the requests handled by this handler, excluding active
+ protected Timer requests;
+
+ // the number of dispatches seen by this handler, excluding active
+ protected Timer dispatches;
+
+ // the number of active requests
+ protected Counter activeRequests;
+
+ // the number of active dispatches
+ protected Counter activeDispatches;
+
+ // the number of requests currently suspended.
+ protected Counter activeSuspended;
+
+ // the number of requests that have been asynchronously dispatched
+ protected Meter asyncDispatches;
+
+ // the number of requests that expired while suspended
+ protected Meter asyncTimeouts;
+
+ protected final ResponseMeteredLevel responseMeteredLevel;
+ protected List responses;
+ protected Map responseCodeMeters;
+
+ protected Timer getRequests;
+ protected Timer postRequests;
+ protected Timer headRequests;
+ protected Timer putRequests;
+ protected Timer deleteRequests;
+ protected Timer optionsRequests;
+ protected Timer traceRequests;
+ protected Timer connectRequests;
+ protected Timer moveRequests;
+ protected Timer otherRequests;
+
+ /**
+ * Create a new instrumented handler using a given metrics registry.
+ *
+ * @param registry the registry for the metrics
+ */
+ protected AbstractInstrumentedHandler(MetricRegistry registry) {
+ this(registry, null);
+ }
+
+ /**
+ * 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
+ */
+ protected AbstractInstrumentedHandler(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
+ */
+ protected AbstractInstrumentedHandler(MetricRegistry registry, String prefix, ResponseMeteredLevel responseMeteredLevel) {
+ this.responseMeteredLevel = responseMeteredLevel;
+ this.metricRegistry = registry;
+ this.prefix = prefix;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ super.doStart();
+
+ final String prefix = getMetricPrefix();
+
+ this.requests = metricRegistry.timer(name(prefix, NAME_REQUESTS));
+ this.dispatches = metricRegistry.timer(name(prefix, NAME_DISPATCHES));
+
+ this.activeRequests = metricRegistry.counter(name(prefix, NAME_ACTIVE_REQUESTS));
+ this.activeDispatches = metricRegistry.counter(name(prefix, NAME_ACTIVE_DISPATCHES));
+ this.activeSuspended = metricRegistry.counter(name(prefix, NAME_ACTIVE_SUSPENDED));
+
+ this.asyncDispatches = metricRegistry.meter(name(prefix, NAME_ASYNC_DISPATCHES));
+ this.asyncTimeouts = metricRegistry.meter(name(prefix, NAME_ASYNC_TIMEOUTS));
+
+ 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));
+ this.headRequests = metricRegistry.timer(name(prefix, NAME_HEAD_REQUESTS));
+ this.putRequests = metricRegistry.timer(name(prefix, NAME_PUT_REQUESTS));
+ this.deleteRequests = metricRegistry.timer(name(prefix, NAME_DELETE_REQUESTS));
+ this.optionsRequests = metricRegistry.timer(name(prefix, NAME_OPTIONS_REQUESTS));
+ this.traceRequests = metricRegistry.timer(name(prefix, NAME_TRACE_REQUESTS));
+ this.connectRequests = metricRegistry.timer(name(prefix, NAME_CONNECT_REQUESTS));
+ this.moveRequests = metricRegistry.timer(name(prefix, NAME_MOVE_REQUESTS));
+ this.otherRequests = metricRegistry.timer(name(prefix, NAME_OTHER_REQUESTS));
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_4XX_1M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ 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.get(3).getFiveMinuteRate(),
+ requests.getFiveMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_4XX_15M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ 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.get(4).getOneMinuteRate(),
+ requests.getOneMinuteRate());
+ }
+ });
+
+ metricRegistry.register(name(prefix, NAME_PERCENT_5XX_5M), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ 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.get(4).getFifteenMinuteRate(),
+ requests.getFifteenMinuteRate());
+ }
+ });
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ final String prefix = getMetricPrefix();
+
+ metricRegistry.remove(name(prefix, NAME_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_DISPATCHES));
+ metricRegistry.remove(name(prefix, NAME_ACTIVE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_ACTIVE_DISPATCHES));
+ metricRegistry.remove(name(prefix, NAME_ACTIVE_SUSPENDED));
+ metricRegistry.remove(name(prefix, NAME_ASYNC_DISPATCHES));
+ metricRegistry.remove(name(prefix, NAME_ASYNC_TIMEOUTS));
+ metricRegistry.remove(name(prefix, NAME_1XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_2XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_3XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_4XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_5XX_RESPONSES));
+ metricRegistry.remove(name(prefix, NAME_GET_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_POST_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_HEAD_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_PUT_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_DELETE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_OPTIONS_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_TRACE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_CONNECT_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_MOVE_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_OTHER_REQUESTS));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_4XX_1M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_4XX_5M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_4XX_15M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_5XX_1M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_5XX_5M));
+ metricRegistry.remove(name(prefix, NAME_PERCENT_5XX_15M));
+
+ if (responseCodeMeters != null) {
+ responseCodeMeters.keySet().stream()
+ .map(sc -> name(getMetricPrefix(), String.format("%d-responses", sc)))
+ .forEach(metricRegistry::remove);
+ }
+ super.doStop();
+ }
+
+ protected Timer requestTimer(String method) {
+ final HttpMethod m = HttpMethod.fromString(method);
+ if (m == null) {
+ return otherRequests;
+ } else {
+ switch (m) {
+ case GET:
+ return getRequests;
+ case POST:
+ return postRequests;
+ case PUT:
+ return putRequests;
+ case HEAD:
+ return headRequests;
+ case DELETE:
+ return deleteRequests;
+ case OPTIONS:
+ return optionsRequests;
+ case TRACE:
+ return traceRequests;
+ case CONNECT:
+ return connectRequests;
+ case MOVE:
+ return moveRequests;
+ default:
+ return otherRequests;
+ }
+ }
+ }
+
+ protected void updateResponses(Request request, Response response, long start, boolean isHandled) {
+ if (isHandled) {
+ mark(response.getStatus());
+ } else {
+ 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);
+ }
+
+ protected 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();
+ }
+ }
+ }
+
+ protected Meter getResponseCodeMeter(int statusCode) {
+ return responseCodeMeters
+ .computeIfAbsent(statusCode, sc -> metricRegistry
+ .meter(name(getMetricPrefix(), String.format("%d-responses", sc))));
+ }
+
+ protected String getMetricPrefix() {
+ return this.prefix == null ? name(getHandler().getClass(), name) : name(this.prefix, name);
+ }
+}
diff --git a/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/InstrumentedConnectionFactory.java b/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/InstrumentedConnectionFactory.java
new file mode 100644
index 0000000000..679d310f4f
--- /dev/null
+++ b/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/InstrumentedConnectionFactory.java
@@ -0,0 +1,63 @@
+package io.dropwizard.metrics.jetty12;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Timer;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+
+import java.util.List;
+
+public class InstrumentedConnectionFactory extends ContainerLifeCycle implements ConnectionFactory {
+ private final ConnectionFactory connectionFactory;
+ private final Timer timer;
+ private final Counter counter;
+
+ public InstrumentedConnectionFactory(ConnectionFactory connectionFactory, Timer timer) {
+ this(connectionFactory, timer, null);
+ }
+
+ public InstrumentedConnectionFactory(ConnectionFactory connectionFactory, Timer timer, Counter counter) {
+ this.connectionFactory = connectionFactory;
+ this.timer = timer;
+ this.counter = counter;
+ addBean(connectionFactory);
+ }
+
+ @Override
+ public String getProtocol() {
+ return connectionFactory.getProtocol();
+ }
+
+ @Override
+ public List getProtocols() {
+ return connectionFactory.getProtocols();
+ }
+
+ @Override
+ public Connection newConnection(Connector connector, EndPoint endPoint) {
+ final Connection connection = connectionFactory.newConnection(connector, endPoint);
+ connection.addEventListener(new Connection.Listener() {
+ private Timer.Context context;
+
+ @Override
+ public void onOpened(Connection connection) {
+ this.context = timer.time();
+ if (counter != null) {
+ counter.inc();
+ }
+ }
+
+ @Override
+ public void onClosed(Connection connection) {
+ context.stop();
+ if (counter != null) {
+ counter.dec();
+ }
+ }
+ });
+ return connection;
+ }
+}
diff --git a/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/InstrumentedQueuedThreadPool.java b/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/InstrumentedQueuedThreadPool.java
new file mode 100644
index 0000000000..cfcccb0b7f
--- /dev/null
+++ b/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/InstrumentedQueuedThreadPool.java
@@ -0,0 +1,159 @@
+package io.dropwizard.metrics.jetty12;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.RatioGauge;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ThreadFactory;
+
+import static com.codahale.metrics.MetricRegistry.name;
+
+public class InstrumentedQueuedThreadPool extends QueuedThreadPool {
+ private static final String NAME_UTILIZATION = "utilization";
+ private static final String NAME_UTILIZATION_MAX = "utilization-max";
+ private static final String NAME_SIZE = "size";
+ private static final String NAME_JOBS = "jobs";
+ private static final String NAME_JOBS_QUEUE_UTILIZATION = "jobs-queue-utilization";
+
+ private final MetricRegistry metricRegistry;
+ private String prefix;
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry) {
+ this(registry, 200);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads) {
+ this(registry, maxThreads, 8);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads) {
+ this(registry, maxThreads, minThreads, 60000);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("queue") BlockingQueue queue) {
+ this(registry, maxThreads, minThreads, 60000, queue);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout) {
+ this(registry, maxThreads, minThreads, idleTimeout, null);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("queue") BlockingQueue queue) {
+ this(registry, maxThreads, minThreads, idleTimeout, queue, null);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("queue") BlockingQueue queue,
+ @Name("threadGroup") ThreadGroup threadGroup) {
+ this(registry, maxThreads, minThreads, idleTimeout, -1, queue, threadGroup);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("reservedThreads") int reservedThreads,
+ @Name("queue") BlockingQueue queue,
+ @Name("threadGroup") ThreadGroup threadGroup) {
+ this(registry, maxThreads, minThreads, idleTimeout, reservedThreads, queue, threadGroup, null);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("reservedThreads") int reservedThreads,
+ @Name("queue") BlockingQueue queue,
+ @Name("threadGroup") ThreadGroup threadGroup,
+ @Name("threadFactory") ThreadFactory threadFactory) {
+ this(registry, maxThreads, minThreads, idleTimeout, reservedThreads, queue, threadGroup, threadFactory, null);
+ }
+
+ public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+ @Name("maxThreads") int maxThreads,
+ @Name("minThreads") int minThreads,
+ @Name("idleTimeout") int idleTimeout,
+ @Name("reservedThreads") int reservedThreads,
+ @Name("queue") BlockingQueue queue,
+ @Name("threadGroup") ThreadGroup threadGroup,
+ @Name("threadFactory") ThreadFactory threadFactory,
+ @Name("prefix") String prefix) {
+ super(maxThreads, minThreads, idleTimeout, reservedThreads, queue, threadGroup, threadFactory);
+ this.metricRegistry = registry;
+ this.prefix = prefix;
+ }
+
+ public String getPrefix() {
+ return prefix;
+ }
+
+ public void setPrefix(String prefix) {
+ this.prefix = prefix;
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ super.doStart();
+
+ final String prefix = getMetricPrefix();
+
+ metricRegistry.register(name(prefix, NAME_UTILIZATION), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(getThreads() - getIdleThreads(), getThreads());
+ }
+ });
+ metricRegistry.register(name(prefix, NAME_UTILIZATION_MAX), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ return Ratio.of(getThreads() - getIdleThreads(), getMaxThreads());
+ }
+ });
+ metricRegistry.registerGauge(name(prefix, NAME_SIZE), this::getThreads);
+ // This assumes the QueuedThreadPool is using a BlockingArrayQueue or
+ // ArrayBlockingQueue for its queue, and is therefore a constant-time operation.
+ metricRegistry.registerGauge(name(prefix, NAME_JOBS), () -> getQueue().size());
+ metricRegistry.register(name(prefix, NAME_JOBS_QUEUE_UTILIZATION), new RatioGauge() {
+ @Override
+ protected Ratio getRatio() {
+ BlockingQueue queue = getQueue();
+ return Ratio.of(queue.size(), queue.size() + queue.remainingCapacity());
+ }
+ });
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ final String prefix = getMetricPrefix();
+
+ metricRegistry.remove(name(prefix, NAME_UTILIZATION));
+ metricRegistry.remove(name(prefix, NAME_UTILIZATION_MAX));
+ metricRegistry.remove(name(prefix, NAME_SIZE));
+ metricRegistry.remove(name(prefix, NAME_JOBS));
+ metricRegistry.remove(name(prefix, NAME_JOBS_QUEUE_UTILIZATION));
+
+ super.doStop();
+ }
+
+ private String getMetricPrefix() {
+ return this.prefix == null ? name(QueuedThreadPool.class, getName()) : name(this.prefix, getName());
+ }
+}
diff --git a/metrics-jetty12/src/test/java/io/dropwizard/metrics/jetty12/InstrumentedConnectionFactoryTest.java b/metrics-jetty12/src/test/java/io/dropwizard/metrics/jetty12/InstrumentedConnectionFactoryTest.java
new file mode 100644
index 0000000000..a988de2de0
--- /dev/null
+++ b/metrics-jetty12/src/test/java/io/dropwizard/metrics/jetty12/InstrumentedConnectionFactoryTest.java
@@ -0,0 +1,86 @@
+package io.dropwizard.metrics.jetty12;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import org.eclipse.jetty.client.ContentResponse;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.io.Content;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.Callback;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class InstrumentedConnectionFactoryTest {
+ private final MetricRegistry registry = new MetricRegistry();
+ private final Server server = new Server();
+ private final ServerConnector connector =
+ new ServerConnector(server, new InstrumentedConnectionFactory(new HttpConnectionFactory(),
+ registry.timer("http.connections"),
+ registry.counter("http.active-connections")));
+ private final HttpClient client = new HttpClient();
+
+ @Before
+ public void setUp() throws Exception {
+ server.setHandler(new Handler.Abstract() {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback) throws Exception {
+ Content.Sink.write(response, true, "OK", callback);
+ return true;
+ }
+ });
+
+ server.addConnector(connector);
+ server.start();
+
+ client.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ server.stop();
+ client.stop();
+ }
+
+ @Test
+ public void instrumentsConnectionTimes() throws Exception {
+ final ContentResponse response = client.GET("http://localhost:" + connector.getLocalPort() + "/hello");
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+
+ client.stop(); // close the connection
+
+ Thread.sleep(100); // make sure the connection is closed
+
+ final Timer timer = registry.timer(MetricRegistry.name("http.connections"));
+ assertThat(timer.getCount())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void instrumentsActiveConnections() throws Exception {
+ final Counter counter = registry.counter("http.active-connections");
+
+ final ContentResponse response = client.GET("http://localhost:" + connector.getLocalPort() + "/hello");
+ assertThat(response.getStatus())
+ .isEqualTo(200);
+
+ assertThat(counter.getCount())
+ .isEqualTo(1);
+
+ client.stop(); // close the connection
+
+ Thread.sleep(100); // make sure the connection is closed
+
+ assertThat(counter.getCount())
+ .isEqualTo(0);
+ }
+}
diff --git a/metrics-jetty12/src/test/java/io/dropwizard/metrics/jetty12/InstrumentedQueuedThreadPoolTest.java b/metrics-jetty12/src/test/java/io/dropwizard/metrics/jetty12/InstrumentedQueuedThreadPoolTest.java
new file mode 100644
index 0000000000..5a4e4afcf3
--- /dev/null
+++ b/metrics-jetty12/src/test/java/io/dropwizard/metrics/jetty12/InstrumentedQueuedThreadPoolTest.java
@@ -0,0 +1,49 @@
+package io.dropwizard.metrics.jetty12;
+
+import com.codahale.metrics.MetricRegistry;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class InstrumentedQueuedThreadPoolTest {
+ private static final String PREFIX = "prefix";
+
+ private MetricRegistry metricRegistry;
+ private InstrumentedQueuedThreadPool iqtp;
+
+ @Before
+ public void setUp() {
+ metricRegistry = new MetricRegistry();
+ iqtp = new InstrumentedQueuedThreadPool(metricRegistry);
+ }
+
+ @Test
+ public void customMetricsPrefix() throws Exception {
+ iqtp.setPrefix(PREFIX);
+ iqtp.start();
+
+ assertThat(metricRegistry.getNames())
+ .overridingErrorMessage("Custom metrics prefix doesn't match")
+ .allSatisfy(name -> assertThat(name).startsWith(PREFIX));
+
+ iqtp.stop();
+ assertThat(metricRegistry.getMetrics())
+ .overridingErrorMessage("The default metrics prefix was changed")
+ .isEmpty();
+ }
+
+ @Test
+ public void metricsPrefixBackwardCompatible() throws Exception {
+ iqtp.start();
+ assertThat(metricRegistry.getNames())
+ .overridingErrorMessage("The default metrics prefix was changed")
+ .allSatisfy(name -> assertThat(name).startsWith(QueuedThreadPool.class.getName()));
+
+ iqtp.stop();
+ assertThat(metricRegistry.getMetrics())
+ .overridingErrorMessage("The default metrics prefix was changed")
+ .isEmpty();
+ }
+}
diff --git a/pom.xml b/pom.xml
index b795a445b9..7b7a324360 100644
--- a/pom.xml
+++ b/pom.xml
@@ -60,6 +60,7 @@
9.4.51.v20230217
10.0.15
11.0.15
+ 12.0.0
1.7.36
3.24.2
1.14.6
@@ -69,6 +70,7 @@
3.11.0
2.21.1
9+181-r4173-1
+ 6.0.0
dropwizard_metrics
dropwizard
@@ -161,6 +163,10 @@
[17,)
+
+ metrics-jetty12
+ metrics-jetty12-ee10
+