Skip to content

Commit

Permalink
Merge pull request #44999 from vkn/mongodb-micrometer-metrics
Browse files Browse the repository at this point in the history
Enable more MongoDB Micrometer metrics
  • Loading branch information
brunobat authored Dec 17, 2024
2 parents e67b126 + be1fa40 commit 849ec70
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 58 deletions.
12 changes: 11 additions & 1 deletion extensions/mongodb-client/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-metrics-deployment</artifactId>
<artifactId>quarkus-micrometer-deployment</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-micrometer-registry-prometheus-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
Expand All @@ -86,6 +91,11 @@
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem;
import io.quarkus.mongodb.MongoClientName;
import io.quarkus.mongodb.metrics.MicrometerCommandListener;
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
import io.quarkus.mongodb.runtime.MongoClientBeanUtil;
import io.quarkus.mongodb.runtime.MongoClientCustomizer;
Expand Down Expand Up @@ -124,6 +125,21 @@ AdditionalIndexedClassesBuildItem includeMongoCommandListener(MongoClientBuildTi
return new AdditionalIndexedClassesBuildItem();
}

@BuildStep
void includeMongoCommandMetricListener(
BuildProducer<AdditionalIndexedClassesBuildItem> additionalIndexedClasses,
MongoClientBuildTimeConfig buildTimeConfig,
Optional<MetricsCapabilityBuildItem> metricsCapability) {
if (!buildTimeConfig.metricsEnabled) {
return;
}
boolean withMicrometer = metricsCapability.map(cap -> cap.metricsSupported(MetricsFactory.MICROMETER))
.orElse(false);
if (withMicrometer) {
additionalIndexedClasses.produce(new AdditionalIndexedClassesBuildItem(MicrometerCommandListener.class.getName()));
}
}

