Skip to content

[FEATURE] Micrometer support for Oracle UCP #306

Description

@simasch

When using the standard HikariCP Micrometer is automatically enabled.
It would be helpful to have that with UCP as well.

Currently I do this:

public class UcpPoolMetrics implements MeterBinder {

    private final PoolDataSource poolDataSource;

    private final Tags tags;

    public UcpPoolMetrics(PoolDataSource poolDataSource) {
        this.poolDataSource = poolDataSource;
        var poolName = poolDataSource.getConnectionPoolName();
        this.tags = Tags.of("pool", poolName != null ? poolName : "ucp");
    }

    @Override
    public void bindTo(MeterRegistry registry) {
        gauge(registry, "ucp.connections", "Total connections in the pool",
                UniversalConnectionPoolStatistics::getTotalConnectionsCount);
        gauge(registry, "ucp.connections.active", "Borrowed (in use) connections",
                UniversalConnectionPoolStatistics::getBorrowedConnectionsCount);
        gauge(registry, "ucp.connections.idle", "Available (idle) connections",
                UniversalConnectionPoolStatistics::getAvailableConnectionsCount);
        gauge(registry, "ucp.connections.pending", "Threads waiting for a connection",
                UniversalConnectionPoolStatistics::getPendingRequestsCount);
        gauge(registry, "ucp.connections.peak", "Peak connection count since pool start",
                UniversalConnectionPoolStatistics::getPeakConnectionsCount);

        Gauge.builder("ucp.connections.min", this.poolDataSource, PoolDataSource::getMinPoolSize)
            .tags(this.tags)
            .description("Configured minimum pool size")
            .register(registry);
        Gauge.builder("ucp.connections.max", this.poolDataSource, PoolDataSource::getMaxPoolSize)
            .tags(this.tags)
            .description("Configured maximum pool size")
            .register(registry);

        // Not named "created": OpenMetrics reserves the _created suffix, so a meter named
        // ucp.connections.created would collide with the ucp.connections gauge.
        counter(registry, "ucp.connections.opened", "Physical connections created",
                UniversalConnectionPoolStatistics::getConnectionsCreatedCount);
        counter(registry, "ucp.connections.closed", "Physical connections closed",
                UniversalConnectionPoolStatistics::getConnectionsClosedCount);
        counter(registry, "ucp.connections.abandoned", "Connections reclaimed as abandoned",
                UniversalConnectionPoolStatistics::getAbandonedConnectionsCount);
        counter(registry, "ucp.connections.timeout", "Failed connection borrow attempts (wait failures)",
                UniversalConnectionPoolStatistics::getCumulativeFailedConnectionWaitCount);

        FunctionTimer
            .builder("ucp.connections.acquire", this.poolDataSource,
                    ds -> (long) statistic(ds, UniversalConnectionPoolStatistics::getCumulativeConnectionBorrowedCount),
                    ds -> statistic(ds, UniversalConnectionPoolStatistics::getCumulativeConnectionWaitTime),
                    TimeUnit.MILLISECONDS)
            .tags(this.tags)
            .description("Time spent waiting to borrow a connection")
            .register(registry);
        FunctionTimer
            .builder("ucp.connections.usage", this.poolDataSource,
                    ds -> (long) statistic(ds, UniversalConnectionPoolStatistics::getCumulativeConnectionReturnedCount),
                    ds -> statistic(ds, UniversalConnectionPoolStatistics::getCumulativeConnectionUseTime),
                    TimeUnit.MILLISECONDS)
            .tags(this.tags)
            .description("Time connections spend borrowed before being returned")
            .register(registry);
    }

    private void gauge(MeterRegistry registry, String name, String description,
            ToDoubleFunction<UniversalConnectionPoolStatistics> value) {
        Gauge.builder(name, this.poolDataSource, ds -> statistic(ds, value))
            .tags(this.tags)
            .description(description)
            .register(registry);
    }

    private void counter(MeterRegistry registry, String name, String description,
            ToDoubleFunction<UniversalConnectionPoolStatistics> value) {
        FunctionCounter.builder(name, this.poolDataSource, ds -> statistic(ds, value))
            .tags(this.tags)
            .description(description)
            .register(registry);
    }

    /**
     * UCP returns no statistics object before the pool has been started.
     */
    private static double statistic(PoolDataSource poolDataSource,
            ToDoubleFunction<UniversalConnectionPoolStatistics> value) {
        var statistics = poolDataSource.getStatistics();
        return statistics != null ? value.applyAsDouble(statistics) : 0;
    }

}

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions