Skip to content

Commit 4d387d4

Browse files
authored
Jetty 11 module (micrometer-metrics#3382)
Introduces a new Jetty 11 instrumentation module. This is a separate module because Jetty 11 requires Java 11 and it has breaking changes from Jetty 9 that we cannot adapt to in the same module (such as the Jakarta namespace change). The implementation is copy and paste from the existing Jetty 9 instrumentation, adapting only to the API changes in Jetty 11. Resolves micrometer-metricsgh-3234
1 parent 1311006 commit 4d387d4

File tree

11 files changed

+834
-13
lines changed

11 files changed

+834
-13
lines changed

micrometer-core/build.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ dependencies {
5959

6060
// server runtime monitoring
6161
optionalApi 'org.eclipse.jetty:jetty-server'
62+
// jakarta servlet
63+
optionalApi 'jakarta.servlet:jakarta.servlet-api:5.+'
6264
optionalApi 'org.eclipse.jetty:jetty-client'
6365
optionalApi 'org.apache.tomcat.embed:tomcat-embed-core'
6466
optionalApi 'org.glassfish.jersey.core:jersey-server'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2022 VMware, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micrometer.core.instrument.binder.http;
17+
18+
import io.micrometer.core.annotation.Incubating;
19+
import io.micrometer.core.instrument.Tag;
20+
import io.micrometer.core.instrument.Tags;
21+
import jakarta.servlet.http.HttpServletRequest;
22+
import jakarta.servlet.http.HttpServletResponse;
23+
24+
/**
25+
* Default {@link HttpServletRequestTagsProvider}.
26+
*
27+
* @author Jon Schneider
28+
* @since 1.10.0
29+
*/
30+
@Incubating(since = "1.10.0")
31+
public class DefaultHttpJakartaServletRequestTagsProvider implements HttpJakartaServletRequestTagsProvider {
32+
33+
@Override
34+
public Iterable<Tag> getTags(HttpServletRequest request, HttpServletResponse response) {
35+
return Tags.of(HttpRequestTags.method(request), HttpRequestTags.status(response),
36+
HttpRequestTags.outcome(response));
37+
}
38+
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2022 VMware, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micrometer.core.instrument.binder.http;
17+
18+
import io.micrometer.core.annotation.Incubating;
19+
import io.micrometer.core.instrument.Tag;
20+
import jakarta.servlet.http.HttpServletRequest;
21+
import jakarta.servlet.http.HttpServletResponse;
22+
23+
/**
24+
* Provides {@link Tag Tags} for HTTP Servlet request handling.
25+
*
26+
* @author Jon Schneider
27+
* @since 1.10.0
28+
*/
29+
@Incubating(since = "1.10.0")
30+
@FunctionalInterface
31+
public interface HttpJakartaServletRequestTagsProvider {
32+
33+
/**
34+
* Provides tags to be associated with metrics for the given {@code request} and
35+
* {@code response} exchange.
36+
* @param request the request
37+
* @param response the response
38+
* @return tags to associate with metrics for the request and response exchange
39+
*/
40+
Iterable<Tag> getTags(HttpServletRequest request, HttpServletResponse response);
41+
42+
}

micrometer-core/src/main/java/io/micrometer/core/instrument/binder/http/HttpRequestTags.java

+30
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@ public static Tag method(HttpServletRequest request) {
5050
return (request != null) ? Tag.of("method", request.getMethod()) : METHOD_UNKNOWN;
5151
}
5252

53+
/**
54+
* Creates a {@code method} tag based on the
55+
* {@link jakarta.servlet.http.HttpServletRequest#getMethod() method} of the given
56+
* {@code request}.
57+
* @param request the request
58+
* @return the method tag whose value is a capitalized method (e.g. GET).
59+
*/
60+
public static Tag method(jakarta.servlet.http.HttpServletRequest request) {
61+
return (request != null) ? Tag.of("method", request.getMethod()) : METHOD_UNKNOWN;
62+
}
63+
5364
/**
5465
* Creates a {@code status} tag based on the status of the given {@code response}.
5566
* @param response the HTTP response
@@ -59,6 +70,15 @@ public static Tag status(HttpServletResponse response) {
5970
return (response != null) ? Tag.of("status", Integer.toString(response.getStatus())) : STATUS_UNKNOWN;
6071
}
6172

73+
/**
74+
* Creates a {@code status} tag based on the status of the given {@code response}.
75+
* @param response the HTTP response
76+
* @return the status tag derived from the status of the response
77+
*/
78+
public static Tag status(jakarta.servlet.http.HttpServletResponse response) {
79+
return (response != null) ? Tag.of("status", Integer.toString(response.getStatus())) : STATUS_UNKNOWN;
80+
}
81+
6282
/**
6383
* Creates an {@code exception} tag based on the {@link Class#getSimpleName() simple
6484
* name} of the class of the given {@code exception}.
@@ -84,4 +104,14 @@ public static Tag outcome(HttpServletResponse response) {
84104
return outcome.asTag();
85105
}
86106

107+
/**
108+
* Creates an {@code outcome} tag based on the status of the given {@code response}.
109+
* @param response the HTTP response
110+
* @return the outcome tag derived from the status of the response
111+
*/
112+
public static Tag outcome(jakarta.servlet.http.HttpServletResponse response) {
113+
Outcome outcome = (response != null) ? Outcome.forStatus(response.getStatus()) : Outcome.UNKNOWN;
114+
return outcome.asTag();
115+
}
116+
87117
}

micrometer-jetty11/build.gradle

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
description 'Micrometer instrumentation for Jetty 11'
2+
3+
dependencies {
4+
api project(":micrometer-core")
5+
api 'org.eclipse.jetty:jetty-server:11.+'
6+
7+
testImplementation 'org.junit.jupiter:junit-jupiter'
8+
testImplementation 'org.assertj:assertj-core'
9+
}
10+
11+
compileJava {
12+
sourceCompatibility = JavaVersion.VERSION_11
13+
targetCompatibility = JavaVersion.VERSION_11
14+
options.release = 11
15+
}
16+
17+
// this module was introduced in 1.10; the following can be removed in later branches
18+
if ("$project.version".startsWith("1.10.")) {
19+
japicmp.enabled = false
20+
downloadBaseline.enabled = false
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/*
2+
* Copyright 2022 VMware, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micrometer.jetty11;
17+
18+
import io.micrometer.core.instrument.*;
19+
import io.micrometer.core.instrument.binder.BaseUnits;
20+
import io.micrometer.core.instrument.binder.http.DefaultHttpJakartaServletRequestTagsProvider;
21+
import io.micrometer.core.instrument.binder.http.HttpJakartaServletRequestTagsProvider;
22+
import jakarta.servlet.AsyncEvent;
23+
import jakarta.servlet.AsyncListener;
24+
import jakarta.servlet.ServletException;
25+
import jakarta.servlet.http.HttpServletRequest;
26+
import jakarta.servlet.http.HttpServletResponse;
27+
import org.eclipse.jetty.http.HttpStatus;
28+
import org.eclipse.jetty.server.AsyncContextEvent;
29+
import org.eclipse.jetty.server.Handler;
30+
import org.eclipse.jetty.server.HttpChannelState;
31+
import org.eclipse.jetty.server.Request;
32+
import org.eclipse.jetty.server.handler.HandlerWrapper;
33+
import org.eclipse.jetty.util.component.Graceful;
34+
35+
import java.io.IOException;
36+
import java.util.concurrent.CompletableFuture;
37+
import java.util.concurrent.atomic.AtomicInteger;
38+
39+
/**
40+
* Adapted from Jetty's <a href=
41+
* "https://github.com/eclipse/jetty.project/blob/jetty-9.4.x/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java">StatisticsHandler</a>.
42+
*
43+
* @author Jon Schneider
44+
* @since 1.10.0
45+
*/
46+
public class TimedHandler extends HandlerWrapper implements Graceful {
47+
48+
private static final String SAMPLE_REQUEST_TIMER_ATTRIBUTE = "__micrometer_timer_sample";
49+
50+
private static final String SAMPLE_REQUEST_LONG_TASK_TIMER_ATTRIBUTE = "__micrometer_ltt_sample";
51+
52+
private final MeterRegistry registry;
53+
54+
private final Iterable<Tag> tags;
55+
56+
private final HttpJakartaServletRequestTagsProvider tagsProvider;
57+
58+
private final Shutdown shutdown = new Shutdown(this) {
59+
@Override
60+
public boolean isShutdownDone() {
61+
return openRequests.activeTasks() == 0;
62+
}
63+
};
64+
65+
private final LongTaskTimer openRequests;
66+
67+
private final Counter asyncDispatches;
68+
69+
private final Counter asyncExpires;
70+
71+
private final AtomicInteger asyncWaits = new AtomicInteger();
72+
73+
public TimedHandler(MeterRegistry registry, Iterable<Tag> tags) {
74+
this(registry, tags, new DefaultHttpJakartaServletRequestTagsProvider());
75+
}
76+
77+
public TimedHandler(MeterRegistry registry, Iterable<Tag> tags,
78+
HttpJakartaServletRequestTagsProvider tagsProvider) {
79+
this.registry = registry;
80+
this.tags = tags;
81+
this.tagsProvider = tagsProvider;
82+
83+
this.openRequests = LongTaskTimer.builder("jetty.server.dispatches.open")
84+
.description("Jetty dispatches that are currently in progress").tags(tags).register(registry);
85+
86+
this.asyncDispatches = Counter.builder("jetty.server.async.dispatches").description("Asynchronous dispatches")
87+
.tags(tags).register(registry);
88+
89+
this.asyncExpires = Counter.builder("jetty.server.async.expires")
90+
.description("Asynchronous operations that timed out before completing").tags(tags).register(registry);
91+
92+
Gauge.builder("jetty.server.async.waits", asyncWaits, AtomicInteger::doubleValue)
93+
.description("Pending asynchronous wait operations").baseUnit(BaseUnits.OPERATIONS).tags(tags)
94+
.register(registry);
95+
}
96+
97+
@Override
98+
public void handle(String path, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
99+
throws IOException, ServletException {
100+
Timer.Sample sample = Timer.start(registry);
101+
LongTaskTimer.Sample requestSample;
102+
103+
HttpChannelState state = baseRequest.getHttpChannelState();
104+
if (state.isInitial()) {
105+
requestSample = openRequests.start();
106+
request.setAttribute(SAMPLE_REQUEST_TIMER_ATTRIBUTE, sample);
107+
request.setAttribute(SAMPLE_REQUEST_LONG_TASK_TIMER_ATTRIBUTE, requestSample);
108+
}
109+
else {
110+
asyncDispatches.increment();
111+
request.setAttribute(SAMPLE_REQUEST_TIMER_ATTRIBUTE, sample);
112+
requestSample = (LongTaskTimer.Sample) request.getAttribute(SAMPLE_REQUEST_LONG_TASK_TIMER_ATTRIBUTE);
113+
}
114+
115+
try {
116+
Handler handler = getHandler();
117+
if (handler != null && !shutdown.isShutdown() && isStarted()) {
118+
handler.handle(path, baseRequest, request, response);
119+
}
120+
else {
121+
if (!baseRequest.isHandled()) {
122+
baseRequest.setHandled(true);
123+
}
124+
if (!baseRequest.getResponse().isCommitted()) {
125+
response.sendError(HttpStatus.SERVICE_UNAVAILABLE_503);
126+
}
127+
}
128+
}
129+
finally {
130+
if (state.isSuspended()) {
131+
if (state.isInitial()) {
132+
state.addListener(onCompletion);
133+
asyncWaits.incrementAndGet();
134+
}
135+
}
136+
else if (state.isInitial()) {
137+
sample.stop(Timer.builder("jetty.server.requests").description("HTTP requests to the Jetty server")
138+
.tags(tagsProvider.getTags(request, response)).tags(tags).register(registry));
139+
140+
requestSample.stop();
141+
142+
// If we have no more dispatches, should we signal shutdown?
143+
if (shutdown.isShutdown()) {
144+
shutdown.check();
145+
}
146+
}
147+
// else onCompletion will handle it.
148+
}
149+
}
150+
151+
private final AsyncListener onCompletion = new AsyncListener() {
152+
@Override
153+
public void onTimeout(AsyncEvent event) {
154+
asyncExpires.increment();
155+
156+
HttpChannelState state = ((AsyncContextEvent) event).getHttpChannelState();
157+
Request request = state.getBaseRequest();
158+
159+
LongTaskTimer.Sample lttSample = (LongTaskTimer.Sample) request
160+
.getAttribute(SAMPLE_REQUEST_LONG_TASK_TIMER_ATTRIBUTE);
161+
lttSample.stop();
162+
}
163+
164+
@Override
165+
public void onStartAsync(AsyncEvent event) {
166+
event.getAsyncContext().addListener(this);
167+
}
168+
169+
@Override
170+
public void onError(AsyncEvent event) {
171+
}
172+
173+
@Override
174+
public void onComplete(AsyncEvent event) {
175+
HttpChannelState state = ((AsyncContextEvent) event).getHttpChannelState();
176+
177+
Request request = state.getBaseRequest();
178+
Timer.Sample sample = (Timer.Sample) request.getAttribute(SAMPLE_REQUEST_TIMER_ATTRIBUTE);
179+
LongTaskTimer.Sample lttSample = (LongTaskTimer.Sample) request
180+
.getAttribute(SAMPLE_REQUEST_LONG_TASK_TIMER_ATTRIBUTE);
181+
182+
if (sample != null) {
183+
sample.stop(Timer.builder("jetty.server.requests").description("HTTP requests to the Jetty server")
184+
.tags(tagsProvider.getTags(request, request.getResponse())).tags(tags).register(registry));
185+
186+
lttSample.stop();
187+
}
188+
189+
asyncWaits.decrementAndGet();
190+
191+
// If we have no more dispatches, should we signal shutdown?
192+
if (shutdown.isShutdown()) {
193+
shutdown.check();
194+
}
195+
}
196+
};
197+
198+
@Override
199+
protected void doStart() throws Exception {
200+
shutdown.cancel();
201+
super.doStart();
202+
}
203+
204+
@Override
205+
protected void doStop() throws Exception {
206+
shutdown.cancel();
207+
super.doStop();
208+
}
209+
210+
@Override
211+
public CompletableFuture<Void> shutdown() {
212+
return shutdown.shutdown();
213+
}
214+
215+
@Override
216+
public boolean isShutdown() {
217+
return shutdown.isShutdown();
218+
}
219+
220+
}

0 commit comments

Comments
 (0)