-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement HikariCP connection pool metrics (open-telemetry#6003)
* Implement HikariCP connection pool metrics * rebase after SDK update * fix muzzle * code review comments
- Loading branch information
Showing
11 changed files
with
747 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
158 changes: 158 additions & 0 deletions
158
...rc/main/java/io/opentelemetry/instrumentation/api/metrics/db/DbConnectionPoolMetrics.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
26 changes: 26 additions & 0 deletions
26
...va/io/opentelemetry/javaagent/instrumentation/hikaricp/HikariCpInstrumentationModule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
...n/java/io/opentelemetry/javaagent/instrumentation/hikaricp/HikariPoolInstrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} | ||
} |
78 changes: 78 additions & 0 deletions
78
...java/io/opentelemetry/javaagent/instrumentation/hikaricp/OpenTelemetryMetricsTracker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
Oops, something went wrong.