-
Notifications
You must be signed in to change notification settings - Fork 867
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add tomcat-jdbc connection pool metrics instrumentation (#6102)
* add tomcat-jdbc connection pool metrics instrumentation * use duration * code review comments * remove unnecessary awaits * udpate supported-libraries.md * add comment about weakmap * add sleeps for safety
- Loading branch information
1 parent
0274fc8
commit f1a746d
Showing
7 changed files
with
254 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
plugins { | ||
id("otel.javaagent-instrumentation") | ||
} | ||
|
||
muzzle { | ||
pass { | ||
group.set("org.apache.tomcat") | ||
module.set("tomcat-jdbc") | ||
versions.set("[8.5.0,)") | ||
// no assertInverse because tomcat-jdbc < 8.5 doesn't have methods that we hook into | ||
} | ||
} | ||
|
||
dependencies { | ||
compileOnly("org.apache.tomcat:tomcat-jdbc:8.5.0") | ||
testImplementation("org.apache.tomcat:tomcat-jdbc:8.5.0") | ||
} |
53 changes: 53 additions & 0 deletions
53
...o/opentelemetry/javaagent/instrumentation/tomcat/jdbc/DataSourceProxyInstrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.tomcat.jdbc; | ||
|
||
import static net.bytebuddy.matcher.ElementMatchers.isPublic; | ||
import static net.bytebuddy.matcher.ElementMatchers.named; | ||
import static net.bytebuddy.matcher.ElementMatchers.takesArguments; | ||
import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments; | ||
|
||
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; | ||
import org.apache.tomcat.jdbc.pool.DataSourceProxy; | ||
|
||
class DataSourceProxyInstrumentation implements TypeInstrumentation { | ||
|
||
@Override | ||
public ElementMatcher<TypeDescription> typeMatcher() { | ||
return named("org.apache.tomcat.jdbc.pool.DataSourceProxy"); | ||
} | ||
|
||
@Override | ||
public void transform(TypeTransformer transformer) { | ||
transformer.applyAdviceToMethod( | ||
isPublic().and(named("createPool")).and(takesNoArguments()), | ||
this.getClass().getName() + "$CreatePoolAdvice"); | ||
|
||
transformer.applyAdviceToMethod( | ||
isPublic().and(named("close")).and(takesArguments(1)), | ||
this.getClass().getName() + "$CloseAdvice"); | ||
} | ||
|
||
@SuppressWarnings("unused") | ||
public static class CreatePoolAdvice { | ||
@Advice.OnMethodExit(suppress = Throwable.class) | ||
public static void onExit(@Advice.This DataSourceProxy dataSource) { | ||
TomcatConnectionPoolMetrics.registerMetrics(dataSource); | ||
} | ||
} | ||
|
||
@SuppressWarnings("unused") | ||
public static class CloseAdvice { | ||
@Advice.OnMethodExit(suppress = Throwable.class) | ||
public static void onExit(@Advice.This DataSourceProxy dataSource) { | ||
TomcatConnectionPoolMetrics.unregisterMetrics(dataSource); | ||
} | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
...a/io/opentelemetry/javaagent/instrumentation/tomcat/jdbc/TomcatConnectionPoolMetrics.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.tomcat.jdbc; | ||
|
||
import io.opentelemetry.api.GlobalOpenTelemetry; | ||
import io.opentelemetry.api.OpenTelemetry; | ||
import io.opentelemetry.api.metrics.ObservableLongUpDownCounter; | ||
import io.opentelemetry.instrumentation.api.metrics.db.DbConnectionPoolMetrics; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import org.apache.tomcat.jdbc.pool.DataSourceProxy; | ||
|
||
public final class TomcatConnectionPoolMetrics { | ||
|
||
private static final OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); | ||
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.tomcat-jdbc"; | ||
|
||
// a weak map does not make sense here because each Meter holds a reference to the dataSource | ||
// DataSourceProxy does not implement equals()/hashCode(), so it's safe to keep them in a plain | ||
// ConcurrentHashMap | ||
private static final Map<DataSourceProxy, List<ObservableLongUpDownCounter>> dataSourceMetrics = | ||
new ConcurrentHashMap<>(); | ||
|
||
public static void registerMetrics(DataSourceProxy dataSource) { | ||
dataSourceMetrics.computeIfAbsent(dataSource, TomcatConnectionPoolMetrics::createCounters); | ||
} | ||
|
||
private static List<ObservableLongUpDownCounter> createCounters(DataSourceProxy dataSource) { | ||
|
||
DbConnectionPoolMetrics metrics = | ||
DbConnectionPoolMetrics.create( | ||
openTelemetry, INSTRUMENTATION_NAME, dataSource.getPoolName()); | ||
|
||
return Arrays.asList( | ||
metrics.usedConnections(dataSource::getActive), | ||
metrics.idleConnections(dataSource::getIdle), | ||
metrics.minIdleConnections(dataSource::getMinIdle), | ||
metrics.maxIdleConnections(dataSource::getMaxIdle), | ||
metrics.maxConnections(dataSource::getMaxActive), | ||
metrics.pendingRequestsForConnection(dataSource::getWaitCount)); | ||
} | ||
|
||
public static void unregisterMetrics(DataSourceProxy dataSource) { | ||
List<ObservableLongUpDownCounter> counters = dataSourceMetrics.remove(dataSource); | ||
if (counters != null) { | ||
for (ObservableLongUpDownCounter meter : counters) { | ||
meter.close(); | ||
} | ||
} | ||
} | ||
|
||
private TomcatConnectionPoolMetrics() {} | ||
} |
25 changes: 25 additions & 0 deletions
25
.../opentelemetry/javaagent/instrumentation/tomcat/jdbc/TomcatJdbcInstrumentationModule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.tomcat.jdbc; | ||
|
||
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 TomcatJdbcInstrumentationModule extends InstrumentationModule { | ||
public TomcatJdbcInstrumentationModule() { | ||
super("tomcat-jdbc"); | ||
} | ||
|
||
@Override | ||
public List<TypeInstrumentation> typeInstrumentations() { | ||
return singletonList(new DataSourceProxyInstrumentation()); | ||
} | ||
} |
99 changes: 99 additions & 0 deletions
99
...io/opentelemetry/javaagent/instrumentation/tomcat/jdbc/TomcatJdbcInstrumentationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.tomcat.jdbc; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.mockito.BDDMockito.given; | ||
|
||
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; | ||
import io.opentelemetry.instrumentation.testing.junit.db.DbConnectionPoolMetricsAssertions; | ||
import java.sql.Connection; | ||
import org.apache.tomcat.jdbc.pool.DataSource; | ||
import org.assertj.core.api.AbstractIterableAssert; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.junit.jupiter.api.extension.RegisterExtension; | ||
import org.mockito.Mock; | ||
import org.mockito.junit.jupiter.MockitoExtension; | ||
|
||
@ExtendWith(MockitoExtension.class) | ||
public class TomcatJdbcInstrumentationTest { | ||
|
||
@RegisterExtension | ||
static final AgentInstrumentationExtension testing = AgentInstrumentationExtension.create(); | ||
|
||
@Mock javax.sql.DataSource dataSourceMock; | ||
@Mock Connection connectionMock; | ||
|
||
@Test | ||
void shouldReportMetrics() throws Exception { | ||
// given | ||
given(dataSourceMock.getConnection()).willReturn(connectionMock); | ||
|
||
DataSource tomcatDataSource = new DataSource(); | ||
tomcatDataSource.setDataSource(dataSourceMock); | ||
|
||
// there shouldn't be any problems if this methods gets called more than once | ||
tomcatDataSource.createPool(); | ||
tomcatDataSource.createPool(); | ||
|
||
// when | ||
Connection connection = tomcatDataSource.getConnection(); | ||
Thread.sleep(100); | ||
connection.close(); | ||
|
||
// then | ||
assertConnectionPoolMetrics(tomcatDataSource.getPoolName()); | ||
|
||
// when | ||
// this one too shouldn't cause any problems when called more than once | ||
tomcatDataSource.close(); | ||
tomcatDataSource.close(); | ||
Thread.sleep(100); | ||
testing.clearData(); | ||
Thread.sleep(100); | ||
|
||
// then | ||
assertNoConnectionPoolMetrics(); | ||
} | ||
|
||
private static void assertConnectionPoolMetrics(String poolName) { | ||
assertThat(poolName) | ||
.as("tomcat-jdbc generates a unique pool name if it's not explicitly provided") | ||
.isNotEmpty(); | ||
|
||
DbConnectionPoolMetricsAssertions.create(testing, "io.opentelemetry.tomcat-jdbc", poolName) | ||
// no timeouts happen during this test | ||
.disableConnectionTimeouts() | ||
.disableCreateTime() | ||
.disableWaitTime() | ||
.disableUseTime() | ||
.assertConnectionPoolEmitsMetrics(); | ||
} | ||
|
||
private static void assertNoConnectionPoolMetrics() { | ||
testing.waitAndAssertMetrics( | ||
"io.opentelemetry.tomcat-jdbc", | ||
"db.client.connections.usage", | ||
AbstractIterableAssert::isEmpty); | ||
testing.waitAndAssertMetrics( | ||
"io.opentelemetry.tomcat-jdbc", | ||
"db.client.connections.idle.min", | ||
AbstractIterableAssert::isEmpty); | ||
testing.waitAndAssertMetrics( | ||
"io.opentelemetry.tomcat-jdbc", | ||
"db.client.connections.idle.max", | ||
AbstractIterableAssert::isEmpty); | ||
testing.waitAndAssertMetrics( | ||
"io.opentelemetry.tomcat-jdbc", | ||
"db.client.connections.max", | ||
AbstractIterableAssert::isEmpty); | ||
testing.waitAndAssertMetrics( | ||
"io.opentelemetry.tomcat-jdbc", | ||
"db.client.connections.pending_requests", | ||
AbstractIterableAssert::isEmpty); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters