Skip to content

Commit

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

* Use PooledDataSource instead of specific implementation

* Add C3P0 readme

* RuntimeException in case of underlying SQLException

* Use ISE instead of RuntimeException
  • Loading branch information
agoallikmaa authored Jun 20, 2022
1 parent 11dd079 commit ba912bc
Show file tree
Hide file tree
Showing 14 changed files with 516 additions and 1 deletion.
22 changes: 22 additions & 0 deletions instrumentation/c3p0-0.9/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("com.mchange")
module.set("c3p0")
versions.set("[0.9.2,)")
assertInverse.set(true)
// these versions have missing dependencies in maven central
skip("0.9.2-pre2-RELEASE", "0.9.2-pre3")
}
}

dependencies {
library("com.mchange:c3p0:0.9.2")

implementation(project(":instrumentation:c3p0-0.9:library"))

testImplementation(project(":instrumentation:c3p0-0.9:testing"))
}
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.c3p0;

import static io.opentelemetry.javaagent.instrumentation.c3p0.C3p0Singletons.telemetry;
import static net.bytebuddy.matcher.ElementMatchers.named;

import com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource;
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;

final class AbstractPoolBackedDataSourceInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("resetPoolManager"), this.getClass().getName() + "$ResetPoolManagerAdvice");
transformer.applyAdviceToMethod(named("close"), this.getClass().getName() + "$CloseAdvice");
}

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

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

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

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

package io.opentelemetry.javaagent.instrumentation.c3p0;

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

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

public C3p0InstrumentationModule() {
super("c3p0", "c3p0-0.9");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Collections.singletonList(new AbstractPoolBackedDataSourceInstrumentation());
}
}
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.c3p0;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.c3p0.C3p0Telemetry;

public final class C3p0Singletons {

private static final C3p0Telemetry c3p0Telemetry =
C3p0Telemetry.create(GlobalOpenTelemetry.get());

public static C3p0Telemetry telemetry() {
return c3p0Telemetry;
}

private C3p0Singletons() {}
}
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.c3p0;

import com.mchange.v2.c3p0.PooledDataSource;
import io.opentelemetry.instrumentation.c3p0.AbstractC3p0InstrumentationTest;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import org.junit.jupiter.api.extension.RegisterExtension;

public class C3p0InstrumentationTest extends AbstractC3p0InstrumentationTest {

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

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

@Override
protected void configure(PooledDataSource dataSource) {}

@Override
protected void shutdown(PooledDataSource dataSource) {}
}
47 changes: 47 additions & 0 deletions instrumentation/c3p0-0.9/library/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Manual Instrumentation for C3P0

Provides OpenTelemetry instrumentation for [C3P0](https://www.mchange.com/projects/c3p0/).

## 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-c3p0-0.9</artifactId>
<version>OPENTELEMETRY_VERSION</version>
</dependency>
</dependencies>
```

For Gradle, add to your dependencies:

```groovy
implementation("io.opentelemetry.instrumentation:opentelemetry-c3p0-0.9:OPENTELEMETRY_VERSION")
```

### Usage

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

```java
C3p0Telemetry c3p0Telemetry;

void configure(OpenTelemetry openTelemetry, PooledDataSource dataSource) {
c3p0Telemetry = C3p0Telemetry.create(openTelemetry);
c3p0Telemetry.registerMetrics(dataSource);
}

void destroy(PooledDataSource dataSource) {
c3p0Telemetry.unregisterMetrics(dataSource);
}
```
10 changes: 10 additions & 0 deletions instrumentation/c3p0-0.9/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.mchange:c3p0:0.9.2")

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

package io.opentelemetry.instrumentation.c3p0;

import com.mchange.v2.c3p0.PooledDataSource;
import io.opentelemetry.api.OpenTelemetry;

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

private final OpenTelemetry openTelemetry;

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

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

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

package io.opentelemetry.instrumentation.c3p0;

import com.mchange.v2.c3p0.PooledDataSource;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.metrics.ObservableLongUpDownCounter;
import io.opentelemetry.instrumentation.api.metrics.db.DbConnectionPoolMetrics;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.LongSupplier;
import javax.annotation.Nullable;

final class ConnectionPoolMetrics {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.c3p0-0.9";

// a weak map does not make sense here because each Meter holds a reference to the dataSource
// PooledDataSource implements equals() & hashCode() in IdentityTokenResolvable,
// that's why we wrap it with IdentityDataSourceKey that uses identity comparison instead
private static final Map<IdentityDataSourceKey, List<ObservableLongUpDownCounter>>
dataSourceMetrics = new ConcurrentHashMap<>();

public static void registerMetrics(OpenTelemetry openTelemetry, PooledDataSource dataSource) {
dataSourceMetrics.compute(
new IdentityDataSourceKey(dataSource),
(key, existingCounters) ->
ConnectionPoolMetrics.createMeters(openTelemetry, key, existingCounters));
}

private static List<ObservableLongUpDownCounter> createMeters(
OpenTelemetry openTelemetry,
IdentityDataSourceKey key,
List<ObservableLongUpDownCounter> existingCounters) {
// remove old counters from the registry in case they were already there
removeMetersFromRegistry(existingCounters);

PooledDataSource dataSource = key.dataSource;

DbConnectionPoolMetrics metrics =
DbConnectionPoolMetrics.create(
openTelemetry, INSTRUMENTATION_NAME, dataSource.getDataSourceName());

return Arrays.asList(
metrics.usedConnections(wrapThrowingSupplier(dataSource::getNumBusyConnectionsDefaultUser)),
metrics.idleConnections(wrapThrowingSupplier(dataSource::getNumIdleConnectionsDefaultUser)),
metrics.pendingRequestsForConnection(
wrapThrowingSupplier(dataSource::getNumThreadsAwaitingCheckoutDefaultUser)));
}

public static void unregisterMetrics(PooledDataSource dataSource) {
List<ObservableLongUpDownCounter> meters =
dataSourceMetrics.remove(new IdentityDataSourceKey(dataSource));
removeMetersFromRegistry(meters);
}

private static void removeMetersFromRegistry(
@Nullable List<ObservableLongUpDownCounter> observableInstruments) {
if (observableInstruments != null) {
for (ObservableLongUpDownCounter observable : observableInstruments) {
observable.close();
}
}
}

/**
* A wrapper over {@link PooledDataSource} that implements identity comparison in its {@link
* #equals(Object)} and {@link #hashCode()} methods.
*/
static final class IdentityDataSourceKey {
final PooledDataSource dataSource;

IdentityDataSourceKey(PooledDataSource dataSource) {
this.dataSource = dataSource;
}

@Override
@SuppressWarnings("ReferenceEquality")
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
IdentityDataSourceKey that = (IdentityDataSourceKey) o;
return dataSource == that.dataSource;
}

@Override
public int hashCode() {
return System.identityHashCode(dataSource);
}

@Override
public String toString() {
return dataSource.toString();
}
}

static LongSupplier wrapThrowingSupplier(DataSourceIntSupplier supplier) {
return () -> {
try {
return supplier.getAsInt();
} catch (SQLException e) {
throw new IllegalStateException("Failed to get C3P0 datasource metric", e);
}
};
}

@FunctionalInterface
interface DataSourceIntSupplier {
int getAsInt() throws SQLException;
}

private ConnectionPoolMetrics() {}
}
Loading

0 comments on commit ba912bc

Please sign in to comment.