@BuildStep
public void registerDnsProvider(BuildProducer<NativeImageResourceBuildItem> nativeProducer) {
nativeProducer.produce(new NativeImageResourceBuildItem("META-INF/services/" + DnsClientProvider.class.getName()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,58 +4,47 @@

import jakarta.inject.Inject;

import org.eclipse.microprofile.metrics.Metric;
import org.eclipse.microprofile.metrics.MetricID;
import org.eclipse.microprofile.metrics.MetricRegistry;
import org.eclipse.microprofile.metrics.Tag;
import org.eclipse.microprofile.metrics.annotation.RegistryType;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import com.mongodb.client.MongoClient;

import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.quarkus.arc.Arc;
import io.quarkus.mongodb.metrics.ConnectionPoolGauge;
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
import io.quarkus.test.QuarkusUnitTest;

/** Variation of {@link io.quarkus.mongodb.MongoMetricsTest} to verify lazy client initialization. */
public class MongoLazyTest extends MongoTestBase {
class MongoLazyTest extends MongoTestBase {

@Inject
@RegistryType(type = MetricRegistry.Type.VENDOR)
MetricRegistry registry;
MeterRegistry meterRegistry;

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar.addClasses(MongoTestBase.class))
.withApplicationRoot(jar -> jar.addClasses(MongoTestBase.class))
.withConfigurationResource("application-metrics-mongo.properties");

@Test
void testLazyClientCreation() {
// Clients are created lazily, this metric should not be present yet
assertThat(getGaugeValueOrNull("mongodb.connection-pool.size", getTags())).isNull();
assertThat(getGaugeValueOrNull("mongodb.connection-pool.checked-out-count", getTags())).isNull();
assertThat(getMetric("mongodb.driver.pool.size")).isNull();
assertThat(getMetric("mongodb.driver.pool.checkedout")).isNull();
assertThat(getMetric("mongodb.driver.commands")).isNull();

// doing this here instead of in another method in order to avoid messing with the initialization stats
assertThat(Arc.container().instance(MongoClient.class).get()).isNull();
assertThat(Arc.container().instance(ReactiveMongoClient.class).get()).isNull();
}

private Long getGaugeValueOrNull(String metricName, Tag[] tags) {
MetricID metricID = new MetricID(metricName, tags);
Metric metric = registry.getMetrics().get(metricID);

if (metric == null) {
return null;
}
return ((ConnectionPoolGauge) metric).getValue();
private Double getMetric(String name) {
Meter metric = meterRegistry.getMeters()
.stream()
.filter(mtr -> mtr.getId().getName().contains(name))
.findFirst()
.orElse(null);
return metric == null ? null : metric.measure().iterator().next().getValue();
}

private Tag[] getTags() {
return new Tag[] {
new Tag("host", "127.0.0.1"),
new Tag("port", "27018"),
};
}
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,32 @@
package io.quarkus.mongodb;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;

import jakarta.inject.Inject;

import org.eclipse.microprofile.metrics.Metric;
import org.eclipse.microprofile.metrics.MetricID;
import org.eclipse.microprofile.metrics.MetricRegistry;
import org.eclipse.microprofile.metrics.Tag;
import org.eclipse.microprofile.metrics.annotation.RegistryType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import com.mongodb.client.MongoClient;

import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.quarkus.arc.Arc;
import io.quarkus.mongodb.metrics.ConnectionPoolGauge;
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
import io.quarkus.test.QuarkusUnitTest;

public class MongoMetricsTest extends MongoTestBase {
class MongoMetricsTest extends MongoTestBase {

@Inject
MongoClient client;

@Inject
@RegistryType(type = MetricRegistry.Type.VENDOR)
MetricRegistry registry;
MeterRegistry meterRegistry;

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar.addClasses(MongoTestBase.class))
.withApplicationRoot(jar -> jar.addClasses(MongoTestBase.class))
.withConfigurationResource("application-metrics-mongo.properties");

@AfterEach
Expand All @@ -45,38 +39,32 @@ void cleanup() {
@Test
void testMetricsInitialization() {
// Clients are created lazily, this metric should not be present yet
assertThat(getGaugeValueOrNull("mongodb.connection-pool.size", getTags())).isNull();
assertThat(getGaugeValueOrNull("mongodb.connection-pool.checked-out-count", getTags())).isNull();
assertThat(getMetric("mongodb.driver.pool.size")).isNull();
assertThat(getMetric("mongodb.driver.pool.checkedout")).isNull();

// Just need to execute something so that a connection is opened
String name = client.listDatabaseNames().first();

assertEquals(1L, getGaugeValueOrNull("mongodb.connection-pool.size", getTags()));
assertEquals(0L, getGaugeValueOrNull("mongodb.connection-pool.checked-out-count", getTags()));
assertThat(getMetric("mongodb.driver.pool.size")).isOne();
assertThat(getMetric("mongodb.driver.commands")).isOne();
assertThat(getMetric("mongodb.driver.pool.checkedout")).isZero();

client.close();
assertEquals(0L, getGaugeValueOrNull("mongodb.connection-pool.size", getTags()));
assertEquals(0L, getGaugeValueOrNull("mongodb.connection-pool.checked-out-count", getTags()));
assertThat(getMetric("mongodb.driver.pool.size")).isNull();
assertThat(getMetric("mongodb.driver.pool.checkedout")).isNull();

// doing this here instead of in another method in order to avoid messing with the initialization stats
assertThat(Arc.container().instance(MongoClient.class).get()).isNotNull();
assertThat(Arc.container().instance(ReactiveMongoClient.class).get()).isNull();
}

private Long getGaugeValueOrNull(String metricName, Tag[] tags) {
MetricID metricID = new MetricID(metricName, tags);
Metric metric = registry.getMetrics().get(metricID);

if (metric == null) {
return null;
}
return ((ConnectionPoolGauge) metric).getValue();
private Double getMetric(String metricName) {
Meter metric = meterRegistry.getMeters()
.stream()
.filter(mtr -> mtr.getId().getName().contains(metricName))
.findFirst()
.orElse(null);
return metric == null ? null : metric.measure().iterator().next().getValue();
}

private Tag[] getTags() {
return new Tag[] {
new Tag("host", "127.0.0.1"),
new Tag("port", "27018"),
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package io.quarkus.mongodb.deployment;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

import java.util.Optional;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.ArgumentCaptor;

import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.builditem.AdditionalIndexedClassesBuildItem;
import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem;
import io.quarkus.runtime.metrics.MetricsFactory;

class MongoClientProcessorTest {
private final MongoClientProcessor buildStep = new MongoClientProcessor();

@SuppressWarnings("unchecked")
@ParameterizedTest
@CsvSource({
"true, true, true", // Metrics enabled and Micrometer supported
"true, false, false", // Metrics enabled but Micrometer not supported
"false, true, false", // Metrics disabled and Micrometer supported
"false, false, false" // Metrics disabled and Micrometer not supported
})
void testIncludeMongoCommandMetricListener(boolean metricsEnabled, boolean micrometerSupported, boolean expectedResult) {
MongoClientBuildTimeConfig config = config(metricsEnabled);
Optional<MetricsCapabilityBuildItem> capability = capability(metricsEnabled, micrometerSupported);

BuildProducer<AdditionalIndexedClassesBuildItem> buildProducer = mock(BuildProducer.class);
buildStep.includeMongoCommandMetricListener(buildProducer, config, capability);

if (expectedResult) {
var captor = ArgumentCaptor.forClass(AdditionalIndexedClassesBuildItem.class);
verify(buildProducer, times(1)).produce(captor.capture());
assertThat(captor.getAllValues().get(0).getClassesToIndex())
.containsExactly("io.quarkus.mongodb.metrics.MicrometerCommandListener");
} else {
verify(buildProducer, never()).produce(any(AdditionalIndexedClassesBuildItem.class));
}
}

private static Optional<MetricsCapabilityBuildItem> capability(boolean metricsEnabled, boolean micrometerSupported) {
MetricsCapabilityBuildItem capability = metricsEnabled
? new MetricsCapabilityBuildItem(factory -> MetricsFactory.MICROMETER.equals(factory) && micrometerSupported)
: null;
return Optional.ofNullable(capability);
}

private static MongoClientBuildTimeConfig config(boolean metricsEnabled) {
MongoClientBuildTimeConfig buildTimeConfig = new MongoClientBuildTimeConfig();
buildTimeConfig.metricsEnabled = metricsEnabled;
return buildTimeConfig;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.quarkus.mongodb.metrics;

import jakarta.inject.Inject;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.mongodb.MongoMetricsCommandListener;

public class MicrometerCommandListener extends MongoMetricsCommandListener {
@Inject
public MicrometerCommandListener(MeterRegistry registry) {
super(registry);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ private void callPersonEndpoint(String endpoint) {
.when().get("/q/metrics")
.then()
.statusCode(200)
.body(CoreMatchers.containsString("mongodb_driver_commands_seconds_max"))
.body(CoreMatchers.containsString("mongodb_driver_pool_checkedout"))
.body(CoreMatchers.containsString("mongodb_driver_pool_size"))
.body(CoreMatchers.containsString("mongodb_driver_pool_waitqueuesize"));
Expand Down

0 comments on commit 849ec70

Please sign in to comment.