From cd434207ccf257bb12dbfa109708be885d0eaa71 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Wed, 14 Dec 2022 09:16:16 +0100 Subject: [PATCH] Register Redis client metrics into the micrometer registry - Register operation metrics into the default micrometer registry - Add documentation explaining the metrics and how to disable them --- docs/src/main/asciidoc/redis-reference.adoc | 107 +++++++++-- extensions/micrometer/deployment/pom.xml | 11 +- .../binder/RedisBinderProcessor.java | 30 ++++ .../deployment/GlobalDefaultDisabledTest.java | 1 + .../deployment/MetricFiltersTest.java | 3 +- .../MetricsFromMetricsFactoryTestCase.java | 1 + .../deployment/MicrometerDisabledTest.java | 1 + .../binder/GrpcMetricsDisabledTest.java | 1 + .../binder/HttpDevModeConfigTest.java | 1 + .../KafkaClientMetricsDisabledTest.java | 1 + .../KafkaStreamsMetricsDisabledTest.java | 1 + .../RedisClientMetricsDisabledTest.java | 34 ++++ .../binder/RedisClientMetricsTest.java | 101 +++++++++++ .../deployment/binder/UriTagCorsTest.java | 1 + .../deployment/binder/UriTagTest.java | 1 + .../UriTagWithHttpApplicationRootTest.java | 1 + .../binder/UriTagWithHttpRootTest.java | 1 + .../binder/UriWithMaxTagMeterFilterTest.java | 1 + .../binder/VertxWithHttpDisabledTest.java | 1 + .../binder/VertxWithHttpEnabledTest.java | 1 + .../export/AllRegistriesDisabledTest.java | 1 + .../JsonAndPrometheusRegistryEnabledTest.java | 1 + .../export/JsonRegistryEnabledTest.java | 1 + .../export/NoDefaultPrometheusTest.java | 1 + .../export/PrometheusEnabledTest.java | 1 + .../export/SecondPrometheusTest.java | 1 + .../MicrometerCounterInterceptorTest.java | 1 + .../MicrometerTimedInterceptorTest.java | 1 + .../binder/mpmetrics/MpMetricNamingTest.java | 1 + .../mpmetrics/MpMetricRegistrationTest.java | 3 +- extensions/micrometer/runtime/pom.xml | 6 + .../binder/redis/RedisMetricsBean.java | 73 ++++++++ .../runtime/config/MicrometerConfig.java | 2 + .../runtime/config/RedisConfigGroup.java | 35 ++++ .../deployment/RedisMetricsBuildItem.java | 18 ++ .../redis/runtime/client/ObservableRedis.java | 168 ++++++++++++++++++ .../client/ObservableRedisMetrics.java | 22 +++ .../runtime/client/RedisClientRecorder.java | 38 ++-- .../client/VertxRedisClientFactory.java | 4 +- 39 files changed, 639 insertions(+), 39 deletions(-) create mode 100644 extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/RedisBinderProcessor.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/RedisClientMetricsDisabledTest.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/RedisClientMetricsTest.java create mode 100644 extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/redis/RedisMetricsBean.java create mode 100644 extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/RedisConfigGroup.java create mode 100644 extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/RedisMetricsBuildItem.java create mode 100644 extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/ObservableRedis.java create mode 100644 extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/ObservableRedisMetrics.java diff --git a/docs/src/main/asciidoc/redis-reference.adoc b/docs/src/main/asciidoc/redis-reference.adoc index 49aa673eac658..d46772acb4080 100644 --- a/docs/src/main/asciidoc/redis-reference.adoc +++ b/docs/src/main/asciidoc/redis-reference.adoc @@ -134,9 +134,9 @@ public class RedisExample { } ---- -TIP: When using `@RedisClientName`, you can omit the `@Inject` annotation. +TIP: You can omit the `@Inject` annotation when using `@RedisClientName`. -== Connecting to Redis +== Connection to Redis The Redis extension can operate in 4 distinct modes: @@ -152,7 +152,7 @@ The connection url is configured with the `quarkus.redis.hosts` (or `quarkus.red quarkus.redis.hosts=redis://[:password@]host[:port][/db-number] ---- -=== Using Unix Socket +=== Unix Socket When using unix-socket, you need: @@ -196,7 +196,7 @@ quarkus.redis.hosts=redis://localhost:7000 quarkus.redis.client-type=replication ---- -=== Connecting to Redis Cloud +=== Redis Cloud To connect to redis cloud, you need the following properties: @@ -213,13 +213,13 @@ We recommend the latter, and if possible, using secrets or an environment variab The associated environment variable is `QUARKUS_REDIS_PASSWORD`, or `QUARKUS_REDIS__PASSWORD` for named clients. -== Using the high-level clients (data sources) +== Quarkus client API for data sources Quarkus exposes a high-level API on top of Redis. This API is type-safe and structured around the notion of _group_, inherited from the https://redis.io/commands/command-docs/[Redis command organization]. This API lets you execute Redis commands more conveniently and safely. -=== Injecting data sources +=== Inject data sources For each configured Redis client, two Redis data sources are exposed: @@ -334,7 +334,7 @@ To store binary data, use `byte[]`. The `value` group is used to manipulate https://redis.io/docs/manual/data-types/#strings[Redis Strings]. Thus, this group is not limited to Java Strings but can be used for integers (like a counter) or binary content (like images). -==== Caching values +==== Work with cached values You can use Redis as a cache using the `setex` command, which stores a given value to a given key for a given duration. The following snippet shows how such a command can be used to store `BusinessObject` for 1 second. @@ -377,7 +377,7 @@ The `set` method can also receive a `SetArgs` argument that modify the behavior: - `keepttl()` - Retain the time to live associated with the key. ==== -==== Storing binary data +==== Store binary data Redis _strings_ can be used to store binary data, such as images. In this case, we will use `byte[]` as value type: @@ -411,7 +411,7 @@ public static class MyBinaryRepository { } ---- -==== Storing a counter +==== Store a counter You can store counters in Redis as demonstrated below: @@ -454,10 +454,10 @@ There are other methods that can be useful to manipulate counters, such as: - `set` - to set an initial value if needed - `decr` and `decrby` - allows decrementing the stored value -==== Using pub/sub +==== Communicate with pub/sub Redis allows sending _messages_ to channels and listening for these messages. -These features are available from the the `pubsub` group. +These features are available from the `pubsub` group. The following snippets shows how a _cache_ can emit a `Notification` after every `set`, and how a subscriber can receive the notification. @@ -521,7 +521,7 @@ public static class MyCache { } ---- -==== Using transactions +==== Redis transactions Redis transactions are slightly different from relational database transactions. Redis transactions are a batch of commands executed altogether. @@ -585,7 +585,7 @@ TransactionResult result = ds.withTransaction(tx -> { IMPORTANT: You cannot use the pub/sub feature from within a transaction. -==== Using optimistic locking +==== Optimistic locking To use optimistic locking, you need to use a variant of the `withTransaction` method, allowing the execution of code before the transaction starts. In other words, it will be executed as follows: @@ -634,7 +634,7 @@ These commands must not modify the watched keys. The transaction is aborted if the pre-transaction block throws an exception (or produces a failure when using the reactive API). -==== Executing custom commands +==== Execute custom commands To execute a custom command, or a command not supported by the API, use the following approach: @@ -708,7 +708,7 @@ INCR counter EXEC ---- -=== Loading configuration +=== Configuration The data is loaded when the application starts. By default, it drops the whole database before importing. @@ -744,7 +744,7 @@ So when you access the `/q/health/ready` endpoint of your application you will h This behavior can be disabled by setting the `quarkus.redis.health.enabled` property to `false` in your `application.properties`. -== Providing Redis Hosts Programmatically +== Programmatic Redis Hosts The `RedisHostsProvider` programmatically provides redis hosts. This allows for configuration of properties like redis connection password coming from other sources. @@ -775,7 +775,7 @@ The host provider can be used to configure the redis client like shown below quarkus.redis.hosts-provider-name=hosts-provider ---- -== Customizing the Redis options programmatically +== Customize the Redis options programmatically You can expose a bean implementing the `io.quarkus.redis.client.RedisOptionsCustomizer` interface to customize the Redis client options. The bean is called for each configured Redis client: @@ -802,7 +802,78 @@ public static class MyExampleCustomizer implements RedisOptionsCustomizer { See xref:redis-dev-services.adoc[Redis Dev Service]. +== Redis client metrics + +=== Enable metrics collection + +Redis client metrics are automatically enabled when the application also uses the xref:micrometer.adoc[`quarkus-micrometer`] extension. +Micrometer collects the metrics of all the Redis clients implemented by the application. + +As an example, if you export the metrics to Prometheus, you will get: + +[source, text] +---- +# HELP redis_commands_duration_seconds The duration of the operations (commands of batches +# TYPE redis_commands_duration_seconds summary +redis_commands_duration_seconds_count{client_name="",} 3.0 +redis_commands_duration_seconds_sum{client_name="",} 0.047500042 +# HELP redis_commands_duration_seconds_max The duration of the operations (commands of batches +# TYPE redis_commands_duration_seconds_max gauge +redis_commands_duration_seconds_max{client_name="",} 0.033273167 +# HELP redis_pool_active The number of resources from the pool currently used +# TYPE redis_pool_active gauge +redis_pool_active{pool_name="",pool_type="redis",} 0.0 +# HELP redis_pool_ratio Pool usage ratio +# TYPE redis_pool_ratio gauge +redis_pool_ratio{pool_name="",pool_type="redis",} 0.0 +# HELP redis_pool_queue_size Number of pending elements in the waiting queue +# TYPE redis_pool_queue_size gauge +redis_pool_queue_size{pool_name="",pool_type="redis",} 0.0 +# HELP redis_commands_failure_total The number of operations (commands or batches) that have been failed +# TYPE redis_commands_failure_total counter +redis_commands_failure_total{client_name="",} 0.0 +# HELP redis_commands_success_total The number of operations (commands or batches) that have been executed successfully +# TYPE redis_commands_success_total counter +redis_commands_success_total{client_name="",} 3.0 +# HELP redis_pool_idle The number of resources from the pool currently used +# TYPE redis_pool_idle gauge +redis_pool_idle{pool_name="",pool_type="redis",} 6.0 +# HELP redis_pool_completed_total Number of times resources from the pool have been acquired +# TYPE redis_pool_completed_total counter +redis_pool_completed_total{pool_name="",pool_type="redis",} 3.0 +# HELP redis_commands_count_total The number of operations (commands or batches) executed +# TYPE redis_commands_count_total counter +redis_commands_count_total{client_name="",} 3.0 +# HELP redis_pool_usage_seconds Time spent using resources from the pool +# TYPE redis_pool_usage_seconds summary +redis_pool_usage_seconds_count{pool_name="",pool_type="redis",} 3.0 +redis_pool_usage_seconds_sum{pool_name="",pool_type="redis",} 0.024381375 +# HELP redis_pool_usage_seconds_max Time spent using resources from the pool +# TYPE redis_pool_usage_seconds_max gauge +redis_pool_usage_seconds_max{pool_name="",pool_type="redis",} 0.010671542 +# HELP redis_pool_queue_delay_seconds Time spent in the waiting queue before being processed +# TYPE redis_pool_queue_delay_seconds summary +redis_pool_queue_delay_seconds_count{pool_name="",pool_type="redis",} 3.0 +redis_pool_queue_delay_seconds_sum{pool_name="",pool_type="redis",} 0.022341249 +# HELP redis_pool_queue_delay_seconds_max Time spent in the waiting queue before being processed +# TYPE redis_pool_queue_delay_seconds_max gauge +redis_pool_queue_delay_seconds_max{pool_name="",pool_type="redis",} 0.021926083 +---- + +The Redis client name can be found in the _tags_. + +The metrics contain both the Redis connection pool metrics (`redis_pool_*`) and the metrics about the command execution (`redis_commands_*`) such as the number of command, successes, failures, and durations. + +=== Disable metrics collection + +To disable the Redis client metrics when `quarkus-micrometer` is used, add the following property to the application configuration: + +[source, properties] +---- +quarkus.micrometer.binder.redis.enabled=false +---- + [[redis-configuration-reference]] -== Configuration Reference +== Configuration reference include::{generated-dir}/config/quarkus-redis-client.adoc[opts=optional, leveloffset=+1] diff --git a/extensions/micrometer/deployment/pom.xml b/extensions/micrometer/deployment/pom.xml index d566d65767896..cf364f85d613d 100644 --- a/extensions/micrometer/deployment/pom.xml +++ b/extensions/micrometer/deployment/pom.xml @@ -36,6 +36,10 @@ io.quarkus quarkus-vertx-http-deployment + + io.quarkus + quarkus-redis-client-deployment + io.quarkus quarkus-resteasy-common-spi @@ -97,6 +101,12 @@ quarkus-reactive-routes-deployment test + + + io.quarkus + quarkus-redis-client-deployment + test + io.rest-assured @@ -109,7 +119,6 @@ microprofile-metrics-api test - diff --git a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/RedisBinderProcessor.java b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/RedisBinderProcessor.java new file mode 100644 index 0000000000000..10f8a9da11707 --- /dev/null +++ b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/RedisBinderProcessor.java @@ -0,0 +1,30 @@ +package io.quarkus.micrometer.deployment.binder; + +import java.util.function.BooleanSupplier; + +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.micrometer.runtime.MicrometerRecorder; +import io.quarkus.micrometer.runtime.config.MicrometerConfig; + +public class RedisBinderProcessor { + + static final String OBSERVABLE_CLIENT = "io.quarkus.redis.runtime.client.ObservableRedis"; + static final String METRICS_BEAN_CLASS = "io.quarkus.micrometer.runtime.binder.redis.RedisMetricsBean"; + + static final Class OBSERVABLE_CLIENT_CLASS = MicrometerRecorder.getClassForName(OBSERVABLE_CLIENT); + + static class RedisMetricsSupportEnabled implements BooleanSupplier { + MicrometerConfig mConfig; + + public boolean getAsBoolean() { + return OBSERVABLE_CLIENT_CLASS != null && mConfig.checkBinderEnabledWithDefault(mConfig.binder.redis); + } + } + + @BuildStep(onlyIf = RedisMetricsSupportEnabled.class) + AdditionalBeanBuildItem addRedisClientMetric() { + return AdditionalBeanBuildItem.unremovableOf(METRICS_BEAN_CLASS); + } + +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/GlobalDefaultDisabledTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/GlobalDefaultDisabledTest.java index c6419db5612ff..6dface5b65a43 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/GlobalDefaultDisabledTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/GlobalDefaultDisabledTest.java @@ -21,6 +21,7 @@ public class GlobalDefaultDisabledTest { .withConfigurationResource("test-logging.properties") .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withApplicationRoot((jar) -> jar .addClasses(Util.class)); diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/MetricFiltersTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/MetricFiltersTest.java index cb3448960789b..33aef40de5d4c 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/MetricFiltersTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/MetricFiltersTest.java @@ -21,7 +21,8 @@ public class MetricFiltersTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar .addClasses(AnnotatedFilter.class, NonAnnotatedFilter.class, - MeterFilterProducer.class)); + MeterFilterProducer.class)) + .overrideConfigKey("quarkus.redis.devservices.enabled", "false"); @Test public void testCDIFilters() { diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/MetricsFromMetricsFactoryTestCase.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/MetricsFromMetricsFactoryTestCase.java index 42dff99a057ee..3714a7f0c59a5 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/MetricsFromMetricsFactoryTestCase.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/MetricsFromMetricsFactoryTestCase.java @@ -28,6 +28,7 @@ public class MetricsFromMetricsFactoryTestCase { .withConfigurationResource("test-logging.properties") .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withApplicationRoot((jar) -> jar .addClass(MeasureThis.class)); diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/MicrometerDisabledTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/MicrometerDisabledTest.java index 59d2655dae515..7a95655bedc1e 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/MicrometerDisabledTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/MicrometerDisabledTest.java @@ -20,6 +20,7 @@ public class MicrometerDisabledTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar .addAsResource(new StringAsset("quarkus.micrometer.enabled=false"), "application.properties")) + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .assertException(t -> { Assertions.assertEquals(DeploymentException.class, t.getClass()); }); diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/GrpcMetricsDisabledTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/GrpcMetricsDisabledTest.java index 41bb330f1f20b..2652ada4e6adf 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/GrpcMetricsDisabledTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/GrpcMetricsDisabledTest.java @@ -20,6 +20,7 @@ public class GrpcMetricsDisabledTest { .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withEmptyApplication(); @Inject diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/HttpDevModeConfigTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/HttpDevModeConfigTest.java index 7cab1d1dc0f37..8b266ea4fad31 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/HttpDevModeConfigTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/HttpDevModeConfigTest.java @@ -23,6 +23,7 @@ public class HttpDevModeConfigTest { "quarkus.micrometer.binder.http-server.enabled=true\n" + "quarkus.micrometer.binder.http-server.ignore-patterns=/http\n" + "quarkus.micrometer.binder.vertx.enabled=true\n" + + "quarkus.redis.devservices.enabled=false\n" + "orange=banana"), "application.properties")); @Test diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/KafkaClientMetricsDisabledTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/KafkaClientMetricsDisabledTest.java index ac02004f1b963..5083a70d7adcd 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/KafkaClientMetricsDisabledTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/KafkaClientMetricsDisabledTest.java @@ -19,6 +19,7 @@ public class KafkaClientMetricsDisabledTest { .overrideConfigKey("quarkus.micrometer.binder.kafka.enabled", "true") .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withEmptyApplication(); @Inject diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/KafkaStreamsMetricsDisabledTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/KafkaStreamsMetricsDisabledTest.java index fb534eb77a24b..d4cfd8379828d 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/KafkaStreamsMetricsDisabledTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/KafkaStreamsMetricsDisabledTest.java @@ -19,6 +19,7 @@ public class KafkaStreamsMetricsDisabledTest { .overrideConfigKey("quarkus.micrometer.binder.kafka.enabled", "true") .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withEmptyApplication(); @Inject diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/RedisClientMetricsDisabledTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/RedisClientMetricsDisabledTest.java new file mode 100644 index 0000000000000..d4b0f669da5a5 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/RedisClientMetricsDisabledTest.java @@ -0,0 +1,34 @@ +package io.quarkus.micrometer.deployment.binder; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import javax.enterprise.inject.Instance; +import javax.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.redis.runtime.client.ObservableRedisMetrics; +import io.quarkus.test.QuarkusUnitTest; + +public class RedisClientMetricsDisabledTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder.redis.enabled", "false") + .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") + .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") + .withEmptyApplication(); + + @Inject + Instance bean; + + @Test + void testNoInstancePresentIfNoRedisClientsClass() { + assertTrue(bean.isUnsatisfied(), + "No redis metrics bean"); + } + +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/RedisClientMetricsTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/RedisClientMetricsTest.java new file mode 100644 index 0000000000000..f5412d95ded76 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/RedisClientMetricsTest.java @@ -0,0 +1,101 @@ + +package io.quarkus.micrometer.deployment.binder; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.inject.Inject; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.search.MeterNotFoundException; +import io.quarkus.redis.datasource.RedisDataSource; +import io.quarkus.test.QuarkusUnitTest; +import io.vertx.redis.client.Command; +import io.vertx.redis.client.Redis; +import io.vertx.redis.client.Request; + +@DisabledOnOs(OS.WINDOWS) +public class RedisClientMetricsTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest(); + + @Inject + RedisDataSource ds; + + @Inject + Redis redis; + + @Inject + MeterRegistry registry; + + @Test + void testCommands() { + double count = 0.0; + double fail = 0.0; + double succ = 0.0; + + try { + Counter failures = registry.get("redis.commands.failure").counter(); + Counter counter = registry.get("redis.commands.count").counter(); + Counter success = registry.get("redis.commands.success").counter(); + count = counter.count(); + fail = failures.count(); + succ = success.count(); + } catch (MeterNotFoundException e) { + // ignore it + } + + ds.value(Integer.class).incr("counter"); + ds.value(String.class).set("foo", "bar"); + + Assertions.assertEquals(count + 2, registry.get("redis.commands.count").counter().count()); + Assertions.assertEquals(succ + 2, registry.get("redis.commands.success").counter().count()); + Assertions.assertEquals(fail + 0, registry.get("redis.commands.failure").counter().count()); + + Assertions.assertEquals(count + 2, registry.get("redis.commands.duration").timer().count()); + Assertions.assertTrue(registry.get("redis.commands.duration").timer().mean(TimeUnit.NANOSECONDS) > 0); + + Assertions.assertThrows(Exception.class, () -> ds.value(String.class).incr("foo")); + Assertions.assertEquals(fail + 1, registry.get("redis.commands.failure").counter().count()); + } + + @Test + void testBatch() { + double count = 0.0; + double fail = 0.0; + double succ = 0.0; + + try { + Counter failures = registry.get("redis.commands.failure").counter(); + Counter counter = registry.get("redis.commands.count").counter(); + Counter success = registry.get("redis.commands.success").counter(); + count = counter.count(); + fail = failures.count(); + succ = success.count(); + } catch (MeterNotFoundException e) { + // ignore it + } + + redis.batch(List.of( + Request.cmd(Command.SET).arg("b1").arg("value"), + Request.cmd(Command.SET).arg("b2").arg("value"), + Request.cmd(Command.INCR).arg("c1"))).toCompletionStage().toCompletableFuture().join(); + + Assertions.assertEquals(count + 1, registry.get("redis.commands.count").counter().count()); + Assertions.assertEquals(succ + 1, registry.get("redis.commands.success").counter().count()); + Assertions.assertEquals(fail + 0, registry.get("redis.commands.failure").counter().count()); + + Assertions.assertEquals(count + 1, registry.get("redis.commands.duration").timer().count()); + Assertions.assertTrue(registry.get("redis.commands.duration").timer().mean(TimeUnit.NANOSECONDS) > 0); + + } + +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagCorsTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagCorsTest.java index ab20fa9dda67b..33318e5c558eb 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagCorsTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagCorsTest.java @@ -24,6 +24,7 @@ public class UriTagCorsTest { .overrideConfigKey("quarkus.micrometer.binder.http-server.enabled", "true") .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") .overrideConfigKey("quarkus.http.cors", "true") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withApplicationRoot((jar) -> jar .addClasses(Util.class, VertxWebEndpoint.class, diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagTest.java index 0042f816e0555..055fd3ed13d7a 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagTest.java @@ -29,6 +29,7 @@ public class UriTagTest { .overrideConfigKey("quarkus.micrometer.binder.http-server.ignore-patterns", "/two") .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") .overrideConfigKey("pingpong/mp-rest/url", "${test.url}") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withApplicationRoot((jar) -> jar .addClasses(Util.class, PingPongResource.class, diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpApplicationRootTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpApplicationRootTest.java index 76a6a0b6e4908..02212c073dc14 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpApplicationRootTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpApplicationRootTest.java @@ -35,6 +35,7 @@ public class UriTagWithHttpApplicationRootTest { .overrideConfigKey("quarkus.micrometer.binder.http-server.enabled", "true") .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") .overrideConfigKey("pingpong/mp-rest/url", "${test.url}") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withApplicationRoot((jar) -> jar .addClasses(Util.class, PingPongResource.class, diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpRootTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpRootTest.java index 8eca9c353bac8..072334f56c4f8 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpRootTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpRootTest.java @@ -33,6 +33,7 @@ public class UriTagWithHttpRootTest { .overrideConfigKey("quarkus.micrometer.binder.http-server.enabled", "true") .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") .overrideConfigKey("pingpong/mp-rest/url", "${test.url}") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withApplicationRoot((jar) -> jar .addClasses(Util.class, PingPongResource.class, diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriWithMaxTagMeterFilterTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriWithMaxTagMeterFilterTest.java index 795ac31d73868..b386a2d1efc63 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriWithMaxTagMeterFilterTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriWithMaxTagMeterFilterTest.java @@ -27,6 +27,7 @@ public class UriWithMaxTagMeterFilterTest { .overrideConfigKey("quarkus.micrometer.export.prometheus.enabled", "true") .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") .overrideConfigKey("pingpong/mp-rest/url", "${test.url}") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withApplicationRoot((jar) -> jar .addClasses(HelloResource.class, PingPongResource.class, PingPongResource.PingPongRestClient.class)); diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/VertxWithHttpDisabledTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/VertxWithHttpDisabledTest.java index a2e7246cf80c0..8fcc4a47cbee6 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/VertxWithHttpDisabledTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/VertxWithHttpDisabledTest.java @@ -20,6 +20,7 @@ public class VertxWithHttpDisabledTest { .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") .overrideConfigKey("pingpong/mp-rest/url", "${test.url}") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withApplicationRoot((jar) -> jar .addClasses(PingPongResource.class, PingPongResource.PingPongRestClient.class)); diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/VertxWithHttpEnabledTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/VertxWithHttpEnabledTest.java index 2e72918fdaf31..c5a3158712364 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/VertxWithHttpEnabledTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/VertxWithHttpEnabledTest.java @@ -28,6 +28,7 @@ public class VertxWithHttpEnabledTest { .overrideConfigKey("quarkus.micrometer.binder.vertx.match-patterns", "/one=/two") .overrideConfigKey("quarkus.micrometer.binder.vertx.ignore-patterns", "/two") .overrideConfigKey("pingpong/mp-rest/url", "${test.url}") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withApplicationRoot((jar) -> jar .addClasses(PingPongResource.class, PingPongResource.PingPongRestClient.class)); diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/AllRegistriesDisabledTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/AllRegistriesDisabledTest.java index 9c18f956b4e04..f4fb610482d27 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/AllRegistriesDisabledTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/AllRegistriesDisabledTest.java @@ -22,6 +22,7 @@ public class AllRegistriesDisabledTest { .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") .overrideConfigKey("quarkus.micrometer.export.json.enabled", "false") .overrideConfigKey("quarkus.micrometer.export.prometheus.enabled", "false") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withEmptyApplication(); @Inject diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/JsonAndPrometheusRegistryEnabledTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/JsonAndPrometheusRegistryEnabledTest.java index 1807e4425d27c..a3f716ade50f2 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/JsonAndPrometheusRegistryEnabledTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/JsonAndPrometheusRegistryEnabledTest.java @@ -26,6 +26,7 @@ public class JsonAndPrometheusRegistryEnabledTest { .overrideConfigKey("quarkus.micrometer.export.json.enabled", "true") .overrideConfigKey("quarkus.micrometer.export.prometheus.enabled", "true") .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withEmptyApplication(); @Inject diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/JsonRegistryEnabledTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/JsonRegistryEnabledTest.java index 7a3a2faa84e2d..1f58655ced5dd 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/JsonRegistryEnabledTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/JsonRegistryEnabledTest.java @@ -23,6 +23,7 @@ public class JsonRegistryEnabledTest { .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") .overrideConfigKey("quarkus.micrometer.export.json.enabled", "true") .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withEmptyApplication(); @Inject diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/NoDefaultPrometheusTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/NoDefaultPrometheusTest.java index 30423bbe6c3e6..94aeba83e992a 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/NoDefaultPrometheusTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/NoDefaultPrometheusTest.java @@ -24,6 +24,7 @@ public class NoDefaultPrometheusTest { .overrideConfigKey("quarkus.micrometer.export.prometheus.enabled", "true") .overrideConfigKey("quarkus.micrometer.export.prometheus.default-registry", "false") .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withApplicationRoot((jar) -> jar .addClasses(Util.class, PrometheusRegistryProcessor.REGISTRY_CLASS, diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/PrometheusEnabledTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/PrometheusEnabledTest.java index 4f0e0bb01e196..4803b43d78226 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/PrometheusEnabledTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/PrometheusEnabledTest.java @@ -22,6 +22,7 @@ public class PrometheusEnabledTest { .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") .overrideConfigKey("quarkus.micrometer.export.prometheus.enabled", "true") .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withEmptyApplication(); @Inject diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/SecondPrometheusTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/SecondPrometheusTest.java index 22be6912c45f6..ff8dc0dbba69b 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/SecondPrometheusTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/export/SecondPrometheusTest.java @@ -20,6 +20,7 @@ public class SecondPrometheusTest { .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") .overrideConfigKey("quarkus.micrometer.export.prometheus.enabled", "true") .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withApplicationRoot((jar) -> jar .addClass(PrometheusRegistryProcessor.REGISTRY_CLASS) .addClass(SecondPrometheusProvider.class)); diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/MicrometerCounterInterceptorTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/MicrometerCounterInterceptorTest.java index a49ac7ddadb7a..f1afd9f6d3654 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/MicrometerCounterInterceptorTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/MicrometerCounterInterceptorTest.java @@ -28,6 +28,7 @@ public class MicrometerCounterInterceptorTest { .overrideConfigKey("quarkus.micrometer.binder.mp-metrics.enabled", "false") .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "false") .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withApplicationRoot((jar) -> jar .addClass(CountedResource.class) .addClass(TimedResource.class) diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/MicrometerTimedInterceptorTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/MicrometerTimedInterceptorTest.java index 010139dcc4c10..0267ccd6aa938 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/MicrometerTimedInterceptorTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/MicrometerTimedInterceptorTest.java @@ -27,6 +27,7 @@ public class MicrometerTimedInterceptorTest { .overrideConfigKey("quarkus.micrometer.binder.mp-metrics.enabled", "false") .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "false") .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withApplicationRoot((jar) -> jar .addClass(CountedResource.class) .addClass(TimedResource.class) diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/binder/mpmetrics/MpMetricNamingTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/binder/mpmetrics/MpMetricNamingTest.java index ff5c4b98d2e22..daa39d4f42b5a 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/binder/mpmetrics/MpMetricNamingTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/binder/mpmetrics/MpMetricNamingTest.java @@ -18,6 +18,7 @@ public class MpMetricNamingTest { .overrideConfigKey("quarkus.micrometer.binder.mp-metrics.enabled", "true") .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "false") .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") .withApplicationRoot((jar) -> jar .addClass(MpColorResource.class)); diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/binder/mpmetrics/MpMetricRegistrationTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/binder/mpmetrics/MpMetricRegistrationTest.java index c79664f1ae8e7..f3604477f7770 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/binder/mpmetrics/MpMetricRegistrationTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/runtime/binder/mpmetrics/MpMetricRegistrationTest.java @@ -17,7 +17,8 @@ public class MpMetricRegistrationTest { .withConfigurationResource("test-logging.properties") .overrideConfigKey("quarkus.micrometer.binder.mp-metrics.enabled", "true") .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") - .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false"); + .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false"); @Inject MetricRegistryAdapter mpRegistry; diff --git a/extensions/micrometer/runtime/pom.xml b/extensions/micrometer/runtime/pom.xml index 5e91a6b7f24a9..d7a674095085f 100644 --- a/extensions/micrometer/runtime/pom.xml +++ b/extensions/micrometer/runtime/pom.xml @@ -102,6 +102,12 @@ true + + io.quarkus + quarkus-redis-client + true + + diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/redis/RedisMetricsBean.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/redis/RedisMetricsBean.java new file mode 100644 index 0000000000000..e01edccb949a1 --- /dev/null +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/redis/RedisMetricsBean.java @@ -0,0 +1,73 @@ +package io.quarkus.micrometer.runtime.binder.redis; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Typed; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.Timer; +import io.quarkus.redis.runtime.client.ObservableRedisMetrics; + +@ApplicationScoped +@Typed(ObservableRedisMetrics.class) +public class RedisMetricsBean implements ObservableRedisMetrics { + + final MeterRegistry registry = Metrics.globalRegistry; + + final Map reportedMetrics = new ConcurrentHashMap<>(); + + @Override + public void report(String name, long durationInNs, boolean succeeded) { + reportedMetrics.computeIfAbsent(name, n -> new RedisMetrics(registry, n)) + .report(name, durationInNs, succeeded); + } + + private class RedisMetrics implements ObservableRedisMetrics { + private final Tags tags; + private final Counter operationCounter; + private final Counter successCounter; + + private final Counter failureCounter; + private final Timer timer; + private String name; + + private RedisMetrics(MeterRegistry registry, String name) { + this.name = name; + this.tags = Tags.of(Tag.of("client-name", name)); + this.operationCounter = Counter.builder("redis.commands.count") + .description("The number of operations (commands or batches) executed") + .tags(tags) + .register(registry); + this.successCounter = Counter.builder("redis.commands.success") + .description("The number of operations (commands or batches) that have been executed successfully") + .tags(tags) + .register(registry); + this.failureCounter = Counter.builder("redis.commands.failure") + .description("The number of operations (commands or batches) that have been failed") + .tags(tags) + .register(registry); + this.timer = Timer.builder("redis.commands.duration") + .description("The duration of the operations (commands of batches") + .tags(tags) + .register(registry); + } + + @Override + public void report(String name, long durationInNs, boolean succeeded) { + operationCounter.increment(); + if (succeeded) { + this.successCounter.increment(); + } else { + this.failureCounter.increment(); + } + timer.record(durationInNs, TimeUnit.NANOSECONDS); + } + } +} diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/MicrometerConfig.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/MicrometerConfig.java index 666d8eea8ec08..e1fac0e2d4f69 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/MicrometerConfig.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/MicrometerConfig.java @@ -106,6 +106,8 @@ public static class BinderConfig { public KafkaConfigGroup kafka; + public RedisConfigGroup redis; + public GrpcServerConfigGroup grpcServer; public GrpcClientConfigGroup grpcClient; diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/RedisConfigGroup.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/RedisConfigGroup.java new file mode 100644 index 0000000000000..b79ee7b9c2350 --- /dev/null +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/RedisConfigGroup.java @@ -0,0 +1,35 @@ +package io.quarkus.micrometer.runtime.config; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; + +/** + * Build / static runtime config for Redis metrics + */ +@ConfigGroup +public class RedisConfigGroup implements MicrometerConfig.CapabilityEnabled { + /** + * Redis client metrics support. + *

+ * Support for Redis metrics will be enabled if Micrometer support is enabled, + * the Quarkus Redis client extension is on the classpath + * and either this value is true, or this value is unset and + * {@code quarkus.micrometer.binder-enabled-default} is true. + */ + @ConfigItem + public Optional enabled; + + @Override + public Optional getEnabled() { + return enabled; + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + + "{enabled=" + enabled + + '}'; + } +} diff --git a/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/RedisMetricsBuildItem.java b/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/RedisMetricsBuildItem.java new file mode 100644 index 0000000000000..7269b154cf237 --- /dev/null +++ b/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/RedisMetricsBuildItem.java @@ -0,0 +1,18 @@ +package io.quarkus.redis.client.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; +import io.quarkus.redis.runtime.client.ObservableRedisMetrics; +import io.quarkus.runtime.RuntimeValue; + +public final class RedisMetricsBuildItem extends SimpleBuildItem { + + private final RuntimeValue metrics; + + public RedisMetricsBuildItem(RuntimeValue metrics) { + this.metrics = metrics; + } + + public RuntimeValue get() { + return metrics; + } +} diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/ObservableRedis.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/ObservableRedis.java new file mode 100644 index 0000000000000..46dd791970b82 --- /dev/null +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/ObservableRedis.java @@ -0,0 +1,168 @@ +package io.quarkus.redis.runtime.client; + +import java.util.List; + +import io.vertx.codegen.annotations.Nullable; +import io.vertx.core.AsyncResult; +import io.vertx.core.Future; +import io.vertx.core.Handler; +import io.vertx.redis.client.Redis; +import io.vertx.redis.client.RedisConnection; +import io.vertx.redis.client.Request; +import io.vertx.redis.client.Response; + +/** + * An implementation of the {@link Redis} interface that tracks the duration of each operation for observability purpose. + */ +public class ObservableRedis implements Redis { + + private final Redis redis; + private final String name; + private final ObservableRedisMetrics reporter; + + public ObservableRedis(Redis redis, String name, ObservableRedisMetrics reporter) { + this.redis = redis; + this.name = name; + this.reporter = reporter == null ? ObservableRedisMetrics.NOOP : reporter; + } + + public String name() { + return name; + } + + private void report(long time, boolean succeeded) { + reporter.report(name, time, succeeded); + } + + @Override + public Redis connect(Handler> handler) { + this.redis.connect(ar -> { + if (ar.failed()) { + handler.handle(Future.failedFuture(ar.cause())); + } else { + handler.handle(Future.succeededFuture(new ObservableRedisConnection(ar.result()))); + } + }); + return this; + } + + @Override + public Redis send(Request command, Handler> onSend) { + long begin = System.nanoTime(); + redis.send(command, ar -> { + report(System.nanoTime() - begin, ar.succeeded()); + onSend.handle(ar); + }); + return this; + } + + @Override + public Redis batch(List commands, Handler>> onSend) { + long begin = System.nanoTime(); + redis.batch(commands, ar -> { + report(System.nanoTime() - begin, ar.succeeded()); + onSend.handle(ar); + }); + return this; + } + + @Override + public Future connect() { + return redis.connect() + .map(ObservableRedisConnection::new); + } + + @Override + public void close() { + redis.close(); + } + + @Override + public Future send(Request command) { + long begin = System.nanoTime(); + return redis.send(command) + .onComplete(x -> report(System.nanoTime() - begin, x.succeeded())); + } + + @Override + public Future> batch(List commands) { + long begin = System.nanoTime(); + return redis.batch(commands) + .onComplete(x -> report(System.nanoTime() - begin, x.succeeded())); + } + + private class ObservableRedisConnection implements RedisConnection { + + private final RedisConnection delegate; + + private ObservableRedisConnection(RedisConnection delegate) { + this.delegate = delegate; + } + + @Override + public RedisConnection exceptionHandler(Handler handler) { + delegate.exceptionHandler(handler); + return this; + } + + @Override + public RedisConnection handler(@Nullable Handler handler) { + delegate.handler(handler); + return this; + } + + @Override + public RedisConnection pause() { + delegate.pause(); + return this; + } + + @Override + public RedisConnection resume() { + delegate.resume(); + return this; + } + + @Override + public RedisConnection fetch(long amount) { + delegate.fetch(amount); + return this; + } + + @Override + public RedisConnection endHandler(@Nullable Handler endHandler) { + delegate.endHandler(endHandler); + return this; + } + + @Override + public Future send(Request command) { + long begin = System.nanoTime(); + return delegate.send(command) + .onComplete(ar -> { + long end = System.nanoTime(); + report(end - begin, ar.succeeded()); + }); + } + + @Override + public Future> batch(List commands) { + long begin = System.nanoTime(); + return delegate.batch(commands) + .onComplete(ar -> { + long end = System.nanoTime(); + report(end - begin, ar.succeeded()); + }); + } + + @Override + public Future close() { + return delegate.close(); + } + + @Override + public boolean pendingQueueFull() { + return delegate.pendingQueueFull(); + } + } +} diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/ObservableRedisMetrics.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/ObservableRedisMetrics.java new file mode 100644 index 0000000000000..814b28011a406 --- /dev/null +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/ObservableRedisMetrics.java @@ -0,0 +1,22 @@ +package io.quarkus.redis.runtime.client; + +public interface ObservableRedisMetrics { + + /** + * Method called by the {@link ObservableRedis} after every operation. + * + * @param name the client name + * @param durationInNs the duration of the operation in ns, it can represent the execution of a single command or + * a batch. + * @param succeeded whether the operation succeeded + */ + void report(String name, long durationInNs, boolean succeeded); + + ObservableRedisMetrics NOOP = new ObservableRedisMetrics() { + @Override + public void report(String name, long durationInNs, boolean succeeded) { + + } + }; + +} diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/RedisClientRecorder.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/RedisClientRecorder.java index 74a3bd5540acf..fcb1abfbc654c 100644 --- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/RedisClientRecorder.java +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/RedisClientRecorder.java @@ -10,6 +10,9 @@ import java.util.Set; import java.util.function.Supplier; +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.spi.CDI; + import io.quarkus.redis.client.RedisClient; import io.quarkus.redis.client.reactive.ReactiveRedisClient; import io.quarkus.redis.datasource.ReactiveRedisDataSource; @@ -37,25 +40,25 @@ public class RedisClientRecorder { private static final Map clients = new HashMap<>(); private static final Map dataSources = new HashMap<>(); private Vertx vertx; + private ObservableRedisMetrics metrics; public RedisClientRecorder(RedisConfig rc) { this.config = rc; } public void initialize(RuntimeValue vertx, Set names) { - this.vertx = Vertx.newInstance(vertx.getValue()); - _initialize(this.vertx, names); - } - - private void closeAllClients() { - for (Map.Entry entry : clients.entrySet()) { - entry.getValue().redis.close(); + Instance instance = CDI.current().select(ObservableRedisMetrics.class); + if (instance.isResolvable()) { + this.metrics = instance.get(); + } else { + this.metrics = null; } - clients.clear(); - dataSources.clear(); + + this.vertx = Vertx.newInstance(vertx.getValue()); + _initialize(vertx.getValue(), names); } - public void _initialize(Vertx vertx, Set names) { + public void _initialize(io.vertx.core.Vertx vertx, Set names) { for (String name : names) { // Search if we have an associated config: // - if default -> Default @@ -75,10 +78,11 @@ public ConfigurationException get() { } }); clients.computeIfAbsent(name, - x -> new RedisClientAndApi(VertxRedisClientFactory.create(name, vertx, actualConfig))); + x -> new RedisClientAndApi(name, VertxRedisClientFactory.create(name, vertx, actualConfig), metrics)); } else if (DEFAULT_CLIENT_NAME.equalsIgnoreCase(name) && maybe.isPresent()) { clients.computeIfAbsent(name, - x -> new RedisClientAndApi(VertxRedisClientFactory.create(DEFAULT_CLIENT_NAME, vertx, maybe.get()))); + x -> new RedisClientAndApi(name, + VertxRedisClientFactory.create(DEFAULT_CLIENT_NAME, vertx, maybe.get()), metrics)); } // Do not throw an error. We would need to check if the default redis client is used. } @@ -111,7 +115,7 @@ public Supplier getBareRedisClient(String name) { return new Supplier() { @Override public io.vertx.redis.client.Redis get() { - return clients.get(name).redis.getDelegate(); + return clients.get(name).observable; } }; } @@ -232,10 +236,12 @@ public void preload(String name, List loadScriptPaths, boolean redisFlus private static class RedisClientAndApi { private final Redis redis; private final RedisAPI api; + private final ObservableRedis observable; - private RedisClientAndApi(Redis redis) { - this.redis = redis; - this.api = RedisAPI.api(redis); + private RedisClientAndApi(String name, io.vertx.redis.client.Redis redis, ObservableRedisMetrics metrics) { + this.observable = new ObservableRedis(redis, name, metrics); + this.redis = Redis.newInstance(this.observable); + this.api = RedisAPI.api(this.redis); } } diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java index d4daf1a445adb..c9131c9f1809e 100644 --- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java @@ -22,10 +22,10 @@ import io.quarkus.redis.runtime.client.config.TlsConfig; import io.quarkus.runtime.configuration.ConfigurationException; import io.smallrye.common.annotation.Identifier; +import io.vertx.core.Vertx; import io.vertx.core.net.NetClientOptions; import io.vertx.core.net.ProxyOptions; -import io.vertx.mutiny.core.Vertx; -import io.vertx.mutiny.redis.client.Redis; +import io.vertx.redis.client.Redis; import io.vertx.redis.client.RedisClientType; import io.vertx.redis.client.RedisOptions;