فهرست منبع

Add instrumentation for druid connection pool (#9935)

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
dingjiefei 1 سال پیش
والد
کامیت
20ab0121fa
13فایلهای تغییر یافته به همراه416 افزوده شده و 0 حذف شده
  1. 1 0
      docs/supported-libraries.md
  2. 20 0
      instrumentation/alibaba-druid-1.0/javaagent/build.gradle.kts
  3. 60 0
      instrumentation/alibaba-druid-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/alibabadruid/v1_0/DruidDataSourceInstrumentation.java
  4. 26 0
      instrumentation/alibaba-druid-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/alibabadruid/v1_0/DruidInstrumentationModule.java
  5. 21 0
      instrumentation/alibaba-druid-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/alibabadruid/v1_0/DruidSingletons.java
  6. 34 0
      instrumentation/alibaba-druid-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/alibabadruid/v1_0/DruidInstrumentationTest.java
  7. 10 0
      instrumentation/alibaba-druid-1.0/library/build.gradle.kts
  8. 68 0
      instrumentation/alibaba-druid-1.0/library/src/main/java/io/opentelemetry/instrumentation/alibabadruid/v1_0/ConnectionPoolMetrics.java
  9. 30 0
      instrumentation/alibaba-druid-1.0/library/src/main/java/io/opentelemetry/instrumentation/alibabadruid/v1_0/DruidTelemetry.java
  10. 44 0
      instrumentation/alibaba-druid-1.0/library/src/test/java/io/opentelemetry/instrumentation/alibabadruid/v1_0/DruidInstrumentationTest.java
  11. 11 0
      instrumentation/alibaba-druid-1.0/testing/build.gradle.kts
  12. 88 0
      instrumentation/alibaba-druid-1.0/testing/src/main/java/io/opentelemetry/instrumentation/alibabadruid/AbstractDruidInstrumentationTest.java
  13. 3 0
      settings.gradle.kts

+ 1 - 0
docs/supported-libraries.md

@@ -21,6 +21,7 @@ These are the supported libraries and frameworks:
 |---------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |----------------------------------------------------------------------------------------|
 | [Akka Actors](https://doc.akka.io/docs/akka/current/typed/index.html)                                                                       | 2.3+                          | N/A                                                                                                                                                                                                                                                                                                                                                                                     | Context propagation                                                                    |
 | [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] |
+| [Alibaba Druid](https://github.com/alibaba/druid)                                                                                           | 1.0+                          | [opentelemetry-alibaba-druid-1.0](../instrumentation/alibaba-druid-1.0/library)                                                                                                                                                                                                                                                                                                         | [Database Pool Metrics]                                                                |
 | [Apache Axis2](https://axis.apache.org/axis2/java/core/)                                                                                    | 1.6+                          | N/A                                                                                                                                                                                                                                                                                                                                                                                     | Provides `http.route` [2], Controller Spans [3]                                        |
 | [Apache Camel](https://camel.apache.org/)                                                                                                   | 2.20+ (not including 3.x yet) | N/A                                                                                                                                                                                                                                                                                                                                                                                     | Dependent on components in use                                                         |
 | [Apache CXF JAX-RS](https://cxf.apache.org/)                                                                                                | 3.2+                          | N/A                                                                                                                                                                                                                                                                                                                                                                                     | Provides `http.route` [2], Controller Spans [3]                                        |

+ 20 - 0
instrumentation/alibaba-druid-1.0/javaagent/build.gradle.kts

@@ -0,0 +1,20 @@
+plugins {
+  id("otel.javaagent-instrumentation")
+}
+
+muzzle {
+  pass {
+    group.set("com.alibaba")
+    module.set("druid")
+    versions.set("(,)")
+    skip("1.0.30")
+  }
+}
+
+dependencies {
+  library("com.alibaba:druid:1.0.0")
+
+  implementation(project(":instrumentation:alibaba-druid-1.0:library"))
+
+  testImplementation(project(":instrumentation:alibaba-druid-1.0:testing"))
+}

+ 60 - 0
instrumentation/alibaba-druid-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/alibabadruid/v1_0/DruidDataSourceInstrumentation.java

@@ -0,0 +1,60 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.alibabadruid.v1_0;
+
+import static io.opentelemetry.javaagent.instrumentation.alibabadruid.v1_0.DruidSingletons.telemetry;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.isPublic;
+import static net.bytebuddy.matcher.ElementMatchers.isStatic;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+import com.alibaba.druid.pool.DruidDataSourceMBean;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import javax.management.ObjectName;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class DruidDataSourceInstrumentation implements TypeInstrumentation {
+  @Override
+  public ElementMatcher<TypeDescription> typeMatcher() {
+    return named("com.alibaba.druid.stat.DruidDataSourceStatManager");
+  }
+
+  @Override
+  public void transform(TypeTransformer typeTransformer) {
+    typeTransformer.applyAdviceToMethod(
+        isMethod().and(isPublic()).and(isStatic()).and(named("addDataSource")),
+        this.getClass().getName() + "$AddDataSourceAdvice");
+
+    typeTransformer.applyAdviceToMethod(
+        isMethod().and(isPublic()).and(isStatic()).and(named("removeDataSource")),
+        this.getClass().getName() + "$RemoveDataSourceAdvice");
+  }
+
+  @SuppressWarnings("unused")
+  public static class AddDataSourceAdvice {
+
+    @Advice.OnMethodExit(suppress = Throwable.class)
+    public static void onExit(
+        @Advice.Argument(0) Object dataSource, @Advice.Return ObjectName objectName) {
+      DruidDataSourceMBean druidDataSource = (DruidDataSourceMBean) dataSource;
+      String dataSourceName =
+          objectName.getKeyProperty("type") + "-" + objectName.getKeyProperty("id");
+      telemetry().registerMetrics(druidDataSource, dataSourceName);
+    }
+  }
+
+  @SuppressWarnings("unused")
+  public static class RemoveDataSourceAdvice {
+    @Advice.OnMethodExit(suppress = Throwable.class)
+    public static void onExit(@Advice.Argument(0) Object dataSource) {
+      DruidDataSourceMBean druidDataSource = (DruidDataSourceMBean) dataSource;
+      telemetry().unregisterMetrics(druidDataSource);
+    }
+  }
+}

+ 26 - 0
instrumentation/alibaba-druid-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/alibabadruid/v1_0/DruidInstrumentationModule.java

@@ -0,0 +1,26 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.alibabadruid.v1_0;
+
+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 DruidInstrumentationModule extends InstrumentationModule {
+
+  public DruidInstrumentationModule() {
+    super("alibaba-druid", "alibaba-druid-1.0");
+  }
+
+  @Override
+  public List<TypeInstrumentation> typeInstrumentations() {
+    return singletonList(new DruidDataSourceInstrumentation());
+  }
+}

+ 21 - 0
instrumentation/alibaba-druid-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/alibabadruid/v1_0/DruidSingletons.java

@@ -0,0 +1,21 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.alibabadruid.v1_0;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.instrumentation.alibabadruid.v1_0.DruidTelemetry;
+
+public final class DruidSingletons {
+
+  private static final DruidTelemetry druidTelemetry =
+      DruidTelemetry.create(GlobalOpenTelemetry.get());
+
+  public static DruidTelemetry telemetry() {
+    return druidTelemetry;
+  }
+
+  private DruidSingletons() {}
+}

+ 34 - 0
instrumentation/alibaba-druid-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/alibabadruid/v1_0/DruidInstrumentationTest.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.alibabadruid.v1_0;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import com.alibaba.druid.stat.DruidDataSourceStatManager;
+import io.opentelemetry.instrumentation.alibabadruid.AbstractDruidInstrumentationTest;
+import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class DruidInstrumentationTest extends AbstractDruidInstrumentationTest {
+
+  @RegisterExtension
+  static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
+
+  @Override
+  protected InstrumentationExtension testing() {
+    return testing;
+  }
+
+  @Override
+  protected void configure(DruidDataSource dataSource, String name) throws Exception {
+    DruidDataSourceStatManager.addDataSource(dataSource, name);
+  }
+
+  @Override
+  protected void shutdown(DruidDataSource dataSource) throws Exception {
+    DruidDataSourceStatManager.removeDataSource(dataSource);
+  }
+}

+ 10 - 0
instrumentation/alibaba-druid-1.0/library/build.gradle.kts

@@ -0,0 +1,10 @@
+plugins {
+  id("otel.library-instrumentation")
+  id("otel.nullaway-conventions")
+}
+
+dependencies {
+  library("com.alibaba:druid:1.0.0")
+
+  testImplementation(project(":instrumentation:alibaba-druid-1.0:testing"))
+}

+ 68 - 0
instrumentation/alibaba-druid-1.0/library/src/main/java/io/opentelemetry/instrumentation/alibabadruid/v1_0/ConnectionPoolMetrics.java

@@ -0,0 +1,68 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.alibabadruid.v1_0;
+
+import com.alibaba.druid.pool.DruidDataSourceMBean;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.BatchCallback;
+import io.opentelemetry.api.metrics.ObservableLongMeasurement;
+import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbConnectionPoolMetrics;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+final class ConnectionPoolMetrics {
+
+  private static final String INSTRUMENTATION_NAME = "io.opentelemetry.alibaba-druid-1.0";
+
+  private static final Map<DruidDataSourceMBean, BatchCallback> dataSourceMetrics =
+      new ConcurrentHashMap<>();
+
+  public static void registerMetrics(
+      OpenTelemetry openTelemetry, DruidDataSourceMBean dataSource, String dataSourceName) {
+    DbConnectionPoolMetrics metrics =
+        DbConnectionPoolMetrics.create(openTelemetry, INSTRUMENTATION_NAME, dataSourceName);
+
+    ObservableLongMeasurement connections = metrics.connections();
+    ObservableLongMeasurement minIdleConnections = metrics.minIdleConnections();
+    ObservableLongMeasurement maxIdleConnections = metrics.maxIdleConnections();
+    ObservableLongMeasurement maxConnections = metrics.maxConnections();
+    ObservableLongMeasurement pendingRequestsForConnection = metrics.pendingRequestsForConnection();
+
+    Attributes attributes = metrics.getAttributes();
+    Attributes usedConnectionsAttributes = metrics.getUsedConnectionsAttributes();
+    Attributes idleConnectionsAttributes = metrics.getIdleConnectionsAttributes();
+
+    BatchCallback callback =
+        metrics.batchCallback(
+            () -> {
+              connections.record(dataSource.getActiveCount(), usedConnectionsAttributes);
+              connections.record(
+                  dataSource.getPoolingCount() - dataSource.getActiveCount(),
+                  idleConnectionsAttributes);
+              pendingRequestsForConnection.record(dataSource.getWaitThreadCount(), attributes);
+              minIdleConnections.record(dataSource.getMinIdle(), attributes);
+              maxIdleConnections.record(dataSource.getMaxIdle(), attributes);
+              maxConnections.record(dataSource.getMaxActive(), attributes);
+            },
+            connections,
+            pendingRequestsForConnection,
+            minIdleConnections,
+            maxIdleConnections,
+            maxConnections);
+
+    dataSourceMetrics.put(dataSource, callback);
+  }
+
+  public static void unregisterMetrics(DruidDataSourceMBean dataSource) {
+    BatchCallback callback = dataSourceMetrics.remove(dataSource);
+    if (callback != null) {
+      callback.close();
+    }
+  }
+
+  private ConnectionPoolMetrics() {}
+}

+ 30 - 0
instrumentation/alibaba-druid-1.0/library/src/main/java/io/opentelemetry/instrumentation/alibabadruid/v1_0/DruidTelemetry.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.alibabadruid.v1_0;
+
+import com.alibaba.druid.pool.DruidDataSourceMBean;
+import io.opentelemetry.api.OpenTelemetry;
+
+public final class DruidTelemetry {
+
+  public static DruidTelemetry create(OpenTelemetry openTelemetry) {
+    return new DruidTelemetry(openTelemetry);
+  }
+
+  private final OpenTelemetry openTelemetry;
+
+  private DruidTelemetry(OpenTelemetry openTelemetry) {
+    this.openTelemetry = openTelemetry;
+  }
+
+  public void registerMetrics(DruidDataSourceMBean dataSource, String dataSourceName) {
+    ConnectionPoolMetrics.registerMetrics(openTelemetry, dataSource, dataSourceName);
+  }
+
+  public void unregisterMetrics(DruidDataSourceMBean dataSource) {
+    ConnectionPoolMetrics.unregisterMetrics(dataSource);
+  }
+}

+ 44 - 0
instrumentation/alibaba-druid-1.0/library/src/test/java/io/opentelemetry/instrumentation/alibabadruid/v1_0/DruidInstrumentationTest.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.alibabadruid.v1_0;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import io.opentelemetry.instrumentation.alibabadruid.AbstractDruidInstrumentationTest;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
+import javax.management.ObjectName;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class DruidInstrumentationTest extends AbstractDruidInstrumentationTest {
+
+  @RegisterExtension
+  static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();
+
+  private static DruidTelemetry telemetry;
+
+  @Override
+  protected InstrumentationExtension testing() {
+    return testing;
+  }
+
+  @BeforeAll
+  static void setup() {
+    telemetry = DruidTelemetry.create(testing.getOpenTelemetry());
+  }
+
+  @Override
+  protected void configure(DruidDataSource dataSource, String name) throws Exception {
+    ObjectName objectName = new ObjectName("com.alibaba.druid:type=DruidDataSource,id=" + name);
+    telemetry.registerMetrics(
+        dataSource, objectName.getKeyProperty("type") + "-" + objectName.getKeyProperty("id"));
+  }
+
+  @Override
+  protected void shutdown(DruidDataSource dataSource) throws Exception {
+    telemetry.unregisterMetrics(dataSource);
+  }
+}

+ 11 - 0
instrumentation/alibaba-druid-1.0/testing/build.gradle.kts

@@ -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.alibaba:druid:1.0.0")
+}

+ 88 - 0
instrumentation/alibaba-druid-1.0/testing/src/main/java/io/opentelemetry/instrumentation/alibabadruid/AbstractDruidInstrumentationTest.java

@@ -0,0 +1,88 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.alibabadruid;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.db.DbConnectionPoolMetricsAssertions;
+import io.opentelemetry.instrumentation.testing.junit.db.MockDriver;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import javax.management.ObjectName;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+public abstract class AbstractDruidInstrumentationTest {
+
+  private static final String INSTRUMENTATION_NAME = "io.opentelemetry.alibaba-druid-1.0";
+
+  protected abstract InstrumentationExtension testing();
+
+  protected abstract void configure(DruidDataSource dataSource, String dataSourceName)
+      throws Exception;
+
+  protected abstract void shutdown(DruidDataSource dataSource) throws Exception;
+
+  @BeforeAll
+  static void setUpMocks() throws SQLException {
+    MockDriver.register();
+  }
+
+  @Test
+  void shouldReportMetrics() throws Exception {
+    String name = "dataSourceName";
+    DruidDataSource dataSource = new DruidDataSource();
+    dataSource.setDriverClassName(MockDriver.class.getName());
+    dataSource.setUrl("db:///url");
+    dataSource.setTestWhileIdle(false);
+    configure(dataSource, name);
+
+    // then
+    ObjectName objectName = new ObjectName("com.alibaba.druid:type=DruidDataSource,id=" + name);
+
+    DbConnectionPoolMetricsAssertions.create(
+            testing(),
+            INSTRUMENTATION_NAME,
+            objectName.getKeyProperty("type") + "-" + objectName.getKeyProperty("id"))
+        .disableConnectionTimeouts()
+        .disableCreateTime()
+        .disableWaitTime()
+        .disableUseTime()
+        .assertConnectionPoolEmitsMetrics();
+
+    // when
+    dataSource.close();
+    shutdown(dataSource);
+
+    // sleep exporter interval
+    Thread.sleep(100);
+    testing().clearData();
+    Thread.sleep(100);
+
+    // then
+    Set<String> metricNames =
+        new HashSet<>(
+            Arrays.asList(
+                "db.client.connections.usage",
+                "db.client.connections.idle.min",
+                "db.client.connections.idle.max",
+                "db.client.connections.max",
+                "db.client.connections.pending_requests"));
+    assertThat(testing().metrics())
+        .filteredOn(
+            metricData ->
+                metricData.getInstrumentationScopeInfo().getName().equals(INSTRUMENTATION_NAME)
+                    && metricNames.contains(metricData.getName()))
+        .isEmpty();
+  }
+}

+ 3 - 0
settings.gradle.kts

@@ -141,6 +141,9 @@ include(":smoke-tests-otel-starter")
 include(":instrumentation:akka:akka-actor-2.3:javaagent")
 include(":instrumentation:akka:akka-actor-fork-join-2.5:javaagent")
 include(":instrumentation:akka:akka-http-10.0:javaagent")
+include(":instrumentation:alibaba-druid-1.0:javaagent")
+include(":instrumentation:alibaba-druid-1.0:library")
+include(":instrumentation:alibaba-druid-1.0:testing")
 include(":instrumentation:apache-dbcp-2.0:javaagent")
 include(":instrumentation:apache-dbcp-2.0:library")
 include(":instrumentation:apache-dbcp-2.0:testing")