Skip to content

Commit 82f4303

Browse files
authored
Merge branch 'master' into ygree/llmobs-systest-fixes
2 parents 028d64f + 5580c61 commit 82f4303

File tree

596 files changed

+13376
-4410
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

596 files changed

+13376
-4410
lines changed
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.

.claude/skills/migrate-groovy-to-java/SKILL.md

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
---
22
name: migrate-groovy-to-java
3-
description: migrate test groovy files to java
3+
description: >
4+
Converts Spock/Groovy test files in a Gradle module to equivalent JUnit 5 Java tests.
5+
Use when asked to "migrate groovy", "convert groovy to java", "g2j", or when a module
6+
has .groovy test files that need to be replaced with .java equivalents.
47
---
58

69
Migrate test Groovy files to Java using JUnit 5
@@ -13,11 +16,45 @@ Migrate test Groovy files to Java using JUnit 5
1316

1417
When converting Groovy code to Java code, make sure that:
1518
- The Java code generated is compatible with JDK 8
16-
- When translating Spock tests, favor using `@CsvSource` with `|` delimiters
17-
- When using `@MethodSource`, name the arguments method by appending `Arguments` using camelCase to the test method name (e.g. `testMethodArguments`)
18-
- Ensure parameterized test names are human-readable (i.e. no hashcodes); instead add a description string as the first `Arguments.of(...)` value or index the test case
19+
- When translating Spock tests, prefer `@TableTest` for data rows that are naturally tabular. See detailed guidance in the "TableTest usage" section.
20+
- `@TableTest` and `@MethodSource` may be combined on the same `@ParameterizedTest` when most cases are tabular but a few cases require programmatic setup.
21+
- In combined mode, keep table-friendly cases in `@TableTest`, and put only non-tabular/complex cases in `@MethodSource`.
22+
- If `@TableTest` is not viable for the test at all, use `@MethodSource` only.
23+
- For `@MethodSource`, name the arguments method `<testMethodName>Arguments` (camelCase, e.g. `testMethodArguments`) and return `Stream<Arguments>` using `Stream.of(...)` and `arguments(...)` with static import.
24+
- Ensure parameterized test names are human-readable (i.e. no hashcodes); instead add a description string as the first `Arguments.arguments(...)` value or index the test case
1925
- When converting tuples, create a light dedicated structure instead to keep the typing system
2026
- Instead of checking a state and throwing an exception, use JUnit asserts
27+
- Instead of using `assertTrue(a.equals(b))` or `assertFalse(a.equals(b))`, use `assertEquals(expected, actual)` and `assertNotEquals(unexpected, actual)`
28+
- Import frequently used types rather than using fully-qualified names inline, to improve readability
2129
- Do not wrap checked exceptions and throw a Runtime exception; prefer adding a throws clause at method declaration
2230
- Do not mark local variables `final`
2331
- Ensure variables are human-readable; avoid single-letter names and pre-define variables that are referenced multiple times
32+
- When translating Spock `Mock(...)` usage, use `libs.bundles.mockito` instead of writing manual recording/stub implementations
33+
34+
TableTest usage
35+
Import: `import org.tabletest.junit.TableTest;`
36+
37+
JDK 8 rules:
38+
- No text blocks.
39+
- @TableTest must use String[] annotation array syntax:
40+
```
41+
@TableTest({
42+
"a | b",
43+
"1 | 2"
44+
})
45+
```
46+
47+
Spock `where:`@TableTest:
48+
- First row = header (column names = method parameters).
49+
- Add `scenario` column as first column (display name, not a method parameter).
50+
- Use `|` delimiter; align columns so pipes line up vertically.
51+
- Prefer single quotes for strings with special chars (e.g., `'a|b'`, `'[]'`).
52+
- Blank cell = null (object types); `''` = empty string.
53+
- Collections: `[a, b]` = List/array, `{a, b}` = Set, `[k: v]` = Map.
54+
55+
Mixed eligibility:
56+
- Prefer combining `@TableTest` + `@MethodSource` on one `@ParameterizedTest` when only some cases are complex.
57+
- Use `@MethodSource` only when tabular representation is not practical for the test.
58+
59+
Do NOT use @TableTest when:
60+
- Majority of rows require complex objects or custom converters.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
---
2+
name: migrate-junit-source-to-tabletest
3+
description: Convert JUnit 5 @MethodSource/@CsvSource/@ValueSource parameterized tests to @TableTest (JDK8)
4+
---
5+
Goal: Migrate JUnit 5 parameterized tests using @MethodSource/@CsvSource/@ValueSource to @TableTest with minimal churn and passing tests.
6+
7+
Process (do in this order):
8+
1) Locate targets via Grep (no agent subprocess). Search for: "@ParameterizedTest", "@MethodSource", "@CsvSource", "@ValueSource".
9+
2) Read all matching files up front (parallel is OK).
10+
3) Convert eligible tests to @TableTest.
11+
4) Write each modified file once in full using Write (no incremental per-test edits).
12+
5) Run module tests once and verify "BUILD SUCCESSFUL". If failed, inspect JUnit XML report.
13+
14+
Import: `import org.tabletest.junit.TableTest;`
15+
16+
JDK 8 rules:
17+
- No text blocks.
18+
- @TableTest must use String[] annotation array syntax:
19+
```
20+
@TableTest({
21+
"a | b",
22+
"1 | 2"
23+
})
24+
```
25+
26+
Table formatting rules (mandatory):
27+
- Always include a header row (parameter names).
28+
- Always add a "scenario" column; using common sense for naming; scenario is NOT a method parameter.
29+
- Use '|' as delimiter.
30+
- Align columns with spaces so pipes line up vertically.
31+
- Prefer single quotes for strings requiring quotes (e.g., 'a|b', '[]', '{}', ' ').
32+
- Use value sets (`{a, b, c}`) instead of matrix-style repetition when only one dimension varies across otherwise-identical rows.
33+
34+
Conversions:
35+
A) @CsvSource
36+
- Remove @ParameterizedTest and @CsvSource.
37+
- If delimiter is '|': rows map directly to @TableTest.
38+
- If delimiter is ',' (default): replace ',' with '|' in rows.
39+
40+
B) @ValueSource
41+
- Keep single-parameter tests on `@ValueSource` (and `@NullSource` when null cases are needed).
42+
- Otherwise convert to @TableTest with header from parameter name.
43+
- Each value becomes one row.
44+
- Add "scenario" column using common sense for name.
45+
46+
C) @MethodSource (convert only if values are representable as strings)
47+
- Convert when argument values are primitives, strings, enums, booleans, nulls, and simple collection literals supported by TableTest:
48+
- Array: [a, b, ...]
49+
- List: [a, b, ...]
50+
- Set: {a, b, ...}
51+
- Map: [k: v, ...]
52+
- `@TableTest` and `@MethodSource` may be combined on the same `@ParameterizedTest` when most cases are tabular but a few cases require programmatic setup.
53+
- In combined mode, keep table-friendly cases in `@TableTest`, and put only non-tabular/complex cases in `@MethodSource`.
54+
- If `@TableTest` is not viable for the test at all, use `@MethodSource` only.
55+
- For `@MethodSource`, name the arguments method `<testMethodName>Arguments` (camelCase, e.g. `testMethodArguments`) and return `Stream<Arguments>` using `Stream.of(...)` and `arguments(...)` with static import.
56+
- Blank cell = null (non-primitive).
57+
- '' = empty string.
58+
- For String params that start with '[' or '{', quote to avoid collection parsing (prefer '[]'/'{}').
59+
60+
D) @TypeConverter
61+
- Use `@TypeConverter` for symbolic constants used by migrated table rows (e.g. `Long.MAX_VALUE`, `DDSpanId.MAX`).
62+
- Prefer explicit one-case-one-return mappings.
63+
- Prefer shared converter utilities (e.g. in `utils/test-utils`) when reuse across modules is likely.
64+
65+
Scenario handling:
66+
- If MethodSource includes a leading description string OR @ParameterizedTest(name=...) uses {0}, convert that to a scenario column and remove that parameter from method signature.
67+
68+
Cleanup:
69+
- Delete now-unused @MethodSource provider methods and unused imports.
70+
71+
Mixed eligibility:
72+
- Prefer combining `@TableTest` + `@MethodSource` on one `@ParameterizedTest` when only some cases are complex.
73+
74+
Do NOT convert when:
75+
- Most rows require complex builders/mocks.
76+
77+
Test command (exact):
78+
./gradlew :path:to:module:test --rerun-tasks 2>&1 | tail -20
79+
- If BUILD FAILED: cat path/to/module/build/test-results/test/TEST-*.xml

0 commit comments

Comments
 (0)