Skip to content

Commit 6ec6716

Browse files
authored
Agent integrations checks (#11692)
feat: Expand the jar checks to stricter agent jar validation * Maintains the size check. * Verify some required entries * Ensure there's a minimum number of classes in the whole jar * Ensure products are correctly included and have at least one class * Light size check on the indexes * Fixed list of packages that should not appear in the jar * Run checks as part of the build job, to catch issues earlier feat: verify shipped agent integrations Run `--list-integrations` on the assembled jar as part of check. This exercises dd-java-agent.index, inst/instrumenter.index, and integration class loading end-to-end. The today's 208 integration names are stored in `expected-integrations.txt` and compared against the runtime output. Diff is shown on mismatch. Run `updateAgentJarIntegrationsGolden` after intentional changes and commit the result. fix: Also track the stderr when verifying integrations Multi-version integrations issues could be misreported as valid. InstrumenterIndex.buildModule() logs ERROR and returns null when a module fails to load, while the process exits 0. For integrations with multiple versioned modules sharing one name (akka-http, vertx, servlet...), if any of a versionned integration fails it's invisible. This commit changes that. In a clean run stderr is empty; any output indicates a module load failure and fails the task immediately. fix: merge master into agent integrations check chore: PR review Co-authored-by: brice.dutheil <brice.dutheil@datadoghq.com>
1 parent c6f9664 commit 6ec6716

3 files changed

Lines changed: 305 additions & 2 deletions

File tree

dd-java-agent/build.gradle

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ tasks.register('verifyAgentJarContents') {
530530

531531
// Sanity check on the minimum number of classes; update as needed. Set to about 98% of that number.
532532
def classCount = entries.keySet().count { it.endsWith('.class') || it.endsWith('.classdata') }
533-
def classFloor = 17_000 // a bit moe than 98% of 17,279 at time of writing
533+
def classFloor = 17_000 // a bit more than 98% of 17,279 at time of writing
534534
if (classCount < classFloor) {
535535
failures << "Class count ${classCount} is below floor ${classFloor}"
536536
}
@@ -577,6 +577,85 @@ tasks.register('verifyAgentJarContents') {
577577
}
578578
}
579579

580+
def integrationsGoldenFile = project.file('expected-integrations.txt')
581+
582+
tasks.register('verifyAgentJarIntegrations', JavaExec) {
583+
group = LifecycleBasePlugin.VERIFICATION_GROUP
584+
description = 'Verify the agent jar lists exactly the integrations in expected-integrations.txt'
585+
586+
def jarProvider = tasks.named('shadowJar', ShadowJar).flatMap { it.archiveFile }
587+
inputs.file(jarProvider)
588+
inputs.file(integrationsGoldenFile)
589+
outputs.file(project.layout.buildDirectory.file("tmp/${it.name}/.verified"))
590+
591+
// Run the assembled agent jar directly — this exercises dd-java-agent.index,
592+
// inst/instrumenter.index, and instrumentation class loading end-to-end.
593+
mainClass = 'datadog.trace.bootstrap.AgentBootstrap'
594+
classpath = objects.fileCollection().from(jarProvider)
595+
args = ['--list-integrations']
596+
597+
// Capture both stdout and stderr: InstrumenterIndex.buildModule() logs ERROR and returns null when a module
598+
// fails to load, while the process exits with status 0.
599+
def capturedOutput = new ByteArrayOutputStream()
600+
def capturedError = new ByteArrayOutputStream()
601+
standardOutput = capturedOutput
602+
errorOutput = capturedError
603+
604+
doLast {
605+
def stderr = capturedError.toString()
606+
if (!stderr.isBlank()) {
607+
throw new GradleException(
608+
"--list-integrations produced unexpected stderr output " +
609+
"(likely a module load failure; see InstrumenterIndex.buildModule):\n${stderr}")
610+
}
611+
612+
if (!integrationsGoldenFile.exists()) {
613+
throw new GradleException(
614+
"${integrationsGoldenFile.name} not found. " +
615+
"Run './gradlew :dd-java-agent:updateAgentJarIntegrationsGoldenFile' to create it.")
616+
}
617+
618+
def actual = capturedOutput.toString().readLines().findAll { !it.isBlank() }.toSorted()
619+
def expected = integrationsGoldenFile.readLines().findAll { !it.isBlank() }.toSorted()
620+
def added = actual - expected
621+
def removed = expected - actual
622+
623+
if (added || removed) {
624+
def msg = new StringBuilder("Integration list differs from ${integrationsGoldenFile.name}.")
625+
msg.append(" Run './gradlew :dd-java-agent:updateAgentJarIntegrationsGoldenFile' to update it.\n")
626+
added.each { msg.append(" + ${it}\n") }
627+
removed.each { msg.append(" - ${it}\n") }
628+
throw new GradleException(msg.toString())
629+
}
630+
631+
def marker = outputs.files.singleFile
632+
marker.parentFile.mkdirs()
633+
marker.text = 'verified'
634+
}
635+
}
636+
637+
// Manual run after adding/removing integrations to update expected-integrations.txt, then add with the new integration.
638+
tasks.register('updateAgentJarIntegrationsGoldenFile', JavaExec) {
639+
group = LifecycleBasePlugin.VERIFICATION_GROUP
640+
description = 'Regenerate expected-integrations.txt from the current agent jar'
641+
642+
def jarProvider = tasks.named('shadowJar', ShadowJar).flatMap { it.archiveFile }
643+
inputs.file(jarProvider)
644+
645+
mainClass = 'datadog.trace.bootstrap.AgentBootstrap'
646+
classpath = objects.fileCollection().from(jarProvider)
647+
args = ['--list-integrations']
648+
649+
def capturedOutput = new ByteArrayOutputStream()
650+
standardOutput = capturedOutput
651+
652+
doLast {
653+
def integrations = capturedOutput.toString().readLines().findAll { !it.isBlank() }.toSorted()
654+
integrationsGoldenFile.text = integrations.join('\n') + '\n'
655+
logger.lifecycle("Updated ${integrationsGoldenFile.name} with ${integrations.size()} integrations")
656+
}
657+
}
658+
580659
tasks.named('check') {
581-
dependsOn 'verifyAgentJarContents'
660+
dependsOn 'verifyAgentJarContents', 'verifyAgentJarIntegrations'
582661
}
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
IastInstrumentation
2+
aerospike
3+
akka-http
4+
akka-http2
5+
akka_actor_mailbox
6+
akka_actor_receive
7+
akka_actor_send
8+
akka_concurrent
9+
allocatedirect
10+
amqp
11+
apache-httpclient
12+
armeria-grpc-client
13+
armeria-grpc-server
14+
armeria-jetty
15+
avro
16+
aws-lambda
17+
aws-sdk
18+
axis2
19+
axway-api
20+
azure-functions
21+
caffeine
22+
cassandra
23+
ci-visibility
24+
cics
25+
classloading
26+
commons-fileupload
27+
commons-http-client
28+
confluent-schema-registry
29+
couchbase
30+
cucumber
31+
cxf
32+
datanucleus
33+
defineclass
34+
dropwizard
35+
dynamodb
36+
elasticsearch
37+
emr-aws-sdk
38+
eventbridge
39+
ffm-native-tracing
40+
finatra
41+
freemarker
42+
gax
43+
glassfish
44+
google-http-client
45+
google-pubsub
46+
gradle
47+
graphql-java
48+
grizzly
49+
grizzly-client
50+
grizzly-filterchain
51+
grpc
52+
gson
53+
guava
54+
hazelcast
55+
hazelcast_legacy
56+
hibernate
57+
httpasyncclient
58+
httpasyncclient5
59+
httpclient
60+
httpclient5
61+
httpcore
62+
httpcore-5
63+
httpurlconnection
64+
hystrix
65+
ignite
66+
inputStream
67+
jackson
68+
jackson-core
69+
jacoco
70+
jakarta-jms
71+
jakarta-mail
72+
jakarta-rs
73+
jakarta-websocket
74+
jakarta-ws
75+
java-http-client
76+
java-lang
77+
java-lang-appsec
78+
java-lang-management
79+
java-module
80+
java-net
81+
java_completablefuture
82+
java_concurrent
83+
java_timer
84+
javax-mail
85+
javax-websocket
86+
jax-rs
87+
jax-ws
88+
jboss-logmanager
89+
jdbc
90+
jdbc-datasource
91+
jedis
92+
jersey
93+
jetty
94+
jetty-client
95+
jetty-concurrent
96+
jms
97+
jni
98+
jsp
99+
jwt
100+
kafka
101+
kotlin_coroutine
102+
lettuce
103+
liberty
104+
log4j
105+
logback
106+
maven
107+
micronaut
108+
mmap
109+
mongo
110+
mule
111+
native-image
112+
netty
113+
netty-concurrent
114+
netty-promise
115+
not-not-trace
116+
ognl
117+
okhttp
118+
openai-java
119+
opensearch
120+
opentelemetry-annotations
121+
opentelemetry-beta
122+
opentelemetry-logs
123+
opentelemetry-metrics
124+
opentelemetry.experimental
125+
opentracing
126+
org-json
127+
pekko-http
128+
pekko-http2
129+
pekko_actor_mailbox
130+
pekko_actor_receive
131+
pekko_actor_send
132+
play
133+
play-ws
134+
protobuf
135+
quartz
136+
ratpack
137+
ratpack-request-body
138+
reactive-streams
139+
reactor-core
140+
reactor-netty
141+
rediscala
142+
redisson
143+
renaissance
144+
resilience4j
145+
resilience4j-reactor
146+
resteasy
147+
restlet-http
148+
rmi
149+
rxjava
150+
s3
151+
scala_concurrent
152+
servicetalk
153+
servlet
154+
servlet-filter
155+
servlet-request-body
156+
servlet-service
157+
sfn
158+
shutdown
159+
slick
160+
snakeyaml
161+
sns
162+
socket
163+
sofarpc
164+
spark
165+
spark-executor
166+
spark-exit
167+
spark-launcher
168+
spark-openlineage
169+
sparkjava
170+
spray-http
171+
spring-async
172+
spring-beans
173+
spring-boot
174+
spring-boot-span-origin
175+
spring-cloud-zuul
176+
spring-core
177+
spring-data
178+
spring-jms
179+
spring-messaging
180+
spring-rabbit
181+
spring-scheduling
182+
spring-security
183+
spring-web
184+
spring-web-code-origin
185+
spring-webflux
186+
spring-ws
187+
spymemcached
188+
sqs
189+
sslsocket
190+
synapse3-client
191+
synapse3-server
192+
testng
193+
throwables
194+
thymeleaf
195+
tibco
196+
tinylog
197+
tomcat
198+
trace
199+
twilio-sdk
200+
undertow
201+
urlconnection
202+
valkey
203+
velocity
204+
vertx
205+
wallclock
206+
websphere-jmx
207+
wildfly
208+
zio.experimental

docs/add_new_instrumentation.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,22 @@ There are four verification strategies, three of which are mandatory.
449449
- [Latest Dependency Tests](./how_instrumentations_work.md#latest-dependency-tests) (Required)
450450
- [Smoke tests](./how_instrumentations_work.md#smoke-tests) (Not required)
451451

452+
### Agent jar integrations golden file
453+
454+
The agent jar check task `checkAgentJarIntegrations` verifies that the set of integrations
455+
listed in `dd-java-agent/expected-integrations.txt` exactly matches the integrations
456+
shipped in the built agent jar. This catches accidental additions or removals.
457+
458+
When you add or remove an integration, update the golden file:
459+
460+
```shell
461+
./gradlew :dd-java-agent:updateAgentJarIntegrationsGoldenFile
462+
```
463+
464+
Then commit `dd-java-agent/expected-integrations.txt` alongside your instrumentation changes.
465+
The `check` task runs `checkAgentJarIntegrations` automatically, so CI will fail if the file
466+
is out of date.
467+
452468
All integrations must include sufficient test coverage. This HTTP client integration will include
453469
a [standard HTTP test class](../dd-java-agent/instrumentation/google-http-client/src/test/groovy/GoogleHttpClientTest.groovy)
454470
and

0 commit comments

Comments
 (0)