This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
The following commit(s) were added to refs/heads/master by this push:
new 2aa6c35b67 [Metrics] Expose a few useful Reactor Netty metrics (#2483)
2aa6c35b67 is described below
commit 2aa6c35b6783617b569171b79dc55a48d0c46ac8
Author: Trần Hồng Quân <[email protected]>
AuthorDate: Mon Nov 4 20:58:58 2024 +0700
[Metrics] Expose a few useful Reactor Netty metrics (#2483)
---
docs/modules/servers/partials/configure/jvm.adoc | 12 ++++
pom.xml | 12 ++++
.../james/jmap/metrics/HttpClientMetrics.scala | 74 ++++++++++++++++++++++
.../apache/james/jmap/routes/SessionRoutes.scala | 15 ++++-
.../james/jmap/routes/SessionRoutesTest.scala | 5 +-
server/protocols/jmap/pom.xml | 8 +++
.../java/org/apache/james/jmap/JMAPServer.java | 18 ++++++
7 files changed, 140 insertions(+), 4 deletions(-)
diff --git a/docs/modules/servers/partials/configure/jvm.adoc
b/docs/modules/servers/partials/configure/jvm.adoc
index 6aec817009..2d8fc35833 100644
--- a/docs/modules/servers/partials/configure/jvm.adoc
+++ b/docs/modules/servers/partials/configure/jvm.adoc
@@ -75,6 +75,18 @@ james.jmap.quota.draft.compatibility=true
----
To enable the compatibility.
+== Enable Reactor Netty metrics for JMAP server
+
+Allow to enable
https://projectreactor.io/docs/netty/1.1.19/reference/index.html#_metrics_4[Reactor
Netty metrics] for JMAP server which would provide useful debug information.
+
+Optional. Boolean. Default to false.
+
+Ex in `jvm.properties`:
+----
+james.jmap.reactor.netty.metrics.enabled=true
+----
+To enable the metrics.
+
== Enable S3 metrics
James supports extracting some S3 client-level metrics e.g. number of
connections being used, time to acquire an S3 connection, total time to finish
a S3 request...
diff --git a/pom.xml b/pom.xml
index 8a00b981e7..949aee9571 100644
--- a/pom.xml
+++ b/pom.xml
@@ -654,6 +654,8 @@
<logback.version>1.4.14</logback.version>
<tink.version>1.9.0</tink.version>
<lettuce.core.version>6.3.2.RELEASE</lettuce.core.version>
+ <io.micrometer.core.version>1.13.6</io.micrometer.core.version>
+ <io.micrometer.tracing.version>1.3.5</io.micrometer.tracing.version>
<bouncycastle.version>1.78.1</bouncycastle.version>
@@ -2427,6 +2429,16 @@
<artifactId>lettuce-core</artifactId>
<version>${lettuce.core.version}</version>
</dependency>
+ <dependency>
+ <groupId>io.micrometer</groupId>
+ <artifactId>micrometer-core</artifactId>
+ <version>${io.micrometer.core.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.micrometer</groupId>
+ <artifactId>micrometer-tracing</artifactId>
+ <version>${io.micrometer.tracing.version}</version>
+ </dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec</artifactId>
diff --git
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/metrics/HttpClientMetrics.scala
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/metrics/HttpClientMetrics.scala
new file mode 100644
index 0000000000..5a3087048b
--- /dev/null
+++
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/metrics/HttpClientMetrics.scala
@@ -0,0 +1,74 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you 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 *
+ * *
+ * http://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 org.apache.james.jmap.metrics
+
+import io.micrometer.core.instrument.Metrics.globalRegistry
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry
+import jakarta.inject.{Inject, Singleton}
+import
org.apache.james.jmap.metrics.HttpClientMetrics.{NETTY_CONNECTIONS_ACTIVE,
NETTY_CONNECTIONS_TOTAL, NETTY_DATA_RECEIVED, NETTY_DATA_SENT}
+import org.apache.james.metrics.api.GaugeRegistry
+import reactor.netty.Metrics.{CONNECTIONS_ACTIVE, CONNECTIONS_TOTAL,
DATA_RECEIVED, DATA_SENT, HTTP_SERVER_PREFIX}
+
+object HttpClientMetrics {
+ lazy val NETTY_CONNECTIONS_ACTIVE: String = HTTP_SERVER_PREFIX +
CONNECTIONS_ACTIVE
+ lazy val NETTY_CONNECTIONS_TOTAL: String = HTTP_SERVER_PREFIX +
CONNECTIONS_TOTAL
+ lazy val NETTY_DATA_RECEIVED: String = HTTP_SERVER_PREFIX + DATA_RECEIVED
+ lazy val NETTY_DATA_SENT: String = HTTP_SERVER_PREFIX + DATA_SENT
+}
+
+@Singleton
+case class HttpClientMetrics @Inject()(gaugeRegistry: GaugeRegistry) {
+ private lazy val activeConnectionGauge: GaugeRegistry.SettableGauge[Integer]
= gaugeRegistry.settableGauge(s"jmap.$NETTY_CONNECTIONS_ACTIVE")
+ private lazy val totalConnectionGauge: GaugeRegistry.SettableGauge[Integer]
= gaugeRegistry.settableGauge(s"jmap.$NETTY_CONNECTIONS_TOTAL")
+ private lazy val dataReceivedGauge: GaugeRegistry.SettableGauge[Integer] =
gaugeRegistry.settableGauge(s"jmap.$NETTY_DATA_RECEIVED")
+ private lazy val dataSentGauge: GaugeRegistry.SettableGauge[Integer] =
gaugeRegistry.settableGauge(s"jmap.$NETTY_DATA_SENT")
+ private lazy val nettyCompositeMeterRegistry = globalRegistry.add(new
SimpleMeterRegistry())
+
+ def update(): Unit = {
+ updateActiveConnectionGauge()
+ updateTotalConnectionGauge()
+ updateDateReceivedGauge()
+ updateDataSentGauge()
+ }
+
+ private def updateActiveConnectionGauge(): Unit =
+ Option(nettyCompositeMeterRegistry.find(NETTY_CONNECTIONS_ACTIVE))
+ .flatMap(search => Option(search.gauge()))
+ .flatMap(gauge => Option(gauge.value()))
+ .foreach(double => activeConnectionGauge.setValue(double.intValue))
+
+ private def updateTotalConnectionGauge(): Unit =
+ Option(nettyCompositeMeterRegistry.find(NETTY_CONNECTIONS_TOTAL))
+ .flatMap(search => Option(search.gauge()))
+ .flatMap(gauge => Option(gauge.value()))
+ .foreach(double => totalConnectionGauge.setValue(double.intValue))
+
+ private def updateDateReceivedGauge(): Unit =
+ Option(nettyCompositeMeterRegistry.find(NETTY_DATA_RECEIVED))
+ .flatMap(search => Option(search.summary()))
+ .flatMap(summary => Option(summary.totalAmount()))
+ .foreach(double => dataReceivedGauge.setValue(double.intValue))
+
+ private def updateDataSentGauge(): Unit =
+ Option(nettyCompositeMeterRegistry.find(NETTY_DATA_SENT))
+ .flatMap(search => Option(search.summary()))
+ .flatMap(summary => Option(summary.totalAmount()))
+ .foreach(double => dataSentGauge.setValue(double.intValue))
+}
diff --git
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/SessionRoutes.scala
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/SessionRoutes.scala
index f1fdafdd3e..128e1c838b 100644
---
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/SessionRoutes.scala
+++
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/SessionRoutes.scala
@@ -23,18 +23,20 @@ import java.nio.charset.StandardCharsets
import java.util.stream.Stream
import io.netty.handler.codec.http.HttpHeaderNames.{CONTENT_LENGTH,
CONTENT_TYPE}
-import io.netty.handler.codec.http.HttpResponseStatus.{BAD_REQUEST,
INTERNAL_SERVER_ERROR, OK, UNAUTHORIZED}
-import io.netty.handler.codec.http.{HttpMethod, HttpResponseStatus}
+import io.netty.handler.codec.http.HttpMethod
+import io.netty.handler.codec.http.HttpResponseStatus.{INTERNAL_SERVER_ERROR,
OK, UNAUTHORIZED}
import jakarta.inject.{Inject, Named}
import org.apache.commons.lang3.tuple.Pair
import org.apache.james.core.Username
import org.apache.james.jmap.HttpConstants.{JSON_CONTENT_TYPE,
JSON_CONTENT_TYPE_UTF8}
import org.apache.james.jmap.JMAPRoutes.CORS_CONTROL
+import org.apache.james.jmap.JMAPServer.REACTOR_NETTY_METRICS_ENABLE
import org.apache.james.jmap.core.{JmapRfc8621Configuration, ProblemDetails,
Session, UrlPrefixes}
import org.apache.james.jmap.exceptions.UnauthorizedException
import org.apache.james.jmap.http.Authenticator
import org.apache.james.jmap.http.rfc8621.InjectionKeys
import org.apache.james.jmap.json.ResponseSerializer
+import org.apache.james.jmap.metrics.HttpClientMetrics
import org.apache.james.jmap.routes.SessionRoutes.{JMAP_SESSION, LOGGER,
WELL_KNOWN_JMAP}
import org.apache.james.jmap.{Endpoint, JMAPRoute, JMAPRoutes}
import org.apache.james.mailbox.MailboxSession
@@ -56,7 +58,8 @@ object SessionRoutes {
class SessionRoutes @Inject()(@Named(InjectionKeys.RFC_8621) val
authenticator: Authenticator,
val sessionSupplier: SessionSupplier,
val delegationStore: DelegationStore,
- val jmapRfc8621Configuration:
JmapRfc8621Configuration) extends JMAPRoutes {
+ val jmapRfc8621Configuration:
JmapRfc8621Configuration,
+ val httpClientMetrics: HttpClientMetrics)
extends JMAPRoutes {
private val generateSession: JMAPRoute.Action =
(request, response) =>
SMono.fromPublisher(authenticator.authenticate(request))
@@ -73,6 +76,12 @@ class SessionRoutes @Inject()(@Named(InjectionKeys.RFC_8621)
val authenticator:
.flatMap(session => sendRespond(session, response))
.onErrorResume(throwable => SMono.fromPublisher(errorHandling(throwable,
response)))
.asJava()
+ .doOnSuccess(_ => updateHttpClientMetricsIfNeeded())
+
+ private def updateHttpClientMetricsIfNeeded(): Unit =
+ if (REACTOR_NETTY_METRICS_ENABLE) {
+ httpClientMetrics.update()
+ }
private val redirectToSession: JMAPRoute.Action =
JMAPRoutes.redirectTo(JMAP_SESSION)
diff --git
a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/SessionRoutesTest.scala
b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/SessionRoutesTest.scala
index 3c29c40f03..573252704c 100644
---
a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/SessionRoutesTest.scala
+++
b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/SessionRoutesTest.scala
@@ -34,9 +34,11 @@ import
org.apache.james.jmap.core.JmapRfc8621Configuration.URL_PREFIX_DEFAULT
import org.apache.james.jmap.core.UuidState.INSTANCE
import org.apache.james.jmap.core.{DefaultCapabilities,
JmapRfc8621Configuration}
import org.apache.james.jmap.http.Authenticator
+import org.apache.james.jmap.metrics.HttpClientMetrics
import org.apache.james.jmap.routes.SessionRoutesTest.{BOB, TEST_CONFIGURATION}
import org.apache.james.jmap.{JMAPConfiguration, JMAPRoutesHandler,
JMAPServer, Version, VersionParser}
import org.apache.james.mailbox.MailboxSession
+import org.apache.james.metrics.api.NoopGaugeRegistry
import org.apache.james.user.api.DelegationStore
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito._
@@ -74,7 +76,8 @@ class SessionRoutesTest extends AnyFlatSpec with
BeforeAndAfter with Matchers {
sessionSupplier = new
SessionSupplier(DefaultCapabilities.supported(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION),
JmapRfc8621Configuration.LOCALHOST_CONFIGURATION),
delegationStore = mockDelegationStore,
authenticator = mockedAuthFilter,
- jmapRfc8621Configuration =
JmapRfc8621Configuration.LOCALHOST_CONFIGURATION)
+ jmapRfc8621Configuration =
JmapRfc8621Configuration.LOCALHOST_CONFIGURATION,
+ httpClientMetrics = HttpClientMetrics(new NoopGaugeRegistry))
jmapServer = new JMAPServer(
TEST_CONFIGURATION,
Set(new JMAPRoutesHandler(Version.RFC8621, sessionRoutes)).asJava,
diff --git a/server/protocols/jmap/pom.xml b/server/protocols/jmap/pom.xml
index 2b1ae4ad5b..0318709633 100644
--- a/server/protocols/jmap/pom.xml
+++ b/server/protocols/jmap/pom.xml
@@ -72,6 +72,14 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
+ <dependency>
+ <groupId>io.micrometer</groupId>
+ <artifactId>micrometer-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.micrometer</groupId>
+ <artifactId>micrometer-tracing</artifactId>
+ </dependency>
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
diff --git
a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
index d40ca96ae8..9c72329a11 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
@@ -21,10 +21,13 @@ package org.apache.james.jmap;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
+import static reactor.netty.Metrics.HTTP_CLIENT_PREFIX;
+import static reactor.netty.Metrics.URI;
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Function;
import java.util.stream.Stream;
import jakarta.annotation.PreDestroy;
@@ -39,12 +42,16 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Multimap;
+import io.micrometer.core.instrument.Metrics;
+import io.micrometer.core.instrument.config.MeterFilter;
import io.netty.handler.codec.http.HttpMethod;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import reactor.netty.http.server.HttpServerRequest;
public class JMAPServer implements Startable {
+ public static final boolean REACTOR_NETTY_METRICS_ENABLE =
Boolean.parseBoolean(System.getProperty("james.jmap.reactor.netty.metrics.enabled",
"false"));
+ private static final int REACTOR_NETTY_METRICS_MAX_URI_TAGS = 100;
private static final int RANDOM_PORT = 0;
private final JMAPConfiguration configuration;
@@ -90,10 +97,21 @@ public class JMAPServer implements Startable {
.orElse(RANDOM_PORT))
.handle((request, response) ->
handleVersionRoute(request).handleRequest(request, response))
.wiretap(wireTapEnabled())
+ .metrics(REACTOR_NETTY_METRICS_ENABLE, Function.identity())
.bindNow());
+
+ if (REACTOR_NETTY_METRICS_ENABLE) {
+ configureReactorNettyMetrics();
+ }
}
}
+ private void configureReactorNettyMetrics() {
+ Metrics.globalRegistry
+ .config()
+ .meterFilter(MeterFilter.maximumAllowableTags(HTTP_CLIENT_PREFIX,
URI, REACTOR_NETTY_METRICS_MAX_URI_TAGS, MeterFilter.deny()));
+ }
+
private boolean wireTapEnabled() {
return
LoggerFactory.getLogger("org.apache.james.jmap.wire").isTraceEnabled();
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]