-
Notifications
You must be signed in to change notification settings - Fork 347
Trim AGENTS.md of duplicate content #12003
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,7 @@ docs/ Developer documentation (see below) | |
| | Testing guide (6 test types) | [docs/how_to_test.md](docs/how_to_test.md) | | ||
| | Working with Gradle | [docs/how_to_work_with_gradle.md](docs/how_to_work_with_gradle.md) | | ||
| | Bootstrap/premain constraints | [docs/bootstrap_design_guidelines.md](docs/bootstrap_design_guidelines.md) | | ||
| | Instrumentation/advice constraints | [docs/instrumentation_design_guidelines.md](docs/instrumentation_design_guidelines.md) | | ||
| | CI/CD workflows | [.github/workflows/README.md](.github/workflows/README.md) | | ||
|
|
||
| **When working on a topic above, read the linked file first** — they are the source of truth maintained by humans. | ||
|
|
@@ -56,14 +57,12 @@ docs/ Developer documentation (see below) | |
| ## Code conventions | ||
|
|
||
| - **Formatting**: google-java-format enforced via Spotless. Run `./gradlew spotlessApply` before committing. | ||
| - **Static imports**: Prefer static imports over class-qualified calls for call-style helpers — JUnit `Assertions`, Mockito (`mock`, `when`, `verify`, `anyString`, `RETURNS_DEFAULTS`, ...), Hamcrest/AssertJ matchers, internal test DSLs, and similar. Same goes for production code: `Collections.emptyList()` is fine, but if you find yourself repeatedly writing `Foo.bar(...)` where `Foo` adds no information at the call site, static-import `bar`. Wildcard `import static x.*` is disallowed (enforced by IDE config in CONTRIBUTING.md). | ||
| - **Static imports**: Prefer static imports over class-qualified calls for call-style helpers, in both test (Assertions.assertEquals, Mockito.mock) and production code (Collections.emptyList). Wildcard imports disallowed — see CONTRIBUTING.md. | ||
| - **Instrumentation layout**: `dd-java-agent/instrumentation/{framework}/{framework}-{minVersion}/` | ||
| - **Instrumentation pattern**: Type matching → Method matching → Advice class (bytecode advice, not AOP) | ||
| - **Test frameworks**: Always use JUnit 5 for unit tests. Only use Groovy / Spock tests for instrumentation and smoke tests. | ||
| - **Forked tests**: Use `ForkedTest` suffix when tests need a separate JVM | ||
| - **Flaky tests**: Annotate with `@Flaky` — they are skipped in CI by default | ||
| - **Instrumentation one-shot methods**: Never extract the return values of `triggerClasses()`, `contextStore()`, `classLoaderMatcher()`, or `methodAdvice()` into static constants. These are called once by the framework — extracting to a constant adds constant-pool bloat with no benefit. | ||
| - **Scope lifecycle order**: Keep the scope open until all work that needs the current active span is done — decorator calls, async callback registration, etc. Required order: decorator calls and callback registration, then `scope.close()`, then `span.finish()`. | ||
|
|
||
| ## PR conventions | ||
|
|
||
|
|
@@ -74,23 +73,10 @@ docs/ Developer documentation (see below) | |
|
|
||
| ## Review Guidelines | ||
|
|
||
| Before marking a PR ready, run the applicable reviews below over the branch changes (the diff since the merge-base with `master`). These are **advisory, precision-first** checks — they surface high-severity issues early and are not merge gates. The linked guidelines are the tool-agnostic source of truth. | ||
| - **Technical debt**: run `/techdebt` over branch changes before marking a PR ready to catch code duplication, unnecessary complexity, and dead code (refactor-only, never changes behavior) — see [.agents/skills/techdebt/SKILL.md](.agents/skills/techdebt/SKILL.md). | ||
| - **Performance**: run `/perf-review` over branch changes before marking a PR ready (advisory, not a merge gate) — see [.agents/skills/perf-review/SKILL.md](.agents/skills/perf-review/SKILL.md). | ||
|
mcculls marked this conversation as resolved.
|
||
|
|
||
| ### Performance Review | ||
| ## Critical constraints | ||
|
|
||
| Follow the performance-review guidelines in [.agents/skills/perf-review/SKILL.md](.agents/skills/perf-review/SKILL.md) — the do-no-harm / assume-hot posture, the hot-path checks (universal + Java J1–J11 + ByteBuddy-Advice idioms), and the confidence/severity model, with the detailed rubric in its `references/`. Scope: hot-path allocation, unbounded memory, repeated work, escaping objects, native-boundary crossings, and JVM-specific pitfalls. | ||
|
|
||
| ## Bootstrap constraints (critical) | ||
|
|
||
| Code running in the agent's `premain` phase must **not** use: | ||
| - `java.util.logging.*` — locks in log manager before app configures it | ||
| - `java.nio.file.*` — triggers premature provider initialization | ||
| - `javax.management.*` — causes class loading issues | ||
|
|
||
| See [docs/bootstrap_design_guidelines.md](docs/bootstrap_design_guidelines.md) for details and alternatives. | ||
|
|
||
| ## Advice constraints (critical) | ||
|
|
||
| Advice methods (OnMethodEnter/OnMethodExit) are inlined into the instrumented class's bytecode. | ||
| Calling a static interface method there, such as Context.root() or Context.current(), can cause | ||
| a VerifyError at runtime — use Java8BytecodeBridge (rootContext, currentContext, etc.) instead. | ||
| - **Bootstrap**: never use `java.util.logging.*`, `java.nio.file.*`, or `javax.management.*` in `premain` code — see [docs/bootstrap_design_guidelines.md](docs/bootstrap_design_guidelines.md). | ||
| - **Advice**: never call a static interface method from advice — can cause a `VerifyError` — see [docs/instrumentation_design_guidelines.md](docs/instrumentation_design_guidelines.md). | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [optional suggestion] Is it possible to have this as static analysis, rather than prose? If so, we can spend the tokens on this up-front rather than having every agent need to read this and enforce it every time
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The tricky part is identifying advice classes which must avoid static interface methods, vs helpers which don't need to (where doing that would just add fluff) - it's basically methods annotated with That's possible but that also would just add a guard - it's still helpful to flag the existence of
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Depending on how prevalent it is through the codebase, the analyzer could either run everywhere and be suppressed in the locations where it's safe or we could make the finer-grained targeting you're describing As for guarding vs guiding, you can do both but as the models evolve, guarding tends to be the piece that survives while the guiding bits can be deleted
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Basically I've had agents introduce static interface methods a lot during recent refactorings and it's been annoying to remind them not to do this - a guard would stop them from reaching a reviewable PR, but you'd still have that loop of a) initial refactor b) fails build check c) find alternative (Java8BytecodeBridge) d) fix code e) pass build check
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep that's correct, so having both would be the belt-and-suspenders so to say
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do agree with this, generally I imagine this as a funnel where we generally:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree, will look into this as a follow-up item |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| # Instrumentation and Advice Design Guidelines | ||
|
|
||
| This document outlines design constraints and best practices when writing or refactoring instrumentation and advice code. | ||
| Following these guidelines will help avoid constant-pool bloat and subtle span/scope lifecycle bugs. | ||
|
|
||
| ## Background | ||
|
|
||
| Instrumentation classes wire up type/method matchers and advice once, when the framework loads them. Advice methods | ||
| are inlined into the instrumented application's bytecode and run on every invocation of the matched method, so their | ||
| ordering directly affects when spans are visible to nested work. | ||
|
|
||
| ## Constraints to Follow | ||
|
|
||
| ### 1. Instrumentation one-shot methods | ||
|
|
||
| **Why to avoid extracting to constants:** | ||
|
|
||
| - `triggerClasses()`, `contextStore()`, `classLoaderMatcher()`, and `methodAdvice()` are called exactly once by the | ||
| framework when the instrumentation is registered | ||
| - Extracting their return values into static constants adds constant-pool bloat with no runtime benefit | ||
|
|
||
| **What to do instead:** | ||
|
|
||
| Return the values directly from the method; don't cache them in a field. | ||
|
|
||
| ```java | ||
| // BAD - unnecessary static constant | ||
| private static final ElementMatcher.Junction<ClassLoader> CL_MATCHER = hasClassesNamed("foo.Bar"); | ||
|
|
||
| @Override | ||
| public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() { | ||
| return CL_MATCHER; | ||
| } | ||
|
|
||
| // GOOD - construct directly in the one-shot method | ||
| @Override | ||
| public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() { | ||
| return hasClassesNamed("foo.Bar"); | ||
| } | ||
| ``` | ||
|
|
||
| ### 2. Scope lifecycle order | ||
|
|
||
| **Why to avoid closing the scope early:** | ||
|
|
||
| - Keeping the scope open until all work needing the current active span is done ensures decorator calls and async | ||
| callback registration see the span as active | ||
| - Closing the scope before that work runs can make nested code lose track of the current span | ||
|
|
||
| **What to do instead:** | ||
|
|
||
| Required order: decorator calls and callback registration, then `scope.close()`, then `span.finish()`. | ||
|
|
||
| ```java | ||
| // GOOD | ||
| DECORATE.onOperation(span, request); | ||
| registerCallback(span); | ||
| scope.close(); | ||
| span.finish(); | ||
| ``` | ||
|
|
||
| ### 3. Static interface methods in advice | ||
|
|
||
| **Why to avoid calling them directly:** | ||
|
|
||
| - Advice methods (`@Advice.OnMethodEnter`/`@Advice.OnMethodExit`) are inlined into the instrumented class's bytecode | ||
| - Calling a static interface method there, such as `Context.current()` or `Context.root()`, can cause a | ||
| `VerifyError` at runtime | ||
|
|
||
| **What to use instead:** | ||
|
|
||
| Use `Java8BytecodeBridge` (`currentContext()`, `rootContext()`, etc.) in place of the static interface method, | ||
| static-imported for readability. | ||
|
|
||
| ```java | ||
| import static datadog.trace.bootstrap.instrumentation.api.Java8BytecodeBridge.currentContext; | ||
|
|
||
| // BAD - static interface method call, can cause VerifyError when inlined | ||
| Context ctx = Context.current(); | ||
|
|
||
| // GOOD - use the bytecode bridge, static-imported | ||
| Context ctx = currentContext(); | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this already part of the formatter? What reason is it here as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The formatter will detect use of wildcard imports, but it won't tell you to avoid continually writing
Collections.emptyList()tens of times in the same file and instead prefer to static importCollections.emptyListand then useemptyList()everywhere.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we can drop the bit about wildcard imports, but we still need the rest
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a benefit to doing this or is this just personal preference for the codebase?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Helps readability - we've found agents don't use static imports as much, and that can make the code harder to read - especially for APIs that were designed to be used with static imports like JUnit/Mockito
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not blocking, but is there any path to doing these as static analysis as well? Said differently, is there a big downside to a static import for a single
emptyList()?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will look into that as a follow-up item