Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import java.time.Duration;
Expand All @@ -19,7 +20,6 @@ public class OpenMetadataMetrics {
private final DistributionSummary httpResponseSize;

private final Timer jdbiQueryTimer;
private final Counter jdbiConnectionCounter;
private final Counter jdbiErrorCounter;

private final Counter entityCreatedCounter;
Expand Down Expand Up @@ -50,10 +50,9 @@ public OpenMetadataMetrics(MeterRegistry meterRegistry) {
.sla(LATENCY_SLA_BUCKETS)
.register(meterRegistry);

this.jdbiConnectionCounter =
Counter.builder("db.connections.total")
.description("Total database connections created")
.register(meterRegistry);
Gauge.builder("db.pool.connections", () -> poolTotalConnections())
.description("Total connections in the database connection pool")
.register(meterRegistry);

this.jdbiErrorCounter =
Counter.builder("db.errors.total")
Expand Down Expand Up @@ -135,10 +134,6 @@ public void recordDatabaseQuery(String queryType, long durationMs) {
.record(Duration.ofMillis(durationMs));
}

public void incrementDatabaseConnections() {
jdbiConnectionCounter.increment();
}

public void incrementDatabaseErrors(String errorType) {
meterRegistry.counter("db.errors.total", "type", errorType).increment();
}
Expand Down Expand Up @@ -200,12 +195,20 @@ public void recordAuthenticationFailure(String authType, String reason) {
// Gauge registration methods
public void registerGauge(
String name, java.util.function.Supplier<Number> supplier, String description) {
io.micrometer.core.instrument.Gauge.builder(name, () -> supplier.get().doubleValue())
Gauge.builder(name, () -> supplier.get().doubleValue())
.description(description)
.register(meterRegistry);
}

public MeterRegistry getMeterRegistry() {
return meterRegistry;
}

private double poolTotalConnections() {
Gauge active = meterRegistry.find("hikaricp.connections.active").gauge();
Gauge idle = meterRegistry.find("hikaricp.connections.idle").gauge();
double activeVal = active != null ? active.value() : 0.0;
double idleVal = idle != null ? idle.value() : 0.0;
return activeVal + idleVal;
}
Comment on lines +207 to +213
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

The gauge callback performs two registry lookups (meterRegistry.find(...).gauge()) on every sample/scrape. To reduce overhead, consider caching the Gauge references once they’re discovered (e.g., initialize lazily on first successful lookup and reuse thereafter), while still supporting late registration of Hikari meters.

Copilot uses AI. Check for mistakes.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package org.openmetadata.service.monitoring;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.prometheusmetrics.PrometheusConfig;
import io.micrometer.prometheusmetrics.PrometheusMeterRegistry;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class OpenMetadataMetricsTest {

private SimpleMeterRegistry registry;

@BeforeEach
void setUp() {
registry = new SimpleMeterRegistry();
}

@Test
void dbPoolConnectionsGaugeReflectsHikariCPPoolSize() {
AtomicInteger activeConnections = new AtomicInteger(5);
AtomicInteger idleConnections = new AtomicInteger(15);

Gauge.builder("hikaricp.connections.active", activeConnections, AtomicInteger::doubleValue)
.register(registry);
Gauge.builder("hikaricp.connections.idle", idleConnections, AtomicInteger::doubleValue)
.register(registry);

new OpenMetadataMetrics(registry);

Gauge total = registry.find("db.pool.connections").gauge();
assertNotNull(total, "db.pool.connections gauge should be registered");
assertEquals(20.0, total.value(), 0.01, "Should equal active + idle");
Comment on lines +24 to +38
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

These tests validate the computed value, but they don’t assert the Prometheus-exposed metric name/type (the original bug report is about /prometheus output). Adding a PrometheusMeterRegistry-based assertion (scrape contains db_connections_total with TYPE gauge) would catch naming/registry-conversion regressions and ensure the fix matches the reported behavior.

Copilot uses AI. Check for mistakes.

activeConnections.set(10);
idleConnections.set(10);
assertEquals(20.0, total.value(), 0.01, "Should reflect updated pool state");

activeConnections.set(0);
idleConnections.set(0);
assertEquals(0.0, total.value(), 0.01, "Should be zero when pool is empty");
}

@Test
void dbPoolConnectionsGaugeReturnsZeroWithoutHikariCP() {
new OpenMetadataMetrics(registry);

Gauge total = registry.find("db.pool.connections").gauge();
assertNotNull(total, "db.pool.connections gauge should be registered");
assertEquals(0.0, total.value(), 0.01, "Should be zero when HikariCP metrics are absent");
}

@Test
void prometheusScrapeExposesDbPoolConnectionsGauge() {
PrometheusMeterRegistry promRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);

AtomicInteger activeConnections = new AtomicInteger(3);
AtomicInteger idleConnections = new AtomicInteger(7);

Gauge.builder("hikaricp.connections.active", activeConnections, AtomicInteger::doubleValue)
.register(promRegistry);
Gauge.builder("hikaricp.connections.idle", idleConnections, AtomicInteger::doubleValue)
.register(promRegistry);

new OpenMetadataMetrics(promRegistry);

String scrape = promRegistry.scrape();
assertTrue(
scrape.contains("db_pool_connections"),
"Prometheus scrape should contain db_pool_connections metric");
assertTrue(
scrape.contains("# TYPE db_pool_connections gauge"),
"db_pool_connections should be exposed as a gauge type");
Comment on lines +58 to +78
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

Test name and assertions are inconsistent: prometheusScrapeExposesDbConnectionsTotal checks for db_pool_connections in the scrape output. Once the metric name is clarified (likely db_connections_total per the PR title/issue), update either the test name or the asserted metric string so they match to avoid confusion and false confidence.

Copilot uses AI. Check for mistakes.
}
}
Loading