Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable more MongoDB Micrometer metrics #44999

Merged
merged 3 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 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 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
AdditionalIndexedClassesBuildItem includeMongoCommandMetricListener(
MongoClientBuildTimeConfig buildTimeConfig,
Optional<MetricsCapabilityBuildItem> metricsCapability) {
if (!buildTimeConfig.metricsEnabled) {
return new AdditionalIndexedClassesBuildItem();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I wouldn't do that. Even if it doesn't complain right now, that's certainly something that could break later.

What you need to do is change the method to return void and inject a BuildProducer<AdditionalIndexedClassesBuildItem> additionalIndexedClasses and call the produce() method when it makes sense (and return; if you want to exit early).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gsmet I've adjusted the code as you requested. Thanks!

}
boolean withMicrometer = metricsCapability.map(cap -> cap.metricsSupported(MetricsFactory.MICROMETER))
.orElse(false);
if (withMicrometer) {
return new AdditionalIndexedClassesBuildItem(MicrometerCommandListener.class.getName());
}
return new AdditionalIndexedClassesBuildItem();
}

@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,51 @@
package io.quarkus.mongodb.deployment;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.Optional;

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

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();

@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);

AdditionalIndexedClassesBuildItem result = buildStep.includeMongoCommandMetricListener(config, capability);

if (expectedResult) {
assertThat(result.getClassesToIndex())
.containsExactly("io.quarkus.mongodb.metrics.MicrometerCommandListener");
} else {
assertThat(result.getClassesToIndex()).isEmpty();
}
}

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
Loading