Skip to content

Commit 20ab012

Browse files
AlchemyDingtrask
andauthored
Add instrumentation for druid connection pool (#9935)
Co-authored-by: Trask Stalnaker <[email protected]>
1 parent 46ac349 commit 20ab012

File tree

13 files changed

+416
-0
lines changed

13 files changed

+416
-0
lines changed

docs/supported-libraries.md

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ These are the supported libraries and frameworks:
2121
|---------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |----------------------------------------------------------------------------------------|
2222
| [Akka Actors](https://doc.akka.io/docs/akka/current/typed/index.html) | 2.3+ | N/A | Context propagation |
2323
| [Akka HTTP](https://doc.akka.io/docs/akka-http/current/index.html) | 10.0+ | N/A | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics] |
24+
| [Alibaba Druid](https://github.com/alibaba/druid) | 1.0+ | [opentelemetry-alibaba-druid-1.0](../instrumentation/alibaba-druid-1.0/library) | [Database Pool Metrics] |
2425
| [Apache Axis2](https://axis.apache.org/axis2/java/core/) | 1.6+ | N/A | Provides `http.route` [2], Controller Spans [3] |
2526
| [Apache Camel](https://camel.apache.org/) | 2.20+ (not including 3.x yet) | N/A | Dependent on components in use |
2627
| [Apache CXF JAX-RS](https://cxf.apache.org/) | 3.2+ | N/A | Provides `http.route` [2], Controller Spans [3] |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
group.set("com.alibaba")
8+
module.set("druid")
9+
versions.set("(,)")
10+
skip("1.0.30")
11+
}
12+
}
13+
14+
dependencies {
15+
library("com.alibaba:druid:1.0.0")
16+
17+
implementation(project(":instrumentation:alibaba-druid-1.0:library"))
18+
19+
testImplementation(project(":instrumentation:alibaba-druid-1.0:testing"))
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.alibabadruid.v1_0;
7+
8+
import static io.opentelemetry.javaagent.instrumentation.alibabadruid.v1_0.DruidSingletons.telemetry;
9+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
10+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
11+
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
12+
import static net.bytebuddy.matcher.ElementMatchers.named;
13+
14+
import com.alibaba.druid.pool.DruidDataSourceMBean;
15+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
16+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
17+
import javax.management.ObjectName;
18+
import net.bytebuddy.asm.Advice;
19+
import net.bytebuddy.description.type.TypeDescription;
20+
import net.bytebuddy.matcher.ElementMatcher;
21+
22+
public class DruidDataSourceInstrumentation implements TypeInstrumentation {
23+
@Override
24+
public ElementMatcher<TypeDescription> typeMatcher() {
25+
return named("com.alibaba.druid.stat.DruidDataSourceStatManager");
26+
}
27+
28+
@Override
29+
public void transform(TypeTransformer typeTransformer) {
30+
typeTransformer.applyAdviceToMethod(
31+
isMethod().and(isPublic()).and(isStatic()).and(named("addDataSource")),
32+
this.getClass().getName() + "$AddDataSourceAdvice");
33+
34+
typeTransformer.applyAdviceToMethod(
35+
isMethod().and(isPublic()).and(isStatic()).and(named("removeDataSource")),
36+
this.getClass().getName() + "$RemoveDataSourceAdvice");
37+
}
38+
39+
@SuppressWarnings("unused")
40+
public static class AddDataSourceAdvice {
41+
42+
@Advice.OnMethodExit(suppress = Throwable.class)
43+
public static void onExit(
44+
@Advice.Argument(0) Object dataSource, @Advice.Return ObjectName objectName) {
45+
DruidDataSourceMBean druidDataSource = (DruidDataSourceMBean) dataSource;
46+
String dataSourceName =
47+
objectName.getKeyProperty("type") + "-" + objectName.getKeyProperty("id");
48+
telemetry().registerMetrics(druidDataSource, dataSourceName);
49+
}
50+
}
51+
52+
@SuppressWarnings("unused")
53+
public static class RemoveDataSourceAdvice {
54+
@Advice.OnMethodExit(suppress = Throwable.class)
55+
public static void onExit(@Advice.Argument(0) Object dataSource) {
56+
DruidDataSourceMBean druidDataSource = (DruidDataSourceMBean) dataSource;
57+
telemetry().unregisterMetrics(druidDataSource);
58+
}
59+
}
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.alibabadruid.v1_0;
7+
8+
import static java.util.Collections.singletonList;
9+
10+
import com.google.auto.service.AutoService;
11+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
12+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
13+
import java.util.List;
14+
15+
@AutoService(InstrumentationModule.class)
16+
public class DruidInstrumentationModule extends InstrumentationModule {
17+
18+
public DruidInstrumentationModule() {
19+
super("alibaba-druid", "alibaba-druid-1.0");
20+
}
21+
22+
@Override
23+
public List<TypeInstrumentation> typeInstrumentations() {
24+
return singletonList(new DruidDataSourceInstrumentation());
25+
}
26+
}
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.alibabadruid.v1_0;
7+
8+
import io.opentelemetry.api.GlobalOpenTelemetry;
9+
import io.opentelemetry.instrumentation.alibabadruid.v1_0.DruidTelemetry;
10+
11+
public final class DruidSingletons {
12+
13+
private static final DruidTelemetry druidTelemetry =
14+
DruidTelemetry.create(GlobalOpenTelemetry.get());
15+
16+
public static DruidTelemetry telemetry() {
17+
return druidTelemetry;
18+
}
19+
20+
private DruidSingletons() {}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.alibabadruid.v1_0;
7+
8+
import com.alibaba.druid.pool.DruidDataSource;
9+
import com.alibaba.druid.stat.DruidDataSourceStatManager;
10+
import io.opentelemetry.instrumentation.alibabadruid.AbstractDruidInstrumentationTest;
11+
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
12+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
13+
import org.junit.jupiter.api.extension.RegisterExtension;
14+
15+
public class DruidInstrumentationTest extends AbstractDruidInstrumentationTest {
16+
17+
@RegisterExtension
18+
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
19+
20+
@Override
21+
protected InstrumentationExtension testing() {
22+
return testing;
23+
}
24+
25+
@Override
26+
protected void configure(DruidDataSource dataSource, String name) throws Exception {
27+
DruidDataSourceStatManager.addDataSource(dataSource, name);
28+
}
29+
30+
@Override
31+
protected void shutdown(DruidDataSource dataSource) throws Exception {
32+
DruidDataSourceStatManager.removeDataSource(dataSource);
33+
}
34+
}
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.alibaba:druid:1.0.0")
8+
9+
testImplementation(project(":instrumentation:alibaba-druid-1.0:testing"))
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.alibabadruid.v1_0;
7+
8+
import com.alibaba.druid.pool.DruidDataSourceMBean;
9+
import io.opentelemetry.api.OpenTelemetry;
10+
import io.opentelemetry.api.common.Attributes;
11+
import io.opentelemetry.api.metrics.BatchCallback;
12+
import io.opentelemetry.api.metrics.ObservableLongMeasurement;
13+
import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbConnectionPoolMetrics;
14+
import java.util.Map;
15+
import java.util.concurrent.ConcurrentHashMap;
16+
17+
final class ConnectionPoolMetrics {
18+
19+
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.alibaba-druid-1.0";
20+
21+
private static final Map<DruidDataSourceMBean, BatchCallback> dataSourceMetrics =
22+
new ConcurrentHashMap<>();
23+
24+
public static void registerMetrics(
25+
OpenTelemetry openTelemetry, DruidDataSourceMBean dataSource, String dataSourceName) {
26+
DbConnectionPoolMetrics metrics =
27+
DbConnectionPoolMetrics.create(openTelemetry, INSTRUMENTATION_NAME, dataSourceName);
28+
29+
ObservableLongMeasurement connections = metrics.connections();
30+
ObservableLongMeasurement minIdleConnections = metrics.minIdleConnections();
31+
ObservableLongMeasurement maxIdleConnections = metrics.maxIdleConnections();
32+
ObservableLongMeasurement maxConnections = metrics.maxConnections();
33+
ObservableLongMeasurement pendingRequestsForConnection = metrics.pendingRequestsForConnection();
34+
35+
Attributes attributes = metrics.getAttributes();
36+
Attributes usedConnectionsAttributes = metrics.getUsedConnectionsAttributes();
37+
Attributes idleConnectionsAttributes = metrics.getIdleConnectionsAttributes();
38+
39+
BatchCallback callback =
40+
metrics.batchCallback(
41+
() -> {
42+
connections.record(dataSource.getActiveCount(), usedConnectionsAttributes);
43+
connections.record(
44+
dataSource.getPoolingCount() - dataSource.getActiveCount(),
45+
idleConnectionsAttributes);
46+
pendingRequestsForConnection.record(dataSource.getWaitThreadCount(), attributes);
47+
minIdleConnections.record(dataSource.getMinIdle(), attributes);
48+
maxIdleConnections.record(dataSource.getMaxIdle(), attributes);
49+
maxConnections.record(dataSource.getMaxActive(), attributes);
50+
},
51+
connections,
52+
pendingRequestsForConnection,
53+
minIdleConnections,
54+
maxIdleConnections,
55+
maxConnections);
56+
57+
dataSourceMetrics.put(dataSource, callback);
58+
}
59+
60+
public static void unregisterMetrics(DruidDataSourceMBean dataSource) {
61+
BatchCallback callback = dataSourceMetrics.remove(dataSource);
62+
if (callback != null) {
63+
callback.close();
64+
}
65+
}
66+
67+
private ConnectionPoolMetrics() {}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.alibabadruid.v1_0;
7+
8+
import com.alibaba.druid.pool.DruidDataSourceMBean;
9+
import io.opentelemetry.api.OpenTelemetry;
10+
11+
public final class DruidTelemetry {
12+
13+
public static DruidTelemetry create(OpenTelemetry openTelemetry) {
14+
return new DruidTelemetry(openTelemetry);
15+
}
16+
17+
private final OpenTelemetry openTelemetry;
18+
19+
private DruidTelemetry(OpenTelemetry openTelemetry) {
20+
this.openTelemetry = openTelemetry;
21+
}
22+
23+
public void registerMetrics(DruidDataSourceMBean dataSource, String dataSourceName) {
24+
ConnectionPoolMetrics.registerMetrics(openTelemetry, dataSource, dataSourceName);
25+
}
26+
27+
public void unregisterMetrics(DruidDataSourceMBean dataSource) {
28+
ConnectionPoolMetrics.unregisterMetrics(dataSource);
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.alibabadruid.v1_0;
7+
8+
import com.alibaba.druid.pool.DruidDataSource;
9+
import io.opentelemetry.instrumentation.alibabadruid.AbstractDruidInstrumentationTest;
10+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
11+
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
12+
import javax.management.ObjectName;
13+
import org.junit.jupiter.api.BeforeAll;
14+
import org.junit.jupiter.api.extension.RegisterExtension;
15+
16+
public class DruidInstrumentationTest extends AbstractDruidInstrumentationTest {
17+
18+
@RegisterExtension
19+
static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();
20+
21+
private static DruidTelemetry telemetry;
22+
23+
@Override
24+
protected InstrumentationExtension testing() {
25+
return testing;
26+
}
27+
28+
@BeforeAll
29+
static void setup() {
30+
telemetry = DruidTelemetry.create(testing.getOpenTelemetry());
31+
}
32+
33+
@Override
34+
protected void configure(DruidDataSource dataSource, String name) throws Exception {
35+
ObjectName objectName = new ObjectName("com.alibaba.druid:type=DruidDataSource,id=" + name);
36+
telemetry.registerMetrics(
37+
dataSource, objectName.getKeyProperty("type") + "-" + objectName.getKeyProperty("id"));
38+
}
39+
40+
@Override
41+
protected void shutdown(DruidDataSource dataSource) throws Exception {
42+
telemetry.unregisterMetrics(dataSource);
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
plugins {
2+
id("otel.java-conventions")
3+
}
4+
5+
dependencies {
6+
api(project(":testing-common"))
7+
api("org.mockito:mockito-core")
8+
api("org.mockito:mockito-junit-jupiter")
9+
10+
compileOnly("com.alibaba:druid:1.0.0")
11+
}

0 commit comments

Comments
 (0)