-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Description
Hi,
I upgraded from Jetty 11 to 12 and now I have to use InstrumentedEE10Handler for recording request metrics. I'm running into a problem with this setup where the active request start is recorded, but the request finish is never and thus most metrics aren't being collected.
Could you please take a look at the below minimal reproducer to check if I'm doing something wrong? Please note that this used to work with Jetty 11 before with InstrumentedHttpChannelListener, but that class is not available anymore for Jetty 12 and I have to use InstrumentedEE10Handler which I cannot figure out how to configure properly.
Please consider this minimal reproducer with two classes, a Jersey resource and a JUnit 5 test class (I am using JDK21 locally on Linux - Fedora 38):
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import java.util.concurrent.CompletableFuture;
@Path("")
public class PingResource {
@GET
public CompletableFuture<String> ping() {
return CompletableFuture.completedFuture("pong");
}
}import static com.codahale.metrics.MetricRegistry.name;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import io.dropwizard.metrics.jetty12.ee10.InstrumentedEE10Handler;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHandler;
import org.eclipse.jetty.server.Server;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.junit.jupiter.api.Test;
class Jetty12DropwizardTest {
@Test
void test() throws Exception {
// Jetty Server on random free port
var server = new Server(0);
// Setup Jersey with our simple async resource returning CompletableFuture
var jerseyConfig = new ResourceConfig()
.registerClasses(PingResource.class);
var jersey = new ServletContainer(jerseyConfig);
// Configure the handler
var contextHandler = new ServletContextHandler();
contextHandler.setContextPath("/");
contextHandler.addServlet(jersey, "/*");
// Add handler instrumentation
var metrics = new MetricRegistry();
var instrumentedHandler = new InstrumentedEE10Handler(metrics);
contextHandler.insertHandler(instrumentedHandler);
// Tell the server to use our handler and start it
server.setHandler(contextHandler);
server.start();
try (var client = HttpClient.newHttpClient()) {
// Ping the server and wait for the response
var response = client.send(
HttpRequest.newBuilder().uri(server.getURI()).GET().build(),
HttpResponse.BodyHandlers.ofString());
assertEquals(200, response.statusCode(), "response code");
assertEquals("pong", response.body(), "response body");
// Print metric counts
metrics.getMetrics().forEach((name, metric) -> System.out.println(name + ": " + printCount(metric)));
// No active requests after the request succeeded
var activeRequestsCounter = metrics.counter(name(ServletHandler.class, "active-requests"));
assertEquals(0, activeRequestsCounter.getCount(), "active requests");
// request recorded
var requestsTimer = metrics.timer(name(ServletHandler.class, "requests"));
assertEquals(1, requestsTimer.getCount(), "requests");
// 200 response recorded
var response200Meter = metrics.meter(name(ServletHandler.class, "2xx-responses"));
assertEquals(1, response200Meter.getCount(), "2xx responses");
} finally {
server.stop();
}
}
String printCount(Metric metric) {
return switch (metric) {
case Counter c -> "count=" + c.getCount();
case Meter m -> "meter=" + m.getCount();
case Timer t -> "timer=" + t.getCount();
case Histogram h -> "histogram=" + h.getCount();
case Gauge<?> g -> "gauge=" + g.getValue();
default -> metric.toString();
};
}
}I expect to see 1xx-responses and all the other stats like requests recorded just like with InstrumentedHttpChannelListener on Jetty 11, but metrics are not being recorded except for an always increasing active-requests metric, which should be 0 in case there are no active requests.
From a brief debugging session I can see that AbstractInstrumentedHandler#updateResponses, the method responsible for collecting metrics after each request, is never called. In InstrumentedEE10Handler there are two places that invoke this method. One is after a synchronous request, which is invoked on state.isInitial() but this is not called, because the state is completed at that point. The another one is from the InstrumentedAsyncListener#onComplete callback, which is again not invoked for some reason.
I'm not sure if I am missing something or the new Dropwizard instrumentation is just not covering this case of async processing with CompletableFutures returned from Jersey resources.