Skip to content

Commit

Permalink
Implement HikariCP connection pool metrics (#6003)
Browse files Browse the repository at this point in the history
* Implement HikariCP connection pool metrics

* rebase after SDK update

* fix muzzle

* code review comments
  • Loading branch information
Mateusz Rzeszutek committed May 13, 2022
1 parent 3611f11 commit 5bcab32
Show file tree
Hide file tree
Showing 11 changed files with 747 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/supported-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ These are the supported libraries and frameworks:
| [Guava ListenableFuture](https://guava.dev/releases/snapshot/api/docs/com/google/common/util/concurrent/ListenableFuture.html) | 10.0+ |
| [GWT](http://www.gwtproject.org/) | 2.0+ |
| [Hibernate](https://github.com/hibernate/hibernate-orm) | 3.3+ |
| [HikariCP](https://github.com/brettwooldridge/HikariCP) | 3.0+ |
| [HttpURLConnection](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpURLConnection.html) | Java 8+ |
| [Hystrix](https://github.com/Netflix/Hystrix) | 1.4+ |
| [Java Executors](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html) | Java 8+ |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.metrics.db;

import static io.opentelemetry.api.common.AttributeKey.stringKey;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.DoubleHistogram;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.MeterBuilder;
import io.opentelemetry.api.metrics.ObservableLongUpDownCounter;
import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties;
import java.util.function.LongSupplier;

/**
* A helper class that models the <a
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/database-metrics.md#connection-pools">database
* client connection pool metrics semantic conventions</a>.
*/
public final class DbConnectionPoolMetrics {

static final AttributeKey<String> POOL_NAME = stringKey("pool.name");
static final AttributeKey<String> CONNECTION_STATE = stringKey("state");

static final String STATE_IDLE = "idle";
static final String STATE_USED = "used";

public static DbConnectionPoolMetrics create(
OpenTelemetry openTelemetry, String instrumentationName, String poolName) {

MeterBuilder meterBuilder = openTelemetry.getMeterProvider().meterBuilder(instrumentationName);
String version = EmbeddedInstrumentationProperties.findVersion(instrumentationName);
if (version != null) {
meterBuilder.setInstrumentationVersion(version);
}
return new DbConnectionPoolMetrics(meterBuilder.build(), Attributes.of(POOL_NAME, poolName));
}

private final Meter meter;
private final Attributes attributes;
private final Attributes usedConnectionsAttributes;
private final Attributes idleConnectionsAttributes;

DbConnectionPoolMetrics(Meter meter, Attributes attributes) {
this.meter = meter;
this.attributes = attributes;
usedConnectionsAttributes = attributes.toBuilder().put(CONNECTION_STATE, STATE_USED).build();
idleConnectionsAttributes = attributes.toBuilder().put(CONNECTION_STATE, STATE_IDLE).build();
}

public ObservableLongUpDownCounter usedConnections(LongSupplier usedConnectionsGetter) {
return meter
.upDownCounterBuilder("db.client.connections.usage")
.setUnit("connections")
.setDescription(
"The number of connections that are currently in state described by the state attribute.")
.buildWithCallback(
measurement ->
measurement.record(usedConnectionsGetter.getAsLong(), usedConnectionsAttributes));
}

public ObservableLongUpDownCounter idleConnections(LongSupplier idleConnectionsGetter) {
return meter
.upDownCounterBuilder("db.client.connections.usage")
.setUnit("connections")
.setDescription(
"The number of connections that are currently in state described by the state attribute.")
.buildWithCallback(
measurement ->
measurement.record(idleConnectionsGetter.getAsLong(), idleConnectionsAttributes));
}

public ObservableLongUpDownCounter minIdleConnections(LongSupplier minIdleConnectionsGetter) {
return meter
.upDownCounterBuilder("db.client.connections.idle.min")
.setUnit("connections")
.setDescription("The minimum number of idle open connections allowed.")
.buildWithCallback(
measurement -> measurement.record(minIdleConnectionsGetter.getAsLong(), attributes));
}

public ObservableLongUpDownCounter maxIdleConnections(LongSupplier maxIdleConnectionsGetter) {
return meter
.upDownCounterBuilder("db.client.connections.idle.max")
.setUnit("connections")
.setDescription("The maximum number of idle open connections allowed.")
.buildWithCallback(
measurement -> measurement.record(maxIdleConnectionsGetter.getAsLong(), attributes));
}

public ObservableLongUpDownCounter maxConnections(LongSupplier maxConnectionsGetter) {
return meter
.upDownCounterBuilder("db.client.connections.max")
.setUnit("connections")
.setDescription("The maximum number of open connections allowed.")
.buildWithCallback(
measurement -> measurement.record(maxConnectionsGetter.getAsLong(), attributes));
}

public ObservableLongUpDownCounter pendingRequestsForConnection(
LongSupplier pendingRequestsGetter) {
return meter
.upDownCounterBuilder("db.client.connections.pending_requests")
.setUnit("requests")
.setDescription(
"The number of pending requests for an open connection, cumulative for the entire pool.")
.buildWithCallback(
measurement -> measurement.record(pendingRequestsGetter.getAsLong(), attributes));
}

// TODO: should be a BoundLongCounter
public LongCounter connectionTimeouts() {
return meter
.counterBuilder("db.client.connections.timeouts")
.setUnit("timeouts")
.setDescription(
"The number of connection timeouts that have occurred trying to obtain a connection from the pool.")
.build();
}

// TODO: should be a BoundDoubleHistogram
public DoubleHistogram connectionCreateTime() {
return meter
.histogramBuilder("db.client.connections.create_time")
.setUnit("ms")
.setDescription("The time it took to create a new connection.")
.build();
}

// TODO: should be a BoundDoubleHistogram
public DoubleHistogram connectionWaitTime() {
return meter
.histogramBuilder("db.client.connections.wait_time")
.setUnit("ms")
.setDescription("The time it took to obtain an open connection from the pool.")
.build();
}

// TODO: should be a BoundDoubleHistogram
public DoubleHistogram connectionUseTime() {
return meter
.histogramBuilder("db.client.connections.use_time")
.setUnit("ms")
.setDescription("The time between borrowing a connection and returning it to the pool.")
.build();
}

// TODO: should be removed once bound instruments are back
public Attributes getAttributes() {
return attributes;
}
}
19 changes: 19 additions & 0 deletions instrumentation/hikaricp-3.0/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("com.zaxxer")
module.set("HikariCP")
versions.set("[3.0.0,)")
// muzzle does not detect PoolStats method references used - some of these methods were introduced in 3.0 and we can't assertInverse

// 4.0.0 uses a broken slf4j version: the "${slf4j.version}" placeholder is taken literally
skip("4.0.0")
}
}

dependencies {
library("com.zaxxer:HikariCP:3.0.0")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.hikaricp;

import static java.util.Collections.singletonList;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.List;

@AutoService(InstrumentationModule.class)
public class HikariCpInstrumentationModule extends InstrumentationModule {

public HikariCpInstrumentationModule() {
super("hikaricp", "hikaricp-3.0");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return singletonList(new HikariPoolInstrumentation());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.hikaricp;

import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

public class HikariPoolInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("com.zaxxer.hikari.pool.HikariPool");
}

@Override
public void transform(TypeTransformer transformer) {
// this method is always called in the HikariPool constructor, even if the user does not
// configure anything
transformer.applyAdviceToMethod(
named("setMetricsTrackerFactory")
.and(takesArguments(1))
.and(takesArgument(0, named("com.zaxxer.hikari.metrics.MetricsTrackerFactory"))),
this.getClass().getName() + "$SetMetricsTrackerFactoryAdvice");
}

@SuppressWarnings("unused")
public static class SetMetricsTrackerFactoryAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(value = 0, readOnly = false) MetricsTrackerFactory metricsTrackerFactory) {

if (!(metricsTrackerFactory instanceof OpenTelemetryMetricsTrackerFactory)) {
metricsTrackerFactory = new OpenTelemetryMetricsTrackerFactory(metricsTrackerFactory);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.hikaricp;

import com.zaxxer.hikari.metrics.IMetricsTracker;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.DoubleHistogram;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.ObservableLongUpDownCounter;
import java.util.List;
import java.util.concurrent.TimeUnit;

final class OpenTelemetryMetricsTracker implements IMetricsTracker {

private static final double NANOS_PER_MS = TimeUnit.MILLISECONDS.toNanos(1);

private final IMetricsTracker userMetricsTracker;

private final List<ObservableLongUpDownCounter> observableInstruments;
private final LongCounter timeouts;
private final DoubleHistogram createTime;
private final DoubleHistogram waitTime;
private final DoubleHistogram useTime;
private final Attributes attributes;

OpenTelemetryMetricsTracker(
IMetricsTracker userMetricsTracker,
List<ObservableLongUpDownCounter> observableInstruments,
LongCounter timeouts,
DoubleHistogram createTime,
DoubleHistogram waitTime,
DoubleHistogram useTime,
Attributes attributes) {
this.userMetricsTracker = userMetricsTracker;
this.observableInstruments = observableInstruments;
this.timeouts = timeouts;
this.createTime = createTime;
this.waitTime = waitTime;
this.useTime = useTime;
this.attributes = attributes;
}

@Override
public void recordConnectionCreatedMillis(long connectionCreatedMillis) {
createTime.record((double) connectionCreatedMillis, attributes);
userMetricsTracker.recordConnectionCreatedMillis(connectionCreatedMillis);
}

@Override
public void recordConnectionAcquiredNanos(long elapsedAcquiredNanos) {
double millis = elapsedAcquiredNanos / NANOS_PER_MS;
waitTime.record(millis, attributes);
userMetricsTracker.recordConnectionAcquiredNanos(elapsedAcquiredNanos);
}

@Override
public void recordConnectionUsageMillis(long elapsedBorrowedMillis) {
useTime.record((double) elapsedBorrowedMillis, attributes);
userMetricsTracker.recordConnectionUsageMillis(elapsedBorrowedMillis);
}

@Override
public void recordConnectionTimeout() {
timeouts.add(1, attributes);
userMetricsTracker.recordConnectionTimeout();
}

@Override
public void close() {
for (ObservableLongUpDownCounter observable : observableInstruments) {
observable.close();
}
userMetricsTracker.close();
}
}
Loading

0 comments on commit 5bcab32

Please sign in to comment.