Skip to content

Commit 89d4cec

Browse files
authored
Merge branch 'master' into zgu/rm-experiment-wall-jvmti
2 parents 01c3287 + 3ff081c commit 89d4cec

6 files changed

Lines changed: 393 additions & 0 deletions

File tree

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
---
2+
name: add-apm-integrations
3+
description: Write a new library instrumentation end-to-end. Use when the user ask to add a new APM integration or a library instrumentation.
4+
context: fork
5+
allowed-tools:
6+
- Bash
7+
- Read
8+
- Write
9+
- Edit
10+
- Glob
11+
- Grep
12+
---
13+
14+
Write a new APM end-to-end integration for dd-trace-java, based on library instrumentations, following all project conventions.
15+
16+
## Step 1 – Read the authoritative docs and sync this skill (mandatory, always first)
17+
18+
Before writing any code, read all three files in full:
19+
20+
1. [`docs/how_instrumentations_work.md`](docs/how_instrumentations_work.md) — full reference (types, methods, advice, helpers, context stores, decorators)
21+
2. [`docs/add_new_instrumentation.md`](docs/add_new_instrumentation.md) — step-by-step walkthrough
22+
3. [`docs/how_to_test.md`](docs/how_to_test.md) — test types and how to run them
23+
24+
These files are the single source of truth. Reference them while implementing.
25+
26+
**After reading the docs, sync this skill with them:**
27+
28+
Compare the content of the three docs against the rules encoded in Steps 2–11 of this skill file. Look for:
29+
- Patterns, APIs, or conventions described in the docs but absent or incorrect here
30+
- Steps that are out of date relative to the current docs (e.g. renamed methods, new base classes)
31+
- Advice constraints or test requirements that have changed
32+
33+
For every discrepancy found, edit this file (`.claude/skills/apm-integrations/SKILL.md`) to correct it using the
34+
`Edit` tool before continuing. Keep changes targeted: fix what diverged, add what is missing, remove what is wrong.
35+
Do not touch content that already matches the docs.
36+
37+
## Step 2 – Clarify the task
38+
39+
If the user has not already provided all of the following, ask before proceeding:
40+
41+
- **Framework name** and **minimum supported version** (e.g. `okhttp-3.0`)
42+
- **Target class(es) and method(s)** to instrument (fully qualified class names preferred)
43+
- **Target system**: one of `Tracing`, `Profiling`, `AppSec`, `Iast`, `CiVisibility`, `Usm`, `ContextTracking`
44+
- **Whether this is a bootstrap instrumentation** (affects allowed imports)
45+
46+
## Step 3 – Find a reference instrumentation
47+
48+
Search `dd-java-agent/instrumentation/` for a structurally similar integration:
49+
- Same target system
50+
- Comparable type-matching strategy (single type, hierarchy, known types)
51+
52+
Read the reference integration's `InstrumenterModule`, Advice, Decorator, and test files to understand the established
53+
pattern before writing new code. Use it as a template.
54+
55+
## Step 4 – Set up the module
56+
57+
1. Create directory: `dd-java-agent/instrumentation/$framework/$framework-$minVersion/`
58+
2. Under it, create the standard Maven source layout:
59+
- `src/main/java/` — instrumentation code
60+
- `src/test/groovy/` — Spock tests
61+
3. Create `build.gradle` with:
62+
- `compileOnly` dependencies for the target framework
63+
- `testImplementation` dependencies for tests
64+
- `muzzle { pass { } }` directives (see Step 9)
65+
4. Register the new module in `settings.gradle.kts` in **alphabetical order**
66+
67+
## Step 5 – Write the InstrumenterModule
68+
69+
Conventions to enforce:
70+
71+
- Add `@AutoService(InstrumenterModule.class)` annotation — required for auto-discovery
72+
- Extend the correct `InstrumenterModule.*` subclass (never the bare abstract class)
73+
- Implement the **narrowest** `Instrumenter` interface possible:
74+
- Prefer `ForSingleType` > `ForKnownTypes` > `ForTypeHierarchy`
75+
- Add `classLoaderMatcher()` if a sentinel class identifies the framework on the classpath
76+
- Declare **all** helper class names in `helperClassNames()`:
77+
- Include inner classes (`Foo$Bar`), anonymous classes (`Foo$1`), and enum synthetic classes
78+
- Declare `contextStore()` entries if context stores are needed (key class → value class)
79+
- Keep method matchers as narrow as possible (name, parameter types, visibility)
80+
81+
## Step 6 – Write the Decorator
82+
83+
- Extend the most specific available base decorator:
84+
- `HttpClientDecorator`, `DatabaseClientDecorator`, `ServerDecorator`, `MessagingClientDecorator`, etc.
85+
- One `public static final DECORATE` instance
86+
- Define `UTF8BytesString` constants for the component name and operation name
87+
- Keep all tag/naming/error logic here — not in the Advice class
88+
- Override `spanType()`, `component()`, `spanKind()` as appropriate
89+
90+
## Step 7 – Write the Advice class (highest-risk step)
91+
92+
### Must do
93+
94+
- Advice methods **must** be `static`
95+
- Annotate enter: `@Advice.OnMethodEnter(suppress = Throwable.class)`
96+
- Annotate exit: `@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)`
97+
- **Exception**: do NOT use `suppress` when hooking a constructor
98+
- Use `@Advice.Local("...")` for values shared between enter and exit (span, scope)
99+
- Use the correct parameter annotations:
100+
- `@Advice.This` — the receiver object
101+
- `@Advice.Argument(N)` — a method argument by index
102+
- `@Advice.Return` — the return value (exit only)
103+
- `@Advice.Thrown` — the thrown exception (exit only)
104+
- `@Advice.Enter` — the return value of the enter method (exit only)
105+
- Use `CallDepthThreadLocalMap` to guard against recursive instrumentation of the same method
106+
107+
### Span lifecycle (in order)
108+
109+
Enter method:
110+
1. `AgentSpan span = startSpan(DECORATE.operationName(), ...)`
111+
2. `DECORATE.afterStart(span)` + set domain-specific tags
112+
3. `AgentScope scope = activateSpan(span)` — return or store via `@Advice.Local`
113+
114+
Exit method:
115+
4. `DECORATE.onError(span, throwable)` — only if throwable is non-null
116+
5. `DECORATE.beforeFinish(span)`
117+
6. `span.finish()`
118+
7. `scope.close()`
119+
120+
### Must NOT do
121+
122+
- **No logger fields** in the Advice class or the Instrumentation class (loggers only in helpers/decorators)
123+
- **No code in the Advice constructor** — it is never called
124+
- **Do not use lambdas in advice methods** — they create synthetic classes that will be missing from helper declarations
125+
- **No references** to other methods in the same Advice class or in the InstrumenterModule class
126+
- **No `InstrumentationContext.get()`** outside of Advice code
127+
- **No `inline=false`** in production code (only for debugging; must be removed before committing)
128+
- **No `java.util.logging.*`, `java.nio.*`, or `javax.management.*`** in bootstrap instrumentations
129+
130+
## Step 8 – Add SETTER/GETTER adapters (if applicable)
131+
132+
For context propagation to and from upstream services, like HTTP headers,
133+
implement `AgentPropagation.Setter` / `AgentPropagation.Getter` adapters that wrap the framework's specific header API.
134+
Place them in the helpers package, declare them in `helperClassNames()`.
135+
136+
## Step 9 – Write tests
137+
138+
Cover all mandatory test types:
139+
140+
### 1. Instrumentation test (mandatory)
141+
142+
- Spock spec extending `InstrumentationSpecification`
143+
- Place in `src/test/groovy/`
144+
- Verify: spans created, tags set, errors propagated, resource names correct
145+
- Use `TEST_WRITER.waitForTraces(N)` for assertions
146+
- Use `runUnderTrace("root") { ... }` for synchronous code
147+
148+
For tests that need a separate JVM, suffix the test class with `ForkedTest` and run via the `forkedTest` task.
149+
150+
### 2. Muzzle directives (mandatory)
151+
152+
In `build.gradle`, add `muzzle` blocks:
153+
```groovy
154+
muzzle {
155+
pass {
156+
group = "com.example"
157+
module = "framework"
158+
versions = "[$minVersion,)"
159+
assertInverse = true // ensures versions below $minVersion fail muzzle
160+
}
161+
}
162+
```
163+
164+
### 3. Latest dependency test (mandatory)
165+
166+
Use the `latestDepTestLibrary` helper in `build.gradle` to pin the latest available version. Run with:
167+
```bash
168+
./gradlew :dd-java-agent:instrumentation:$framework-$version:latestDepTest
169+
```
170+
171+
### 4. Smoke test (optional)
172+
173+
Add a smoke test in `dd-smoke-tests/` only if the framework warrants a full end-to-end demo-app test.
174+
175+
## Step 10 – Build and verify
176+
177+
Run these commands in order and fix any failures before proceeding:
178+
179+
```bash
180+
./gradlew :dd-java-agent:instrumentation:$framework-$version:muzzle
181+
./gradlew :dd-java-agent:instrumentation:$framework-$version:test
182+
./gradlew :dd-java-agent:instrumentation:$framework-$version:latestDepTest
183+
./gradlew spotlessCheck
184+
```
185+
186+
**If muzzle fails:** check for missing helper class names in `helperClassNames()`.
187+
188+
**If tests fail:** verify span lifecycle order (start → activate → error → finish → close), helper registration,
189+
and `contextStore()` map entries match actual usage.
190+
191+
**If spotlessCheck fails:** run `./gradlew spotlessApply` to auto-format, then re-check.
192+
193+
## Step 11 – Checklist before finishing
194+
195+
Output this checklist and confirm each item is satisfied:
196+
197+
- [ ] `settings.gradle.kts` entry added in alphabetical order
198+
- [ ] `build.gradle` has `compileOnly` deps and `muzzle` directives with `assertInverse = true`
199+
- [ ] `@AutoService(InstrumenterModule.class)` annotation present on the module class
200+
- [ ] `helperClassNames()` lists ALL referenced helpers (including inner, anonymous, and enum synthetic classes)
201+
- [ ] Advice methods are `static` with `@Advice.OnMethodEnter` / `@Advice.OnMethodExit` annotations
202+
- [ ] `suppress = Throwable.class` on enter/exit (unless the hooked method is a constructor)
203+
- [ ] No logger field in the Advice class or InstrumenterModule class
204+
- [ ] No `inline=false` left in production code
205+
- [ ] No `java.util.logging.*` / `java.nio.*` / `javax.management.*` in bootstrap path
206+
- [ ] Span lifecycle order is correct: startSpan → afterStart → activateSpan (enter); onError → beforeFinish → finish → close (exit)
207+
- [ ] Muzzle passes
208+
- [ ] Instrumentation tests pass
209+
- [ ] `latestDepTest` passes
210+
- [ ] `spotlessCheck` passes
211+
212+
## Step 12 – Retrospective: update this skill with what was learned
213+
214+
After the instrumentation is complete (or abandoned), review the full session and improve this skill for future use.
215+
216+
**Collect lessons from four sources:**
217+
218+
1. **Build/test failures** — did any Gradle task fail with an error that this skill did not anticipate or gave wrong
219+
guidance for? (e.g. a muzzle failure that wasn't caused by missing helpers, a test pattern that didn't work)
220+
2. **Docs vs. skill gaps** — did Step 1's sync miss anything? Did you consult the docs for something not captured here?
221+
3. **Reference instrumentation insights** — did the reference integration use a pattern, API, or convention not
222+
reflected in any step of this skill?
223+
4. **User corrections** — did the user correct an output, override a decision, or point out a mistake?
224+
225+
**For each lesson identified**, edit this file (`.claude/skills/apm-integrations/SKILL.md`) using the `Edit` tool:
226+
- Wrong rule → fix it in place
227+
- Missing rule → add it to the most relevant step
228+
- Wrong failure guidance → update the relevant "If X fails" section in Step 10
229+
- Misleading or obsolete content → remove it
230+
231+
Keep each change minimal and targeted. Do not rewrite sections that worked correctly.
232+
After editing, confirm to the user which improvements were made to the skill.

