diff --git a/micrometer-core/build.gradle b/micrometer-core/build.gradle index 04ebf2f836..ceb7adced3 100644 --- a/micrometer-core/build.gradle +++ b/micrometer-core/build.gradle @@ -29,8 +29,7 @@ dependencies { compile 'org.hibernate:hibernate-entitymanager:latest.release', optional // server runtime monitoring - // 9.4+ are incompatible with the latest version of wiremock as of 1/4/2019 - compile 'org.eclipse.jetty:jetty-server:9.2.+', optional + compile 'org.eclipse.jetty:jetty-server:9.4.+', optional compile 'org.apache.tomcat.embed:tomcat-embed-core:8.+', optional // apache httpcomponents monitoring @@ -87,8 +86,10 @@ dependencies { // Pin version temporarily to restore builds. testCompile 'org.apache.kafka:kafka-clients:2.3.1' - testCompile 'ru.lanwen.wiremock:wiremock-junit5:1.2.0' - testCompile 'com.github.tomakehurst:wiremock:latest.release' + testCompile 'ru.lanwen.wiremock:wiremock-junit5:1.2.0', { + exclude group: 'com.github.tomakehurst', module: 'wiremock' + } + testCompile 'com.github.tomakehurst:wiremock-jre8:latest.release' testCompile 'de.flapdoodle.embed:de.flapdoodle.embed.mongo:latest.release' } diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/jetty/JettyConnectionMetrics.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/jetty/JettyConnectionMetrics.java new file mode 100644 index 0000000000..ecdc2ecde7 --- /dev/null +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/jetty/JettyConnectionMetrics.java @@ -0,0 +1,89 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micrometer.core.instrument.binder.jetty; + +import io.micrometer.core.instrument.FunctionCounter; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.binder.MeterBinder; +import org.eclipse.jetty.io.ConnectionStatistics; + +import static java.util.Collections.emptyList; + +/** + * {@link MeterBinder} for Jetty's connection metrics.

+ * + * Usage example: + * + *

{@code
+ * MeterRegistry registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock());
+ *
+ * ServerConnectionStatistics serverConnectionStatistics = new ServerConnectionStatistics();
+ * JettyConnectionMetrics connectionMetrics =
+ *     new JettyConnectionMetrics(serverConnectionStatistics, singletonList(Tag.of("protocol", "http")));
+ * connectionMetrics.bindTo(registry);
+ *
+ * Server server = new Server(0);
+ * Connector connector = new ServerConnector(server);
+ * connector.addBean(serverConnectionStatistics);
+ * server.setConnectors(new Connector[] { connector });
+ * }
+ * + * @author Tom Akehurst + * + */ +public class JettyConnectionMetrics implements MeterBinder { + + private final ConnectionStatistics connectionStatistics; + private final Iterable tags; + + public JettyConnectionMetrics(ConnectionStatistics connectionStatistics) { + this(connectionStatistics, emptyList()); + } + + public JettyConnectionMetrics(ConnectionStatistics connectionStatistics, Iterable tags) { + this.connectionStatistics = connectionStatistics; + this.tags = tags; + } + + @Override + public void bindTo(MeterRegistry registry) { + Gauge.builder("jetty.connector.connections.current", connectionStatistics, ConnectionStatistics::getConnections) + .tags(tags) + .description("The current number of open connections") + .register(registry); + Gauge.builder("jetty.connector.connections.max", connectionStatistics, ConnectionStatistics::getConnectionsMax) + .tags(tags) + .description("The maximum number of connections") + .register(registry); + FunctionCounter.builder("jetty.connector.connections.total", connectionStatistics, ConnectionStatistics::getConnectionsTotal) + .tags(tags) + .description("The total number of connections") + .register(registry); + + FunctionCounter.builder("jetty.connector.received", connectionStatistics, ConnectionStatistics::getReceivedBytesRate) + .tags(tags) + .description("The rate of bytes received") + .baseUnit("bytes") + .register(registry); + FunctionCounter.builder("jetty.connector.sent", connectionStatistics, ConnectionStatistics::getSentBytesRate) + .tags(tags) + .description("The rate of bytes sent") + .baseUnit("bytes") + .register(registry); + } +} diff --git a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/jetty/JettyConnectionMetricsTest.java b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/jetty/JettyConnectionMetricsTest.java new file mode 100644 index 0000000000..0dc138db8a --- /dev/null +++ b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/jetty/JettyConnectionMetricsTest.java @@ -0,0 +1,106 @@ +/** + * Copyright 2017 Pivotal Software, Inc. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micrometer.core.instrument.binder.jetty; + +import io.micrometer.core.instrument.MockClock; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.simple.SimpleConfig; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnectionStatistics; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.component.LifeCycle; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.CountDownLatch; + +import static java.util.Collections.singletonList; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class JettyConnectionMetricsTest { + + private SimpleMeterRegistry registry; + private ServerConnector connector; + private Server server; + private CloseableHttpClient client; + + @BeforeEach + void setup() throws Exception { + registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock()); + + ServerConnectionStatistics serverConnectionStatistics = new ServerConnectionStatistics(); + JettyConnectionMetrics connectionMetrics = + new JettyConnectionMetrics(serverConnectionStatistics, singletonList(Tag.of("protocol", "http"))); + connectionMetrics.bindTo(registry); + + server = new Server(0); + connector = new ServerConnector(server); + connector.addBean(serverConnectionStatistics); + server.setConnectors(new Connector[] { connector }); + server.start(); + + client = HttpClients.createDefault(); + } + + @AfterEach + void teardown() throws Exception { + if (server.isRunning()) { + server.stop(); + } + } + + @Test + void contributesConnectorMetrics() throws Exception { + String url = getBaseUrl(); + HttpPost post = new HttpPost(url); + post.setEntity(new StringEntity("some blah whatever text")); + try (CloseableHttpResponse response = client.execute(post)) { + assertThat(registry.get("jetty.connector.connections.max").gauge().value()).isEqualTo(1.0); + assertThat(registry.get("jetty.connector.connections.current").gauge().value()).isEqualTo(1.0); + assertThat(registry.get("jetty.connector.connections.total").functionCounter().count()).isEqualTo(1.0); + } + + CountDownLatch latch = new CountDownLatch(1); + connector.addLifeCycleListener(new AbstractLifeCycle.AbstractLifeCycleListener() { + @Override + public void lifeCycleStopped(LifeCycle event) { + latch.countDown(); + } + }); + // Convenient way to get Jetty to flush its connections, which is required to update the sent/received bytes metrics + server.stop(); + + assertTrue(latch.await(10, SECONDS)); + + assertThat(registry.get("jetty.connector.sent").functionCounter().count()).isGreaterThan(0.0); + assertThat(registry.get("jetty.connector.received").functionCounter().count()).isGreaterThan(0.0); + } + + private String getBaseUrl() { + return "http://localhost:" + connector.getLocalPort(); + } +} diff --git a/micrometer-test/build.gradle b/micrometer-test/build.gradle index 58a0255180..a3837fb980 100644 --- a/micrometer-test/build.gradle +++ b/micrometer-test/build.gradle @@ -4,8 +4,10 @@ dependencies { compile 'org.junit.jupiter:junit-jupiter:latest.release' - compile 'ru.lanwen.wiremock:wiremock-junit5:latest.release' - compile 'com.github.tomakehurst:wiremock:latest.release' + compile 'ru.lanwen.wiremock:wiremock-junit5:latest.release', { + exclude group: 'com.github.tomakehurst', module: 'wiremock' + } + compile 'com.github.tomakehurst:wiremock-jre8:latest.release' compile 'org.mockito:mockito-core:latest.release' testCompile 'org.jsr107.ri:cache-ri-impl:1.0.0'