Skip to content
This repository was archived by the owner on Feb 1, 2024. It is now read-only.

Commit ba912bc

Browse files
authored
Implement C3P0 connection pool metrics (open-telemetry#6174)
* C3P0 connection pool metrics * Use PooledDataSource instead of specific implementation * Add C3P0 readme * RuntimeException in case of underlying SQLException * Use ISE instead of RuntimeException
1 parent 11dd079 commit ba912bc

File tree

14 files changed

+516
-1
lines changed

14 files changed

+516
-1
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
group.set("com.mchange")
8+
module.set("c3p0")
9+
versions.set("[0.9.2,)")
10+
assertInverse.set(true)
11+
// these versions have missing dependencies in maven central
12+
skip("0.9.2-pre2-RELEASE", "0.9.2-pre3")
13+
}
14+
}
15+
16+
dependencies {
17+
library("com.mchange:c3p0:0.9.2")
18+
19+
implementation(project(":instrumentation:c3p0-0.9:library"))
20+
21+
testImplementation(project(":instrumentation:c3p0-0.9:testing"))
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.c3p0;
7+
8+
import static io.opentelemetry.javaagent.instrumentation.c3p0.C3p0Singletons.telemetry;
9+
import static net.bytebuddy.matcher.ElementMatchers.named;
10+
11+
import com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource;
12+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
13+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
14+
import net.bytebuddy.asm.Advice;
15+
import net.bytebuddy.description.type.TypeDescription;
16+
import net.bytebuddy.matcher.ElementMatcher;
17+
18+
final class AbstractPoolBackedDataSourceInstrumentation implements TypeInstrumentation {
19+
20+
@Override
21+
public ElementMatcher<TypeDescription> typeMatcher() {
22+
return named("com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource");
23+
}
24+
25+
@Override
26+
public void transform(TypeTransformer transformer) {
27+
transformer.applyAdviceToMethod(
28+
named("resetPoolManager"), this.getClass().getName() + "$ResetPoolManagerAdvice");
29+
transformer.applyAdviceToMethod(named("close"), this.getClass().getName() + "$CloseAdvice");
30+
}
31+
32+
@SuppressWarnings("unused")
33+
public static class ResetPoolManagerAdvice {
34+
35+
@Advice.OnMethodExit(suppress = Throwable.class)
36+
public static void onExit(@Advice.This AbstractPoolBackedDataSource dataSource) {
37+
telemetry().registerMetrics(dataSource);
38+
}
39+
}
40+
41+
@SuppressWarnings("unused")
42+
public static class CloseAdvice {
43+
44+
@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
45+
public static void onExit(@Advice.This AbstractPoolBackedDataSource dataSource) {
46+
telemetry().unregisterMetrics(dataSource);
47+
}
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.c3p0;
7+
8+
import com.google.auto.service.AutoService;
9+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
10+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
11+
import java.util.Collections;
12+
import java.util.List;
13+
14+
@AutoService(InstrumentationModule.class)
15+
public class C3p0InstrumentationModule extends InstrumentationModule {
16+
17+
public C3p0InstrumentationModule() {
18+
super("c3p0", "c3p0-0.9");
19+
}
20+
21+
@Override
22+
public List<TypeInstrumentation> typeInstrumentations() {
23+
return Collections.singletonList(new AbstractPoolBackedDataSourceInstrumentation());
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.c3p0;
7+
8+
import io.opentelemetry.api.GlobalOpenTelemetry;
9+
import io.opentelemetry.instrumentation.c3p0.C3p0Telemetry;
10+
11+
public final class C3p0Singletons {
12+
13+
private static final C3p0Telemetry c3p0Telemetry =
14+
C3p0Telemetry.create(GlobalOpenTelemetry.get());
15+
16+
public static C3p0Telemetry telemetry() {
17+
return c3p0Telemetry;
18+
}
19+
20+
private C3p0Singletons() {}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.c3p0;
7+
8+
import com.mchange.v2.c3p0.PooledDataSource;
9+
import io.opentelemetry.instrumentation.c3p0.AbstractC3p0InstrumentationTest;
10+
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
11+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
12+
import org.junit.jupiter.api.extension.RegisterExtension;
13+
14+
public class C3p0InstrumentationTest extends AbstractC3p0InstrumentationTest {
15+
16+
@RegisterExtension
17+
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
18+
19+
@Override
20+
protected InstrumentationExtension testing() {
21+
return testing;
22+
}
23+
24+
@Override
25+
protected void configure(PooledDataSource dataSource) {}
26+
27+
@Override
28+
protected void shutdown(PooledDataSource dataSource) {}
29+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Manual Instrumentation for C3P0
2+
3+
Provides OpenTelemetry instrumentation for [C3P0](https://www.mchange.com/projects/c3p0/).
4+
5+
## Quickstart
6+
7+
### Add these dependencies to your project:
8+
9+
Replace `OPENTELEMETRY_VERSION` with the latest stable
10+
[release](https://mvnrepository.com/artifact/io.opentelemetry). `Minimum version: 1.15.0`
11+
12+
For Maven, add to your `pom.xml` dependencies:
13+
14+
```xml
15+
16+
<dependencies>
17+
<dependency>
18+
<groupId>io.opentelemetry.instrumentation</groupId>
19+
<artifactId>opentelemetry-c3p0-0.9</artifactId>
20+
<version>OPENTELEMETRY_VERSION</version>
21+
</dependency>
22+
</dependencies>
23+
```
24+
25+
For Gradle, add to your dependencies:
26+
27+
```groovy
28+
implementation("io.opentelemetry.instrumentation:opentelemetry-c3p0-0.9:OPENTELEMETRY_VERSION")
29+
```
30+
31+
### Usage
32+
33+
The instrumentation library allows registering `PooledDataSource` instances for
34+
collecting OpenTelemetry-based metrics.
35+
36+
```java
37+
C3p0Telemetry c3p0Telemetry;
38+
39+
void configure(OpenTelemetry openTelemetry, PooledDataSource dataSource) {
40+
c3p0Telemetry = C3p0Telemetry.create(openTelemetry);
41+
c3p0Telemetry.registerMetrics(dataSource);
42+
}
43+
44+
void destroy(PooledDataSource dataSource) {
45+
c3p0Telemetry.unregisterMetrics(dataSource);
46+
}
47+
```
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
plugins {
2+
id("otel.library-instrumentation")
3+
id("otel.nullaway-conventions")
4+
}
5+
6+
dependencies {
7+
library("com.mchange:c3p0:0.9.2")
8+
9+
testImplementation(project(":instrumentation:c3p0-0.9:testing"))
10+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.c3p0;
7+
8+
import com.mchange.v2.c3p0.PooledDataSource;
9+
import io.opentelemetry.api.OpenTelemetry;
10+
11+
public final class C3p0Telemetry {
12+
/** Returns a new {@link C3p0Telemetry} configured with the given {@link OpenTelemetry}. */
13+
public static C3p0Telemetry create(OpenTelemetry openTelemetry) {
14+
return new C3p0Telemetry(openTelemetry);
15+
}
16+
17+
private final OpenTelemetry openTelemetry;
18+
19+
private C3p0Telemetry(OpenTelemetry openTelemetry) {
20+
this.openTelemetry = openTelemetry;
21+
}
22+
23+
/** Start collecting metrics for given connection pool. */
24+
public void registerMetrics(PooledDataSource dataSource) {
25+
ConnectionPoolMetrics.registerMetrics(openTelemetry, dataSource);
26+
}
27+
28+
/** Stop collecting metrics for given connection pool. */
29+
public void unregisterMetrics(PooledDataSource dataSource) {
30+
ConnectionPoolMetrics.unregisterMetrics(dataSource);
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.c3p0;
7+
8+
import com.mchange.v2.c3p0.PooledDataSource;
9+
import io.opentelemetry.api.OpenTelemetry;
10+
import io.opentelemetry.api.metrics.ObservableLongUpDownCounter;
11+
import io.opentelemetry.instrumentation.api.metrics.db.DbConnectionPoolMetrics;
12+
import java.sql.SQLException;
13+
import java.util.Arrays;
14+
import java.util.List;
15+
import java.util.Map;
16+
import java.util.concurrent.ConcurrentHashMap;
17+
import java.util.function.LongSupplier;
18+
import javax.annotation.Nullable;
19+
20+
final class ConnectionPoolMetrics {
21+
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.c3p0-0.9";
22+
23+
// a weak map does not make sense here because each Meter holds a reference to the dataSource
24+
// PooledDataSource implements equals() & hashCode() in IdentityTokenResolvable,
25+
// that's why we wrap it with IdentityDataSourceKey that uses identity comparison instead
26+
private static final Map<IdentityDataSourceKey, List<ObservableLongUpDownCounter>>
27+
dataSourceMetrics = new ConcurrentHashMap<>();
28+
29+
public static void registerMetrics(OpenTelemetry openTelemetry, PooledDataSource dataSource) {
30+
dataSourceMetrics.compute(
31+
new IdentityDataSourceKey(dataSource),
32+
(key, existingCounters) ->
33+
ConnectionPoolMetrics.createMeters(openTelemetry, key, existingCounters));
34+
}
35+
36+
private static List<ObservableLongUpDownCounter> createMeters(
37+
OpenTelemetry openTelemetry,
38+
IdentityDataSourceKey key,
39+
List<ObservableLongUpDownCounter> existingCounters) {
40+
// remove old counters from the registry in case they were already there
41+
removeMetersFromRegistry(existingCounters);
42+
43+
PooledDataSource dataSource = key.dataSource;
44+
45+
DbConnectionPoolMetrics metrics =
46+
DbConnectionPoolMetrics.create(
47+
openTelemetry, INSTRUMENTATION_NAME, dataSource.getDataSourceName());
48+
49+
return Arrays.asList(
50+
metrics.usedConnections(wrapThrowingSupplier(dataSource::getNumBusyConnectionsDefaultUser)),
51+
metrics.idleConnections(wrapThrowingSupplier(dataSource::getNumIdleConnectionsDefaultUser)),
52+
metrics.pendingRequestsForConnection(
53+
wrapThrowingSupplier(dataSource::getNumThreadsAwaitingCheckoutDefaultUser)));
54+
}
55+
56+
public static void unregisterMetrics(PooledDataSource dataSource) {
57+
List<ObservableLongUpDownCounter> meters =
58+
dataSourceMetrics.remove(new IdentityDataSourceKey(dataSource));
59+
removeMetersFromRegistry(meters);
60+
}
61+
62+
private static void removeMetersFromRegistry(
63+
@Nullable List<ObservableLongUpDownCounter> observableInstruments) {
64+
if (observableInstruments != null) {
65+
for (ObservableLongUpDownCounter observable : observableInstruments) {
66+
observable.close();
67+
}
68+
}
69+
}
70+
71+
/**
72+
* A wrapper over {@link PooledDataSource} that implements identity comparison in its {@link
73+
* #equals(Object)} and {@link #hashCode()} methods.
74+
*/
75+
static final class IdentityDataSourceKey {
76+
final PooledDataSource dataSource;
77+
78+
IdentityDataSourceKey(PooledDataSource dataSource) {
79+
this.dataSource = dataSource;
80+
}
81+
82+
@Override
83+
@SuppressWarnings("ReferenceEquality")
84+
public boolean equals(@Nullable Object o) {
85+
if (this == o) {
86+
return true;
87+
}
88+
if (o == null || getClass() != o.getClass()) {
89+
return false;
90+
}
91+
IdentityDataSourceKey that = (IdentityDataSourceKey) o;
92+
return dataSource == that.dataSource;
93+
}
94+
95+
@Override
96+
public int hashCode() {
97+
return System.identityHashCode(dataSource);
98+
}
99+
100+
@Override
101+
public String toString() {
102+
return dataSource.toString();
103+
}
104+
}
105+
106+
static LongSupplier wrapThrowingSupplier(DataSourceIntSupplier supplier) {
107+
return () -> {
108+
try {
109+
return supplier.getAsInt();
110+
} catch (SQLException e) {
111+
throw new IllegalStateException("Failed to get C3P0 datasource metric", e);
112+
}
113+
};
114+
}
115+
116+
@FunctionalInterface
117+
interface DataSourceIntSupplier {
118+
int getAsInt() throws SQLException;
119+
}
120+
121+
private ConnectionPoolMetrics() {}
122+
}

0 commit comments

Comments
 (0)