Skip to content

Commit 1d03c76

Browse files
Instrument jdbc datasources with OpenTelemetry (#259)
Co-authored-by: Tim Jacomb <[email protected]>
1 parent d9f219f commit 1d03c76

File tree

6 files changed

+89
-22
lines changed

6 files changed

+89
-22
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,22 @@ public class TestRow {
113113
}
114114
```
115115

116+
## Troubleshooting performance problems with OpenTelemetry and tracing
117+
118+
The Jenkins Database plugin supports OpenTelemetry and tracing to help troubleshoot performance issues.
119+
120+
To enable tracing, you need to:
121+
* Install the [OpenTelemetry plugin](https://plugins.jenkins.io/opentelemetry/) and configure it
122+
* Navigate to the "advanced" section of the Jenkins OpenTelemetry Plugin and set in the "Configuration properties" :
123+
* `otel.instrumentation.jdbc.enabled=true` to enable tracing in the Datasource provided by the Jenkins Database Plugin
124+
* `otel.instrumentation.jenkins.agent.enabled=true` to activate tracing in the Jenkins build agents if database calls are executed from the build agents
125+
126+
Note that changes to the `otel.instrumentation.jdbc.enabled` require to restart the Jenkins Controller and build agents.
127+
128+
Example database call span produced by the Jenkins JUnit SQL Storage plugin:
129+
130+
<img alt="Jenkins pipeline trace with a JDBC span in Grafana" src="resources/images/screenshot-jenkins-pipeline-trace-with-jdbc-span.png" width="250px">
131+
116132
## Developing driver plugin
117133

118134
[MySQL Database

pom.xml

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@
1616
<properties>
1717
<changelist>999999-SNAPSHOT</changelist>
1818
<gitHubRepo>jenkinsci/database-plugin</gitHubRepo>
19-
<jenkins.version>2.426.3</jenkins.version>
19+
<jenkins.version>2.440.3</jenkins.version>
20+
21+
<jenkins.opentelemetry-api.version>1.40.0-32.v65c59076e638</jenkins.opentelemetry-api.version>
22+
<opentelemetry-instrumentation.version>2.6.0</opentelemetry-instrumentation.version>
2023
</properties>
2124

2225
<dependencyManagement>
2326
<dependencies>
2427
<dependency>
2528
<groupId>io.jenkins.tools.bom</groupId>
26-
<artifactId>bom-2.426.x</artifactId>
27-
<version>3208.vb_21177d4b_cd9</version>
29+
<artifactId>bom-2.440.x</artifactId>
30+
<version>3120.v4d898e1e9fc4</version>
2831
<scope>import</scope>
2932
<type>pom</type>
3033
</dependency>
@@ -35,7 +38,7 @@
3538
<dependency>
3639
<groupId>org.apache.commons</groupId>
3740
<artifactId>commons-dbcp2</artifactId>
38-
<version>2.11.0</version>
41+
<version>2.12.0</version>
3942
</dependency>
4043

4144
<dependency>
@@ -102,6 +105,17 @@
102105
<artifactId>workflow-step-api</artifactId>
103106
<optional>true</optional>
104107
</dependency>
108+
109+
<dependency>
110+
<groupId>io.jenkins.plugins</groupId>
111+
<artifactId>opentelemetry-api</artifactId>
112+
<version>${jenkins.opentelemetry-api.version}</version>
113+
</dependency>
114+
<dependency>
115+
<groupId>io.opentelemetry.instrumentation</groupId>
116+
<artifactId>opentelemetry-jdbc</artifactId>
117+
<version>${opentelemetry-instrumentation.version}-alpha</version>
118+
</dependency>
105119
</dependencies>
106120

107121
<repositories>
Loading

src/main/java/org/jenkinsci/plugins/database/AbstractRemoteDatabase.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@
33
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
44
import hudson.Util;
55
import hudson.util.Secret;
6-
import java.io.Serializable;
7-
import org.kohsuke.stapler.DataBoundConstructor;
6+
import io.jenkins.plugins.opentelemetry.api.ReconfigurableOpenTelemetry;
7+
import io.opentelemetry.api.GlobalOpenTelemetry;
8+
import io.opentelemetry.instrumentation.jdbc.datasource.JdbcTelemetry;
9+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
10+
import org.kohsuke.stapler.DataBoundSetter;
11+
import org.kohsuke.stapler.QueryParameter;
812

913
import javax.sql.DataSource;
1014
import java.io.IOException;
15+
import java.io.Serializable;
1116
import java.sql.Driver;
1217
import java.sql.SQLException;
1318
import java.util.Map;
14-
import org.kohsuke.stapler.DataBoundSetter;
15-
import org.kohsuke.stapler.QueryParameter;
1619

1720
/**
1821
* Partial default implementation for typical JDBC connector that talks to a remote server
@@ -36,7 +39,7 @@ public abstract class AbstractRemoteDatabase extends Database implements Seriali
3639

3740
public final String properties;
3841

39-
private transient DataSource source;
42+
private transient DataSource dataSource;
4043

4144
public AbstractRemoteDatabase(String hostname, String database, String username, Secret password, String properties) {
4245
this.hostname = hostname;
@@ -61,7 +64,7 @@ public String getValidationQuery() {
6164

6265
@Override
6366
public synchronized DataSource getDataSource() throws SQLException {
64-
if (source==null) {
67+
if (dataSource ==null) {
6568
BasicDataSource2 fac = new BasicDataSource2();
6669
fac.setDriverClass(getDriverClass());
6770
fac.setUrl(getJdbcUrl());
@@ -77,8 +80,12 @@ public synchronized DataSource getDataSource() throws SQLException {
7780
throw new SQLException("Invalid properties",e);
7881
}
7982

80-
source = fac.createDataSource();
83+
if (isOTelJdbcInstrumentationEnabled()) {
84+
dataSource = JdbcTelemetry.create(GlobalOpenTelemetry.get()).wrap(fac.createDataSource());
85+
} else {
86+
dataSource = fac.createDataSource();
87+
}
8188
}
82-
return source;
89+
return dataSource;
8390
}
8491
}

src/main/java/org/jenkinsci/plugins/database/Database.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import hudson.ExtensionPoint;
44
import hudson.model.AbstractDescribableImpl;
5+
import io.jenkins.plugins.opentelemetry.api.ReconfigurableOpenTelemetry;
6+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
57

68
import javax.sql.DataSource;
79
import java.sql.Connection;
@@ -17,10 +19,30 @@
1719
* @author Kohsuke Kawaguchi
1820
*/
1921
public abstract class Database extends AbstractDescribableImpl<Database> implements ExtensionPoint {
22+
2023
public abstract DataSource getDataSource() throws SQLException;
2124

25+
/**
26+
* <p>
27+
* Returns true if OpenTelemetry JDBC instrumentation is enabled.
28+
* </p>
29+
* <p>
30+
* Implementations couldn't use {@link io.jenkins.plugins.opentelemetry.api.OpenTelemetryLifecycleListener} to
31+
* retrieve the configuration and get configuration changes because it's the {@link DatabaseDescriptor} that should
32+
* have implemented the {@link io.jenkins.plugins.opentelemetry.api.OpenTelemetryLifecycleListener} interface and
33+
* {@link hudson.model.Descriptor} instances are not available on Jenkins build agent JVMs, only on the Jenkins
34+
* controller.
35+
* </p>
36+
*/
37+
protected boolean isOTelJdbcInstrumentationEnabled() {
38+
ReconfigurableOpenTelemetry reconfigurableOpenTelemetry = ReconfigurableOpenTelemetry.get();
39+
ConfigProperties config = reconfigurableOpenTelemetry.getConfig();
40+
return config.getBoolean("otel.instrumentation.jdbc.enabled", false);
41+
}
42+
2243
@Override
2344
public DatabaseDescriptor getDescriptor() {
24-
return (DatabaseDescriptor)super.getDescriptor();
45+
return (DatabaseDescriptor) super.getDescriptor();
2546
}
26-
}
47+
48+
}

src/main/java/org/jenkinsci/plugins/database/GenericDatabase.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,32 @@
11
package org.jenkinsci.plugins.database;
22

3+
import edu.umd.cs.findbugs.annotations.NonNull;
34
import hudson.Extension;
45
import hudson.util.FormValidation;
56
import hudson.util.Secret;
7+
import io.jenkins.plugins.opentelemetry.api.ReconfigurableOpenTelemetry;
8+
import io.opentelemetry.api.GlobalOpenTelemetry;
9+
import io.opentelemetry.instrumentation.jdbc.datasource.JdbcTelemetry;
10+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
611
import jenkins.model.Jenkins;
712
import org.apache.tools.ant.AntClassLoader;
813
import org.kohsuke.stapler.DataBoundConstructor;
914
import org.kohsuke.stapler.DataBoundSetter;
1015
import org.kohsuke.stapler.QueryParameter;
16+
import org.kohsuke.stapler.verb.POST;
1117

12-
import edu.umd.cs.findbugs.annotations.NonNull;
13-
import javax.servlet.ServletException;
1418
import javax.servlet.http.HttpServletRequest;
1519
import javax.sql.DataSource;
1620
import java.io.File;
17-
import java.io.IOException;
1821
import java.sql.SQLException;
19-
import org.kohsuke.stapler.verb.POST;
2022

2123
/**
2224
* {@link Database} implementation that allows the user to specify arbitrary JDBC connection string.
2325
*
2426
* @author Kohsuke Kawaguchi
2527
*/
2628
public class GenericDatabase extends Database {
29+
2730
public final String driver;
2831
public final String username;
2932
public final Secret password;
@@ -34,7 +37,7 @@ public class GenericDatabase extends Database {
3437
private Integer maxIdle = DescriptorImpl.defaultMaxIdle;
3538
private Integer minIdle = DescriptorImpl.defaultMinIdle;
3639

37-
private transient DataSource source;
40+
private transient DataSource dataSource;
3841

3942
@DataBoundConstructor
4043
public GenericDatabase(String url, String driver, String username, Secret password) {
@@ -86,7 +89,7 @@ public void setMinIdle(final Integer minIdle) {
8689

8790
@Override
8891
public synchronized DataSource getDataSource() throws SQLException {
89-
if (source==null) {
92+
if (dataSource ==null) {
9093
BasicDataSource2 source = new BasicDataSource2();
9194
source.setDriverClassLoader(getDescriptor().getClassLoader());
9295
source.setDriverClassName(driver);
@@ -97,9 +100,14 @@ public synchronized DataSource getDataSource() throws SQLException {
97100
source.setMaxTotal(maxTotal);
98101
source.setMaxIdle(maxIdle);
99102
source.setMinIdle(minIdle);
100-
this.source = source.createDataSource();
103+
104+
if (isOTelJdbcInstrumentationEnabled()) {
105+
dataSource = JdbcTelemetry.create(GlobalOpenTelemetry.get()).wrap(source.createDataSource());
106+
} else {
107+
dataSource = source.createDataSource();
108+
}
101109
}
102-
return source;
110+
return dataSource;
103111
}
104112

105113
@Override

0 commit comments

Comments
 (0)