dd-java-agent/agent-crashtracking/src/main/resources/datadog/crashtracking/notify_oome.bat

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ echo Tags: %tags%
3434
echo JAVA_HOME: %java_home%
3535
echo PID: %PID%
3636

37+
:: Clear environment variables that the parent JVM may have set so the child JVM
38+
:: starts with a minimal configuration (avoids port conflicts, memory contention, etc.)
39+
set JDK_JAVA_OPTIONS=
40+
set JAVA_TOOL_OPTIONS=
41+
set _JAVA_OPTIONS=
42+
3743
:: Execute the Java command with the loaded values
3844
"%java_home%\bin\java" -Ddd.dogstatsd.start-delay=0 -jar "%agent%" sendOomeEvent "%tags%"
3945
set RC=%ERRORLEVEL%

dd-java-agent/agent-crashtracking/src/main/resources/datadog/crashtracking/notify_oome.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ echo "Tags: $config_tags"
4949
echo "JAVA_HOME: $config_java_home"
5050
echo "PID: $PID"
5151

52+
# Clear environment variables that the parent JVM may have set so the child JVM
53+
# starts with a minimal configuration (avoids port conflicts, memory contention, etc.)
54+
unset JDK_JAVA_OPTIONS
55+
unset JAVA_TOOL_OPTIONS
56+
unset _JAVA_OPTIONS
57+
5258
# Execute the Java command with the loaded values
5359
"$config_java_home/bin/java" -Ddd.dogstatsd.start-delay=0 -jar "$config_agent" sendOomeEvent "$config_tags"
5460
RC=$?

dd-java-agent/agent-crashtracking/src/main/resources/datadog/crashtracking/upload_crash.bat

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ setlocal enabledelayedexpansion
44
:: Check if PID is provided
55
if "%1"=="" (
66
echo "Error: No PID provided. Running in legacy mode."
7+
:: Clear environment variables that the parent JVM may have set
8+
set JDK_JAVA_OPTIONS=
9+
set JAVA_TOOL_OPTIONS=
10+
set _JAVA_OPTIONS=
11+
712
"!JAVA_HOME!\bin\java" -jar "!AGENT_JAR!" uploadCrash "!JAVA_ERROR_FILE!"
813
if %ERRORLEVEL% EQU 0 (
914
echo "Uploaded error file \"!JAVA_ERROR_FILE!\""
@@ -128,6 +133,12 @@ echo Error Log: %config_hs_err%
128133
echo JAVA_HOME: %config_java_home%
129134
echo PID: %PID%
130135

136+
:: Clear environment variables that the parent JVM may have set so the child JVM
137+
:: starts with a minimal configuration (avoids port conflicts, memory contention, etc.)
138+
set JDK_JAVA_OPTIONS=
139+
set JAVA_TOOL_OPTIONS=
140+
set _JAVA_OPTIONS=
141+
131142
:: Execute the Java command with the loaded values
132143
"%config_java_home%\bin\java" -jar "%config_agent%" uploadCrash -c "%configFile%" "%config_hs_err%"
133144
set RC=%ERRORLEVEL%

dd-java-agent/agent-crashtracking/src/main/resources/datadog/crashtracking/upload_crash.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ set +e
77
if [ -z "$1" ]; then
88
echo "Warn: No PID provided. Running in legacy mode."
99

10+
# Clear environment variables that the parent JVM may have set
11+
unset JDK_JAVA_OPTIONS
12+
unset JAVA_TOOL_OPTIONS
13+
unset _JAVA_OPTIONS
14+
1015
"!JAVA_HOME!/bin/java" -jar "!AGENT_JAR!" uploadCrash "!JAVA_ERROR_FILE!"
1116
if [ $? -eq 0 ]; then
1217
echo "Error file !JAVA_ERROR_FILE! was uploaded successfully"
@@ -119,6 +124,12 @@ echo "Error Log: $config_hs_err"
119124
echo "JAVA_HOME: $config_java_home"
120125
echo "PID: $PID"
121126

127+
# Clear environment variables that the parent JVM may have set so the child JVM
128+
# starts with a minimal configuration (avoids port conflicts, memory contention, etc.)
129+
unset JDK_JAVA_OPTIONS
130+
unset JAVA_TOOL_OPTIONS
131+
unset _JAVA_OPTIONS
132+
122133
# Execute the Java command with the loaded values
123134
"$config_java_home/bin/java" -jar "$config_agent" uploadCrash -c "$configFile" "$config_hs_err"
124135
RC=$?

0 commit comments

Comments
 (0)