From 3fd27aa4d134d0f39bba5166eb94dc1b64bd9a1d Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 2 Dec 2024 18:26:41 +0100 Subject: [PATCH] add db client metrics for JDBC, vertx, r2dbc, cassandra, add tests (#12818) --- .../cassandra/v3_0/CassandraSingletons.java | 2 + .../cassandra/v4_0/CassandraSingletons.java | 2 + .../v4_4/CassandraTelemetryBuilder.java | 2 + .../instrumentation/jdbc/JdbcSingletons.java | 2 + .../jdbc/test/JdbcInstrumentationTest.java | 19 ++++++++- .../internal/JdbcInstrumenterFactory.java | 2 + .../jdbc/datasource/JdbcTelemetryTest.java | 15 +++++++ .../jedis/v3_0/Jedis30ClientTest.java | 12 ++++++ .../internal/R2dbcInstrumenterBuilder.java | 2 + .../v4_0/sql/VertxSqlClientSingletons.java | 4 +- .../junit/db/DbClientMetricsTestUtil.java | 41 +++++++++++++++++++ 11 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/db/DbClientMetricsTestUtil.java diff --git a/instrumentation/cassandra/cassandra-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v3_0/CassandraSingletons.java b/instrumentation/cassandra/cassandra-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v3_0/CassandraSingletons.java index 5598d2fcb994..7a8f81823692 100644 --- a/instrumentation/cassandra/cassandra-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v3_0/CassandraSingletons.java +++ b/instrumentation/cassandra/cassandra-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v3_0/CassandraSingletons.java @@ -7,6 +7,7 @@ import com.datastax.driver.core.ExecutionInfo; import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientSpanNameExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -40,6 +41,7 @@ public final class CassandraSingletons { .addAttributesExtractor( NetworkAttributesExtractor.create(new CassandraNetworkAttributesGetter())) .addAttributesExtractor(new CassandraAttributesExtractor()) + .addOperationMetrics(DbClientMetrics.get()) .buildInstrumenter(SpanKindExtractor.alwaysClient()); } diff --git a/instrumentation/cassandra/cassandra-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v4_0/CassandraSingletons.java b/instrumentation/cassandra/cassandra-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v4_0/CassandraSingletons.java index 668cf2f50974..624994166aa0 100644 --- a/instrumentation/cassandra/cassandra-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v4_0/CassandraSingletons.java +++ b/instrumentation/cassandra/cassandra-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v4_0/CassandraSingletons.java @@ -7,6 +7,7 @@ import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientSpanNameExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -39,6 +40,7 @@ public final class CassandraSingletons { .addAttributesExtractor( NetworkAttributesExtractor.create(new CassandraNetworkAttributesGetter())) .addAttributesExtractor(new CassandraAttributesExtractor()) + .addOperationMetrics(DbClientMetrics.get()) .buildInstrumenter(SpanKindExtractor.alwaysClient()); } diff --git a/instrumentation/cassandra/cassandra-4.4/library/src/main/java/io/opentelemetry/instrumentation/cassandra/v4_4/CassandraTelemetryBuilder.java b/instrumentation/cassandra/cassandra-4.4/library/src/main/java/io/opentelemetry/instrumentation/cassandra/v4_4/CassandraTelemetryBuilder.java index d5a92eb7d936..40e8cccae1d8 100644 --- a/instrumentation/cassandra/cassandra-4.4/library/src/main/java/io/opentelemetry/instrumentation/cassandra/v4_4/CassandraTelemetryBuilder.java +++ b/instrumentation/cassandra/cassandra-4.4/library/src/main/java/io/opentelemetry/instrumentation/cassandra/v4_4/CassandraTelemetryBuilder.java @@ -9,6 +9,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientSpanNameExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -65,6 +66,7 @@ protected Instrumenter createInstrumenter( .addAttributesExtractor( NetworkAttributesExtractor.create(new CassandraNetworkAttributesGetter())) .addAttributesExtractor(new CassandraAttributesExtractor()) + .addOperationMetrics(DbClientMetrics.get()) .buildInstrumenter(SpanKindExtractor.alwaysClient()); } } diff --git a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/JdbcSingletons.java b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/JdbcSingletons.java index afc92f2fcad9..e9c5290aa217 100644 --- a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/JdbcSingletons.java +++ b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/JdbcSingletons.java @@ -8,6 +8,7 @@ import static io.opentelemetry.instrumentation.jdbc.internal.JdbcInstrumenterFactory.createDataSourceInstrumenter; import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientSpanNameExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlClientAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.net.PeerServiceAttributesExtractor; @@ -50,6 +51,7 @@ public final class JdbcSingletons { .addAttributesExtractor( PeerServiceAttributesExtractor.create( netAttributesGetter, AgentCommonConfig.get().getPeerServiceResolver())) + .addOperationMetrics(DbClientMetrics.get()) .buildInstrumenter(SpanKindExtractor.alwaysClient()); } diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java index 649b5c480e45..92684a9026bf 100644 --- a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java +++ b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java @@ -6,17 +6,21 @@ package io.opentelemetry.javaagent.instrumentation.jdbc.test; import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; +import static io.opentelemetry.instrumentation.testing.junit.db.DbClientMetricsTestUtil.assertDurationMetric; import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_COLLECTION_NAME; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAME; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAMESPACE; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION_NAME; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SQL_TABLE; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_USER; import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; @@ -379,6 +383,19 @@ public void testBasicStatement( equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), equalTo(maybeStable(DB_OPERATION), "SELECT"), equalTo(maybeStable(DB_SQL_TABLE), table)))); + + if (table != null) { + assertDurationMetric( + testing, + "io.opentelemetry.jdbc", + DB_SYSTEM, + DB_COLLECTION_NAME, + DB_NAMESPACE, + DB_OPERATION_NAME); + } else { + assertDurationMetric( + testing, "io.opentelemetry.jdbc", DB_SYSTEM, DB_OPERATION_NAME, DB_NAMESPACE); + } } static Stream preparedStatementStream() throws SQLException { diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcInstrumenterFactory.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcInstrumenterFactory.java index 2e0b1e583bb7..861e1dd11759 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcInstrumenterFactory.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcInstrumenterFactory.java @@ -9,6 +9,7 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeSpanNameExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientSpanNameExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -52,6 +53,7 @@ public static Instrumenter createStatementInstrumenter( .setStatementSanitizationEnabled(statementSanitizationEnabled) .build()) .addAttributesExtractor(ServerAttributesExtractor.create(netAttributesGetter)) + .addOperationMetrics(DbClientMetrics.get()) .setEnabled(enabled) .buildInstrumenter(SpanKindExtractor.alwaysClient()); } diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/JdbcTelemetryTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/JdbcTelemetryTest.java index 1f3db9d51bfa..dacd4b177934 100644 --- a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/JdbcTelemetryTest.java +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/JdbcTelemetryTest.java @@ -5,9 +5,15 @@ package io.opentelemetry.instrumentation.jdbc.datasource; +import static io.opentelemetry.instrumentation.testing.junit.db.DbClientMetricsTestUtil.assertDurationMetric; import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; +import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAMESPACE; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION_NAME; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.instrumentation.jdbc.internal.OpenTelemetryConnection; @@ -44,6 +50,15 @@ void buildWithDefaults() throws SQLException { span -> span.hasName("SELECT dbname") .hasAttribute(equalTo(maybeStable(DB_STATEMENT), "SELECT ?;")))); + + assertDurationMetric( + testing, + "io.opentelemetry.jdbc", + DB_NAMESPACE, + DB_OPERATION_NAME, + DB_SYSTEM, + SERVER_ADDRESS, + SERVER_PORT); } @Test diff --git a/instrumentation/jedis/jedis-3.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jedis/v3_0/Jedis30ClientTest.java b/instrumentation/jedis/jedis-3.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jedis/v3_0/Jedis30ClientTest.java index 921610937bc1..65578e6141c9 100644 --- a/instrumentation/jedis/jedis-3.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jedis/v3_0/Jedis30ClientTest.java +++ b/instrumentation/jedis/jedis-3.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jedis/v3_0/Jedis30ClientTest.java @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.jedis.v3_0; +import static io.opentelemetry.instrumentation.testing.junit.db.DbClientMetricsTestUtil.assertDurationMetric; import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; @@ -14,6 +15,7 @@ import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION_NAME; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; import static org.assertj.core.api.Assertions.assertThat; @@ -88,6 +90,16 @@ void setCommand() { equalTo(NETWORK_TYPE, "ipv4"), equalTo(NETWORK_PEER_ADDRESS, ip), satisfies(NETWORK_PEER_PORT, AbstractLongAssert::isNotNegative)))); + + assertDurationMetric( + testing, + "io.opentelemetry.jedis-3.0", + DB_OPERATION_NAME, + DB_SYSTEM, + SERVER_ADDRESS, + SERVER_PORT, + NETWORK_PEER_ADDRESS, + NETWORK_PEER_PORT); } @Test diff --git a/instrumentation/r2dbc-1.0/library/src/main/java/io/opentelemetry/instrumentation/r2dbc/v1_0/internal/R2dbcInstrumenterBuilder.java b/instrumentation/r2dbc-1.0/library/src/main/java/io/opentelemetry/instrumentation/r2dbc/v1_0/internal/R2dbcInstrumenterBuilder.java index 0b15861ff915..f094125633bb 100644 --- a/instrumentation/r2dbc-1.0/library/src/main/java/io/opentelemetry/instrumentation/r2dbc/v1_0/internal/R2dbcInstrumenterBuilder.java +++ b/instrumentation/r2dbc-1.0/library/src/main/java/io/opentelemetry/instrumentation/r2dbc/v1_0/internal/R2dbcInstrumenterBuilder.java @@ -7,6 +7,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientSpanNameExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; @@ -58,6 +59,7 @@ public Instrumenter build( .build()) .addAttributesExtractor(ServerAttributesExtractor.create(R2dbcNetAttributesGetter.INSTANCE)) .addAttributesExtractors(additionalExtractors) + .addOperationMetrics(DbClientMetrics.get()) .buildInstrumenter(SpanKindExtractor.alwaysClient()); } } diff --git a/instrumentation/vertx/vertx-sql-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/sql/VertxSqlClientSingletons.java b/instrumentation/vertx/vertx-sql-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/sql/VertxSqlClientSingletons.java index a77338e5c811..071a27b768c3 100644 --- a/instrumentation/vertx/vertx-sql-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/sql/VertxSqlClientSingletons.java +++ b/instrumentation/vertx/vertx-sql-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/sql/VertxSqlClientSingletons.java @@ -8,6 +8,7 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientSpanNameExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlClientAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.net.PeerServiceAttributesExtractor; @@ -47,7 +48,8 @@ public final class VertxSqlClientSingletons { .addAttributesExtractor( PeerServiceAttributesExtractor.create( VertxSqlClientNetAttributesGetter.INSTANCE, - AgentCommonConfig.get().getPeerServiceResolver())); + AgentCommonConfig.get().getPeerServiceResolver())) + .addOperationMetrics(DbClientMetrics.get()); INSTRUMENTER = builder.buildInstrumenter(SpanKindExtractor.alwaysClient()); } diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/db/DbClientMetricsTestUtil.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/db/DbClientMetricsTestUtil.java new file mode 100644 index 000000000000..40f2832790c7 --- /dev/null +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/db/DbClientMetricsTestUtil.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.testing.junit.db; + +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; + +public class DbClientMetricsTestUtil { + + private DbClientMetricsTestUtil() {} + + public static void assertDurationMetric( + InstrumentationExtension testing, + String instrumentationName, + AttributeKey... expectedKeys) { + if (!emitStableDatabaseSemconv()) { + return; + } + testing.waitAndAssertMetrics( + instrumentationName, + metrics -> + metrics + .hasName("db.client.operation.duration") + .hasUnit("s") + .hasDescription("Duration of database client operations.") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point.hasAttributesSatisfying( + attributes -> + assertThat(attributes.asMap()) + .containsOnlyKeys(expectedKeys))))); + } +}