Skip to content

Infer instrumentation helper classes at build time - #12059

Open
sarahchen6 wants to merge 19 commits into
masterfrom
sarahchen6/infer-helper-classes-pt-1
Open

Infer instrumentation helper classes at build time#12059
sarahchen6 wants to merge 19 commits into
masterfrom
sarahchen6/infer-helper-classes-pt-1

Conversation

@sarahchen6

@sarahchen6 sarahchen6 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What Does This Do

This PR adds logic to generate each InstrumenterModule's helperClassNames() at build-time instead of the existing logic that requires each module's helper classes to be manually listed. The MuzzleGenerator already crawls through each module's advice in order to gather its references and emit these in a $Muzzle side-class. We simultaneously use this crawl to infer the helperClasses each module needs to load, order them dependency-first, drop build-time-only classes, and write these helperClasses directly into the module's helperClassNames() bytecode.

If a module manually declares its helperClassNames(), we use that list instead of inferring.

Also, this PR migrates a few instrumentations from manual declarations of required helper classes to auto-detecting these classes in order to test the new methodology.

Motivation

Currently, instrumentation helper classes are manually listed for each InstrumenterModule. However, we can instead use Muzzle, which already finds class references for each instrumentation, to automatically discover which helper classes need to be loaded per instrumentation. This is inspired by OTel's muzzle, which generates getMuzzleHelperClassNames() from their single build-time advice crawl.

Additional Notes

The intention is for all instrumentations to discover required helper classes at build time; however, I wanted to keep the scope of this PR low so that it's easier to review. The rest of the instrumentations will be migrated in follow-up PRs.

A follow-up PR #12191 also extracts the helper resolver logic to its own class so that the logic is separate from muzzle reference generation and more easily testable and legible.

Contributor Checklist

Jira ticket: [PROJ-IDENT]

@datadog-official

This comment has been minimized.

@DataDog DataDog deleted a comment from dd-octo-sts Bot Jul 23, 2026
@sarahchen6
sarahchen6 force-pushed the sarahchen6/infer-helper-classes-pt-1 branch from 98edf9d to a09ff60 Compare July 28, 2026 18:24
@DataDog DataDog deleted a comment from dd-octo-sts Bot Jul 29, 2026
@dd-octo-sts

dd-octo-sts Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 13.98 s 13.91 s [-0.1%; +1.1%] (no difference)
startup:insecure-bank:tracing:Agent 12.85 s 12.99 s [-1.9%; -0.3%] (maybe better)
startup:petclinic:appsec:Agent 17.52 s 17.13 s [+1.4%; +3.3%] (significantly worse)
startup:petclinic:iast:Agent 17.47 s 17.53 s [-1.3%; +0.6%] (no difference)
startup:petclinic:profiling:Agent 17.52 s 17.49 s [-1.1%; +1.4%] (no difference)
startup:petclinic:sca:Agent 17.48 s 17.19 s [+0.7%; +2.7%] (maybe worse)
startup:petclinic:tracing:Agent 16.60 s 16.74 s [-1.7%; +0.0%] (no difference)

Commit: 9139e4e6 · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

@sarahchen6
sarahchen6 force-pushed the sarahchen6/infer-helper-classes-pt-1 branch from 29b0bff to a8cdba9 Compare August 11, 2026 16:52
@sarahchen6 sarahchen6 changed the title Auto-infer instrumentation required helper classes at build time Infer instrumentation helper classes at build time Aug 11, 2026
* 1. Must implement [net.bytebuddy.build.Plugin]
* 2. Must have a constructor accepting a [java.io.File] parameter (target directory)
* 2. Must have a constructor accepting a [java.io.File] (target directory), or a two-`File`
* `(sourceDirectory, targetDirectory)` constructor when the plugin also needs the source folder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sourceDirectory is needed to determine whether it was the particular submodule that compiled the className (done in isOwnOutput(className) - if yes then inject).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 thought: The fact that target moved from the 1st position to the 2nd position make the implementation weird. Can’t we infer isOwnOutput() some otherway?‏

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to compute the source directory using the TypeDefinition and the ClassLoader.
You can load the module as URL and infer its source folder for example.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point!! Addressed this in 9139e4e by getting the module's codeSource location.

}

/**
* Same as above, but reads bytecode via the passed locator (e.g. during build time when the agent

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MuzzleGenerator needs to call HelperScanner.withClassDependencies at build-time to expand and order the helpers found, but there's no AgentClassLoader during the build (there is during runtime which is previously the only place we called HelperScanner.withClassDependencies) - so we need to pass in the build classpath's locator to use.

@sarahchen6
sarahchen6 marked this pull request as ready for review August 12, 2026 15:09
@sarahchen6
sarahchen6 requested review from a team as code owners August 12, 2026 15:09
@sarahchen6
sarahchen6 requested review from ValentinZakharov and amarziali and removed request for a team August 12, 2026 15:09
@dd-octo-sts

dd-octo-sts Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Hi! 👋 Thanks for your pull request! 🎉

To help us review it, please make sure to:

  • Add at least one type, and one component or instrumentation label to the pull request

If you need help, please check our contributing guidelines.

@sarahchen6 sarahchen6 added type: feature Enhancements and improvements comp: tooling Build & Tooling labels Aug 12, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9cea7a8021

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@datadog-official datadog-official Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Datadog Autotest: PASS

More details

Generated methods preserved all four migrated instrumentations’ former helper sets and correctly handled dependency ordering, nested helpers, and unresolved-class fallback. Runtime instrumentation also loaded the generated helpers successfully for locally executable requests.

Was this helpful? React 👍 or 👎

📊 Validated against 10 scenarios · Open Bits AI session

🤖 Datadog Autotest · Commit 9cea7a8 · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest

@PerfectSlayer
PerfectSlayer self-requested a review August 13, 2026 12:36

@PerfectSlayer PerfectSlayer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I start reviewing and will drop few inline comments for now.

📝 notes: The plugin is enabled for all instrumentations so it will apply to all instrumentations that do not explicitly override the helper declaration method, not only the few that are migrated as demo.

return classVisitor;

// Write the resolved helpers into the module's helperClassNames() so agent reads them directly.
return new HelperClassNamesWriter(classVisitor, orderedHelpers);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❔ question: ‏How is that work with class that defines helper on their parent class? Like the one extending AbstractPreparedStatementInstrumentation?

Because it feels you will always end up overriding the parent method in child classes 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep you're right - the child will override the parent here.... I think we should only generate the helpers for child classes if the parent doesn't have any defined classes. Did this in e1a67f9

InstrumenterModule module, List<Reference> allReferences, Set<String> adviceClasses) {
// A module that declares its own helper list uses it directly.
Set<String> manualHelpers = new LinkedHashSet<>(asList(module.helperClassNames()));
if (!manualHelpers.isEmpty()) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 suggestion: ‏The empty path could be improved. When module.helperClassNames() is empty, there is no need to create an arraylist than a hashset.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in e1a67f9

int lastDot = className.lastIndexOf('.');
String pkg = lastDot < 0 ? "" : className.substring(0, lastDot + 1);
String prefix = (lastDot < 0 ? className : className.substring(lastDot + 1)) + "$";
File[] siblings = dir.listFiles();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 thought:listFiles() has no deterministic order by the helpers are collected into a LinkedHashSet

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point we should sort the siblings to better reproduce the helper ordering... Done in e1a67f9

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: tooling Build & Tooling type: feature Enhancements and improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants