Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Oracle UCP connection pool metrics #6099

Merged
merged 3 commits into from
Jun 3, 2022
Merged
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
1 change: 1 addition & 0 deletions docs/standalone-library-instrumentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ that can be used if you prefer that over using the Java agent:
* [Logback](../instrumentation/logback/logback-appender-1.0/library)
* [MongoDB Driver](../instrumentation/mongo/mongo-3.1/library)
* [OkHttp](../instrumentation/okhttp/okhttp-3.0/library)
* [Oracle UCP](../instrumentation/oracle-ucp-11.2/library)
* [OSHI](../instrumentation/oshi/library)
* [Reactor](../instrumentation/reactor/reactor-3.1/library)
* [RocketMQ](../instrumentation/rocketmq-client-4.8/library)
Expand Down
1 change: 1 addition & 0 deletions docs/supported-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ These are the supported libraries and frameworks:
| [MongoDB Driver](https://mongodb.github.io/mongo-java-driver/) | 3.1+ |
| [Netty](https:/netty/netty) | 3.8+ |
| [OkHttp](https:/square/okhttp/) | 2.2+ |
| [Oracle UCP](https://docs.oracle.com/database/121/JJUCP/) | 11.2+ |
| [OSHI](https:/oshi/oshi/) | 5.3.1+ |
| [Play](https:/playframework/playframework) | 2.4+ (not including 2.8.x yet) |
| [Play WS](https:/playframework/play-ws) | 1.0+ |
Expand Down
20 changes: 20 additions & 0 deletions instrumentation/oracle-ucp-11.2/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("com.oracle.database.jdbc")
module.set("ucp")
versions.set("[,)")
assertInverse.set(true)
}
}

dependencies {
library("com.oracle.database.jdbc:ucp:11.2.0.4")

implementation(project(":instrumentation:oracle-ucp-11.2:library"))

testImplementation(project(":instrumentation:oracle-ucp-11.2:testing"))
}
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.oracleucp;

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 OracleUcpInstrumentationModule extends InstrumentationModule {

public OracleUcpInstrumentationModule() {
super("oracle-ucp", "oracle-ucp-11.2");
}

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

package io.opentelemetry.javaagent.instrumentation.oracleucp;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.oracleucp.OracleUcpTelemetry;

public final class OracleUcpSingletons {

private static final OracleUcpTelemetry oracleUcpTelemetry =
OracleUcpTelemetry.create(GlobalOpenTelemetry.get());

public static OracleUcpTelemetry telemetry() {
return oracleUcpTelemetry;
}

private OracleUcpSingletons() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.oracleucp;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static io.opentelemetry.javaagent.instrumentation.oracleucp.OracleUcpSingletons.telemetry;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

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;
import oracle.ucp.UniversalConnectionPool;

public class UniversalConnectionPoolInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
return hasClassesNamed("oracle.ucp.UniversalConnectionPool");
}

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return implementsInterface(named("oracle.ucp.UniversalConnectionPool"));
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("start")
.and(takesArguments(0).or(takesArguments(1).and(takesArgument(0, boolean.class)))),
this.getClass().getName() + "$StartAdvice");
transformer.applyAdviceToMethod(
named("stop").and(takesArguments(0)), this.getClass().getName() + "$StopAdvice");
}

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

@Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(@Advice.This UniversalConnectionPool connectionPool) {
telemetry().registerMetrics(connectionPool);
}
}

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

@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void onExit(@Advice.This UniversalConnectionPool connectionPool) {
telemetry().unregisterMetrics(connectionPool);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.oracleucp;

import io.opentelemetry.instrumentation.oracleucp.AbstractOracleUcpInstrumentationTest;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import oracle.ucp.jdbc.PoolDataSource;
import org.junit.jupiter.api.extension.RegisterExtension;

class OracleUcpInstrumentationTest extends AbstractOracleUcpInstrumentationTest {

@RegisterExtension
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();

@Override
protected InstrumentationExtension testing() {
return testing;
}

@Override
protected void configure(PoolDataSource connectionPool) {}

@Override
protected void shutdown(PoolDataSource connectionPool) {}
}
47 changes: 47 additions & 0 deletions instrumentation/oracle-ucp-11.2/library/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Manual Instrumentation for Oracle UCP

Provides OpenTelemetry instrumentation for [Oracle UCP](https://docs.oracle.com/database/121/JJUCP/).

## Quickstart

### Add these dependencies to your project:

Replace `OPENTELEMETRY_VERSION` with the latest stable
[release](https://mvnrepository.com/artifact/io.opentelemetry). `Minimum version: 1.15.0`

For Maven, add to your `pom.xml` dependencies:

```xml

<dependencies>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-oracle-ucp-11.2</artifactId>
<version>OPENTELEMETRY_VERSION</version>
</dependency>
</dependencies>
```

For Gradle, add to your dependencies:

```groovy
implementation("io.opentelemetry.instrumentation:opentelemetry-oracle-ucp-11.2:OPENTELEMETRY_VERSION")
```

### Usage

The instrumentation library allows registering `UniversalConnectionPool` instances for collecting
OpenTelemetry-based metrics.

```java
OracleUcpTelemetry oracleUcpTelemetry;

void configure(OpenTelemetry openTelemetry, UniversalConnectionPool universalConnectionPool) {
oracleUcpTelemetry = OracleUcpTelemetry.create(openTelemetry);
oracleUcpTelemetry.registerMetrics(universalConnectionPool);
}

void destroy(UniversalConnectionPool universalConnectionPool) {
oracleUcpTelemetry.unregisterMetrics(universalConnectionPool);
}
```
10 changes: 10 additions & 0 deletions instrumentation/oracle-ucp-11.2/library/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
plugins {
id("otel.library-instrumentation")
id("otel.nullaway-conventions")
}

dependencies {
library("com.oracle.database.jdbc:ucp:11.2.0.4")

testImplementation(project(":instrumentation:oracle-ucp-11.2:testing"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.oracleucp;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.metrics.ObservableLongUpDownCounter;
import io.opentelemetry.instrumentation.api.metrics.db.DbConnectionPoolMetrics;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import oracle.ucp.UniversalConnectionPool;

final class ConnectionPoolMetrics {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.orcale-ucp-11.2";

// a weak map does not make sense here because each Meter holds a reference to the connection pool
// none of the UniversalConnectionPool implementations contain equals()/hashCode(), so it's safe
// to keep them in a plain ConcurrentHashMap
private static final Map<UniversalConnectionPool, List<ObservableLongUpDownCounter>>
dataSourceMetrics = new ConcurrentHashMap<>();

public static void registerMetrics(
OpenTelemetry openTelemetry, UniversalConnectionPool connectionPool) {
dataSourceMetrics.computeIfAbsent(
connectionPool, (unused) -> createMeters(openTelemetry, connectionPool));
}

private static List<ObservableLongUpDownCounter> createMeters(
OpenTelemetry openTelemetry, UniversalConnectionPool connectionPool) {
DbConnectionPoolMetrics metrics =
DbConnectionPoolMetrics.create(
openTelemetry, INSTRUMENTATION_NAME, connectionPool.getName());

return Arrays.asList(
metrics.usedConnections(connectionPool::getBorrowedConnectionsCount),
metrics.idleConnections(connectionPool::getAvailableConnectionsCount),
metrics.maxConnections(() -> connectionPool.getStatistics().getPeakConnectionsCount()),
metrics.pendingRequestsForConnection(
() -> connectionPool.getStatistics().getPendingRequestsCount()));
}

public static void unregisterMetrics(UniversalConnectionPool connectionPool) {
List<ObservableLongUpDownCounter> observableInstruments =
dataSourceMetrics.remove(connectionPool);
if (observableInstruments != null) {
for (ObservableLongUpDownCounter observable : observableInstruments) {
observable.close();
}
}
}

private ConnectionPoolMetrics() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.oracleucp;

import io.opentelemetry.api.OpenTelemetry;
import oracle.ucp.UniversalConnectionPool;

/** Entrypoint for instrumenting Oracle UCP database connection pools. */
public final class OracleUcpTelemetry {

/** Returns a new {@link OracleUcpTelemetry} configured with the given {@link OpenTelemetry}. */
public static OracleUcpTelemetry create(OpenTelemetry openTelemetry) {
return new OracleUcpTelemetry(openTelemetry);
}

private final OpenTelemetry openTelemetry;

private OracleUcpTelemetry(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
}

/** Start collecting metrics for given connection pool. */
public void registerMetrics(UniversalConnectionPool universalConnectionPool) {
ConnectionPoolMetrics.registerMetrics(openTelemetry, universalConnectionPool);
}

/** Stop collecting metrics for given connection pool. */
public void unregisterMetrics(UniversalConnectionPool universalConnectionPool) {
ConnectionPoolMetrics.unregisterMetrics(universalConnectionPool);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.oracleucp;

import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import oracle.ucp.UniversalConnectionPool;
import oracle.ucp.admin.UniversalConnectionPoolManagerImpl;
import oracle.ucp.jdbc.PoolDataSource;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.extension.RegisterExtension;

public class OracleUcpInstrumentationTest extends AbstractOracleUcpInstrumentationTest {

@RegisterExtension
static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();

private static OracleUcpTelemetry telemetry;

@Override
protected InstrumentationExtension testing() {
return testing;
}

@BeforeAll
static void setup() {
telemetry = OracleUcpTelemetry.create(testing.getOpenTelemetry());
}

@Override
protected void configure(PoolDataSource connectionPool) throws Exception {
UniversalConnectionPool universalConnectionPool =
UniversalConnectionPoolManagerImpl.getUniversalConnectionPoolManager()
.getConnectionPool(connectionPool.getConnectionPoolName());
telemetry.registerMetrics(universalConnectionPool);
}

@Override
protected void shutdown(PoolDataSource connectionPool) throws Exception {
UniversalConnectionPool universalConnectionPool =
UniversalConnectionPoolManagerImpl.getUniversalConnectionPoolManager()
.getConnectionPool(connectionPool.getConnectionPoolName());
telemetry.unregisterMetrics(universalConnectionPool);
}
}
11 changes: 11 additions & 0 deletions instrumentation/oracle-ucp-11.2/testing/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
plugins {
id("otel.java-conventions")
}

dependencies {
api(project(":testing-common"))
api("org.mockito:mockito-core")
api("org.mockito:mockito-junit-jupiter")

compileOnly("com.oracle.database.jdbc:ucp:11.2.0.4")
}
Loading