diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md
index 38c9a025a772..189c60b583ed 100644
--- a/docs/supported-libraries.md
+++ b/docs/supported-libraries.md
@@ -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+ |
diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/metrics/db/DbConnectionPoolMetrics.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/metrics/db/DbConnectionPoolMetrics.java
new file mode 100644
index 000000000000..e792130eeb1b
--- /dev/null
+++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/metrics/db/DbConnectionPoolMetrics.java
@@ -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 database
+ * client connection pool metrics semantic conventions.
+ */
+public final class DbConnectionPoolMetrics {
+
+ static final AttributeKey POOL_NAME = stringKey("pool.name");
+ static final AttributeKey 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;
+ }
+}
diff --git a/instrumentation/hikaricp-3.0/javaagent/build.gradle.kts b/instrumentation/hikaricp-3.0/javaagent/build.gradle.kts
new file mode 100644
index 000000000000..1558b737eb39
--- /dev/null
+++ b/instrumentation/hikaricp-3.0/javaagent/build.gradle.kts
@@ -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")
+}
diff --git a/instrumentation/hikaricp-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hikaricp/HikariCpInstrumentationModule.java b/instrumentation/hikaricp-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hikaricp/HikariCpInstrumentationModule.java
new file mode 100644
index 000000000000..bedff419d4ac
--- /dev/null
+++ b/instrumentation/hikaricp-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hikaricp/HikariCpInstrumentationModule.java
@@ -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 typeInstrumentations() {
+ return singletonList(new HikariPoolInstrumentation());
+ }
+}
diff --git a/instrumentation/hikaricp-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hikaricp/HikariPoolInstrumentation.java b/instrumentation/hikaricp-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hikaricp/HikariPoolInstrumentation.java
new file mode 100644
index 000000000000..e4cd141668ab
--- /dev/null
+++ b/instrumentation/hikaricp-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hikaricp/HikariPoolInstrumentation.java
@@ -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 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);
+ }
+ }
+ }
+}
diff --git a/instrumentation/hikaricp-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hikaricp/OpenTelemetryMetricsTracker.java b/instrumentation/hikaricp-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hikaricp/OpenTelemetryMetricsTracker.java
new file mode 100644
index 000000000000..b9f977692cab
--- /dev/null
+++ b/instrumentation/hikaricp-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hikaricp/OpenTelemetryMetricsTracker.java
@@ -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 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 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();
+ }
+}
diff --git a/instrumentation/hikaricp-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hikaricp/OpenTelemetryMetricsTrackerFactory.java b/instrumentation/hikaricp-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hikaricp/OpenTelemetryMetricsTrackerFactory.java
new file mode 100644
index 000000000000..058f13f72949
--- /dev/null
+++ b/instrumentation/hikaricp-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hikaricp/OpenTelemetryMetricsTrackerFactory.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.hikaricp;
+
+import com.zaxxer.hikari.metrics.IMetricsTracker;
+import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
+import com.zaxxer.hikari.metrics.PoolStats;
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.metrics.ObservableLongUpDownCounter;
+import io.opentelemetry.instrumentation.api.metrics.db.DbConnectionPoolMetrics;
+import java.util.Arrays;
+import java.util.List;
+import javax.annotation.Nullable;
+
+public final class OpenTelemetryMetricsTrackerFactory implements MetricsTrackerFactory {
+
+ private static final String INSTRUMENTATION_NAME = "io.opentelemetry.hikaricp-3.0";
+
+ @Nullable private final MetricsTrackerFactory userMetricsFactory;
+
+ public OpenTelemetryMetricsTrackerFactory(@Nullable MetricsTrackerFactory userMetricsFactory) {
+ this.userMetricsFactory = userMetricsFactory;
+ }
+
+ @Override
+ public IMetricsTracker create(String poolName, PoolStats poolStats) {
+ IMetricsTracker userMetricsTracker =
+ userMetricsFactory == null
+ ? NoopMetricsTracker.INSTANCE
+ : userMetricsFactory.create(poolName, poolStats);
+
+ DbConnectionPoolMetrics metrics =
+ DbConnectionPoolMetrics.create(GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, poolName);
+
+ List observableInstruments =
+ Arrays.asList(
+ metrics.usedConnections(poolStats::getActiveConnections),
+ metrics.idleConnections(poolStats::getIdleConnections),
+ metrics.minIdleConnections(poolStats::getMinConnections),
+ metrics.maxConnections(poolStats::getMaxConnections),
+ metrics.pendingRequestsForConnection(poolStats::getPendingThreads));
+
+ return new OpenTelemetryMetricsTracker(
+ userMetricsTracker,
+ observableInstruments,
+ metrics.connectionTimeouts(),
+ metrics.connectionCreateTime(),
+ metrics.connectionWaitTime(),
+ metrics.connectionUseTime(),
+ metrics.getAttributes());
+ }
+
+ enum NoopMetricsTracker implements IMetricsTracker {
+ INSTANCE
+ }
+}
diff --git a/instrumentation/hikaricp-3.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/hikaricp/HikariCpInstrumentationTest.java b/instrumentation/hikaricp-3.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/hikaricp/HikariCpInstrumentationTest.java
new file mode 100644
index 000000000000..c82890c3e111
--- /dev/null
+++ b/instrumentation/hikaricp-3.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/hikaricp/HikariCpInstrumentationTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.hikaricp;
+
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.metrics.IMetricsTracker;
+import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension;
+import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.db.DbConnectionPoolMetricsAssertions;
+import java.sql.Connection;
+import java.util.concurrent.TimeUnit;
+import javax.sql.DataSource;
+import org.assertj.core.api.AbstractIterableAssert;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class HikariCpInstrumentationTest {
+
+ @RegisterExtension
+ static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
+
+ @RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create();
+
+ @Mock DataSource dataSourceMock;
+ @Mock Connection connectionMock;
+ @Mock IMetricsTracker userMetricsMock;
+
+ @Test
+ void shouldReportMetrics() throws Exception {
+ // given
+ when(dataSourceMock.getConnection()).thenReturn(connectionMock);
+
+ HikariDataSource hikariDataSource = new HikariDataSource();
+ hikariDataSource.setPoolName("testPool");
+ hikariDataSource.setDataSource(dataSourceMock);
+
+ // when
+ Connection hikariConnection = hikariDataSource.getConnection();
+ TimeUnit.MILLISECONDS.sleep(100);
+ hikariConnection.close();
+
+ // then
+ DbConnectionPoolMetricsAssertions.create(testing, "io.opentelemetry.hikaricp-3.0", "testPool")
+ .disableMaxIdleConnections()
+ // no timeouts happen during this test
+ .disableConnectionTimeouts()
+ .assertConnectionPoolEmitsMetrics();
+
+ // when
+ hikariDataSource.close();
+
+ // sleep exporter interval
+ Thread.sleep(100);
+ testing.clearData();
+ Thread.sleep(100);
+
+ // then
+ testing.waitAndAssertMetrics(
+ "io.opentelemetry.hikaricp-3.0",
+ "db.client.connections.usage",
+ AbstractIterableAssert::isEmpty);
+ testing.waitAndAssertMetrics(
+ "io.opentelemetry.hikaricp-3.0",
+ "db.client.connections.idle.min",
+ AbstractIterableAssert::isEmpty);
+ testing.waitAndAssertMetrics(
+ "io.opentelemetry.hikaricp-3.0",
+ "db.client.connections.max",
+ AbstractIterableAssert::isEmpty);
+ testing.waitAndAssertMetrics(
+ "io.opentelemetry.hikaricp-3.0",
+ "db.client.connections.pending_requests",
+ AbstractIterableAssert::isEmpty);
+ }
+
+ @Test
+ void shouldNotBreakCustomUserMetrics() throws Exception {
+ // given
+ when(dataSourceMock.getConnection()).thenReturn(connectionMock);
+
+ HikariConfig hikariConfig = new HikariConfig();
+ hikariConfig.setPoolName("anotherTestPool");
+ hikariConfig.setDataSource(dataSourceMock);
+ hikariConfig.setMetricsTrackerFactory((poolName, poolStats) -> userMetricsMock);
+
+ HikariDataSource hikariDataSource = new HikariDataSource(hikariConfig);
+ cleanup.deferCleanup(hikariDataSource);
+
+ // when
+ Connection hikariConnection = hikariDataSource.getConnection();
+ TimeUnit.MILLISECONDS.sleep(100);
+ hikariConnection.close();
+
+ // then
+ DbConnectionPoolMetricsAssertions.create(
+ testing, "io.opentelemetry.hikaricp-3.0", "anotherTestPool")
+ .disableMaxIdleConnections()
+ // no timeouts happen during this test
+ .disableConnectionTimeouts()
+ .assertConnectionPoolEmitsMetrics();
+
+ verify(userMetricsMock, atLeastOnce()).recordConnectionCreatedMillis(anyLong());
+ verify(userMetricsMock, atLeastOnce()).recordConnectionAcquiredNanos(anyLong());
+ verify(userMetricsMock, atLeastOnce()).recordConnectionUsageMillis(anyLong());
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 120713163c05..999668df36cf 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -220,6 +220,7 @@ include(":instrumentation:hibernate:hibernate-3.3:javaagent")
include(":instrumentation:hibernate:hibernate-4.0:javaagent")
include(":instrumentation:hibernate:hibernate-common:javaagent")
include(":instrumentation:hibernate:hibernate-procedure-call-4.3:javaagent")
+include(":instrumentation:hikaricp-3.0:javaagent")
include(":instrumentation:http-url-connection:javaagent")
include(":instrumentation:hystrix-1.4:javaagent")
include(":instrumentation:java-http-client:javaagent")
diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/db/DbConnectionPoolMetricsAssertions.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/db/DbConnectionPoolMetricsAssertions.java
new file mode 100644
index 000000000000..30ae076b462e
--- /dev/null
+++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/db/DbConnectionPoolMetricsAssertions.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.testing.junit.db;
+
+import static io.opentelemetry.api.common.AttributeKey.stringKey;
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
+
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+
+public final class DbConnectionPoolMetricsAssertions {
+
+ public static DbConnectionPoolMetricsAssertions create(
+ InstrumentationExtension testing, String instrumentationName, String poolName) {
+ return new DbConnectionPoolMetricsAssertions(testing, instrumentationName, poolName);
+ }
+
+ private final InstrumentationExtension testing;
+ private final String instrumentationName;
+ private final String poolName;
+
+ private boolean testMaxIdleConnections = true;
+ private boolean testConnectionTimeouts = true;
+
+ DbConnectionPoolMetricsAssertions(
+ InstrumentationExtension testing, String instrumentationName, String poolName) {
+ this.testing = testing;
+ this.instrumentationName = instrumentationName;
+ this.poolName = poolName;
+ }
+
+ public DbConnectionPoolMetricsAssertions disableMaxIdleConnections() {
+ testMaxIdleConnections = false;
+ return this;
+ }
+
+ public DbConnectionPoolMetricsAssertions disableConnectionTimeouts() {
+ testConnectionTimeouts = false;
+ return this;
+ }
+
+ public void assertConnectionPoolEmitsMetrics() {
+ testing.waitAndAssertMetrics(
+ instrumentationName,
+ "db.client.connections.usage",
+ metrics ->
+ metrics.anySatisfy(
+ metric ->
+ assertThat(metric)
+ .hasUnit("connections")
+ .hasDescription(
+ "The number of connections that are currently in state described by the state attribute.")
+ .hasLongSumSatisfying(
+ sum ->
+ sum.isNotMonotonic()
+ .hasPointsSatisfying(
+ point ->
+ point.hasAttributes(
+ Attributes.of(
+ stringKey("pool.name"),
+ poolName,
+ stringKey("state"),
+ "idle")),
+ point ->
+ point.hasAttributes(
+ Attributes.of(
+ stringKey("pool.name"),
+ poolName,
+ stringKey("state"),
+ "used"))))));
+ testing.waitAndAssertMetrics(
+ instrumentationName,
+ "db.client.connections.idle.min",
+ metrics ->
+ metrics.anySatisfy(
+ metric ->
+ assertThat(metric)
+ .hasUnit("connections")
+ .hasDescription("The minimum number of idle open connections allowed.")
+ .hasLongSumSatisfying(
+ sum ->
+ sum.isNotMonotonic()
+ .hasPointsSatisfying(
+ point ->
+ point.hasAttributes(
+ Attributes.of(
+ stringKey("pool.name"), poolName))))));
+ if (testMaxIdleConnections) {
+ testing.waitAndAssertMetrics(
+ instrumentationName,
+ "db.client.connections.idle.max",
+ metrics ->
+ metrics.anySatisfy(
+ metric ->
+ assertThat(metric)
+ .hasUnit("connections")
+ .hasDescription("The maximum number of idle open connections allowed.")
+ .hasLongSumSatisfying(
+ sum ->
+ sum.isNotMonotonic()
+ .hasPointsSatisfying(
+ point ->
+ point.hasAttributes(
+ Attributes.of(
+ stringKey("pool.name"), poolName))))));
+ }
+ testing.waitAndAssertMetrics(
+ instrumentationName,
+ "db.client.connections.max",
+ metrics ->
+ metrics.anySatisfy(
+ metric ->
+ assertThat(metric)
+ .hasUnit("connections")
+ .hasDescription("The maximum number of open connections allowed.")
+ .hasLongSumSatisfying(
+ sum ->
+ sum.isNotMonotonic()
+ .hasPointsSatisfying(
+ point ->
+ point.hasAttributes(
+ Attributes.of(
+ stringKey("pool.name"), poolName))))));
+ testing.waitAndAssertMetrics(
+ instrumentationName,
+ "db.client.connections.pending_requests",
+ metrics ->
+ metrics.anySatisfy(
+ metric ->
+ assertThat(metric)
+ .hasUnit("requests")
+ .hasDescription(
+ "The number of pending requests for an open connection, cumulative for the entire pool.")
+ .hasLongSumSatisfying(
+ sum ->
+ sum.isNotMonotonic()
+ .hasPointsSatisfying(
+ point ->
+ point.hasAttributes(
+ Attributes.of(
+ stringKey("pool.name"), poolName))))));
+ if (testConnectionTimeouts) {
+ testing.waitAndAssertMetrics(
+ instrumentationName,
+ "db.client.connections.timeouts",
+ metrics ->
+ metrics.anySatisfy(
+ metric ->
+ assertThat(metric)
+ .hasUnit("timeouts")
+ .hasDescription(
+ "The number of connection timeouts that have occurred trying to obtain a connection from the pool.")
+ .hasLongSumSatisfying(
+ sum ->
+ sum.isMonotonic()
+ .hasPointsSatisfying(
+ point ->
+ point.hasAttributes(
+ Attributes.of(
+ stringKey("pool.name"), poolName))))));
+ }
+ testing.waitAndAssertMetrics(
+ instrumentationName,
+ "db.client.connections.create_time",
+ metrics ->
+ metrics.anySatisfy(
+ metric ->
+ assertThat(metric)
+ .hasUnit("ms")
+ .hasDescription("The time it took to create a new connection.")
+ .hasHistogramSatisfying(
+ histogram ->
+ histogram.hasPointsSatisfying(
+ point ->
+ point.hasAttributes(
+ Attributes.of(stringKey("pool.name"), poolName))))));
+ testing.waitAndAssertMetrics(
+ instrumentationName,
+ "db.client.connections.wait_time",
+ metrics ->
+ metrics.anySatisfy(
+ metric ->
+ assertThat(metric)
+ .hasUnit("ms")
+ .hasDescription(
+ "The time it took to obtain an open connection from the pool.")
+ .hasHistogramSatisfying(
+ histogram ->
+ histogram.hasPointsSatisfying(
+ point ->
+ point.hasAttributes(
+ Attributes.of(stringKey("pool.name"), poolName))))));
+ testing.waitAndAssertMetrics(
+ instrumentationName,
+ "db.client.connections.use_time",
+ metrics ->
+ metrics.anySatisfy(
+ metric ->
+ assertThat(metric)
+ .hasUnit("ms")
+ .hasDescription(
+ "The time between borrowing a connection and returning it to the pool.")
+ .hasHistogramSatisfying(
+ histogram ->
+ histogram.hasPointsSatisfying(
+ point ->
+ point.hasAttributes(
+ Attributes.of(stringKey("pool.name"), poolName))))));
+ }
+}
diff --git a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/IgnoredTestTypesConfigurer.java b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/IgnoredTestTypesConfigurer.java
new file mode 100644
index 000000000000..49d9dde86a17
--- /dev/null
+++ b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/IgnoredTestTypesConfigurer.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.testing;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.instrumentation.api.config.Config;
+import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesBuilder;
+import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesConfigurer;
+
+@AutoService(IgnoredTypesConfigurer.class)
+public class IgnoredTestTypesConfigurer implements IgnoredTypesConfigurer {
+
+ @Override
+ public void configure(Config config, IgnoredTypesBuilder builder) {
+ // we don't want to instrument auto-generated mocks
+ builder
+ .ignoreClass("org.mockito")
+ .ignoreClass("com.zaxxer.hikari.metrics.IMetricsTracker$MockitoMock$");
+ }
+}