Skip to content

DI: capture real positional parameter names in non-virtual method probes - #6114

Draft
p-datadog wants to merge 11 commits into
masterfrom
di-method-probe-real-arg-names
Draft

DI: capture real positional parameter names in non-virtual method probes#6114
p-datadog wants to merge 11 commits into
masterfrom
di-method-probe-real-arg-names

Conversation

@p-datadog

@p-datadog p-datadog commented Jul 28, 2026

Copy link
Copy Markdown
Member

What does this PR do?

Captures real positional parameter names in probes on explicitly defined methods (i.e. NOT method_missing / C code).

Motivation:

The arg1 etc. placeholders are not discoverable by customers.

Change log entry

Yes. Dynamic Instrumentation: report positional method
argument names for methods explicitly defined in Ruby

Additional Notes:

Virtual methods (method_missing) and C code still doesn't reveal the argument names. arg1 etc. placeholders remain for those.

How to test the change?

Unit and integration tests added

Method-probe snapshots keyed captured positional arguments as arg1,
arg2, ... because the prepend wrapper captures them as *args, losing the
declared names. Recover the names at hook time via UnboundMethod#parameters
(the same reflection the symbol database extractor uses), thread them
through run_method_probe into the serializer, and key each positional
argument by its real name. Arguments without an available name (generated
methods, values absorbed by a splat, virtual/C methods) keep the arg-N
label. Keyword arguments and self are unchanged.

This also lets capture expressions reference positional parameters by
their real names.
@p-datadog p-datadog added the AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos label Jul 28, 2026
@dd-octo-sts dd-octo-sts Bot added the debugger Live Debugger (+Dynamic Instrumentation, +Symbol Database) label Jul 28, 2026
@dd-octo-sts

dd-octo-sts Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Typing analysis

Note: Ignored files are excluded from the next sections.

steep:ignore comments

This PR introduces 12 steep:ignore comments, and clears 12 steep:ignore comments.

steep:ignore comments (+12-12)Introduced:
lib/datadog/di/instrumenter.rb:197
lib/datadog/di/instrumenter.rb:202
lib/datadog/di/instrumenter.rb:206
lib/datadog/di/instrumenter.rb:211
lib/datadog/di/instrumenter.rb:215
lib/datadog/di/instrumenter.rb:234
lib/datadog/di/instrumenter.rb:295
lib/datadog/di/instrumenter.rb:297
lib/datadog/di/instrumenter.rb:940
lib/datadog/di/serializer.rb:297
lib/datadog/di/serializer.rb:437
lib/datadog/di/serializer.rb:562
Cleared:
lib/datadog/di/instrumenter.rb:196
lib/datadog/di/instrumenter.rb:201
lib/datadog/di/instrumenter.rb:205
lib/datadog/di/instrumenter.rb:210
lib/datadog/di/instrumenter.rb:214
lib/datadog/di/instrumenter.rb:233
lib/datadog/di/instrumenter.rb:294
lib/datadog/di/instrumenter.rb:296
lib/datadog/di/instrumenter.rb:903
lib/datadog/di/serializer.rb:264
lib/datadog/di/serializer.rb:404
lib/datadog/di/serializer.rb:529

Untyped methods

This PR introduces 5 partially typed methods, and clears 5 partially typed methods. It increases the percentage of typed methods from 69.52% to 69.53% (+0.01%).

Partially typed methods (+5-5)Introduced:
sig/datadog/di/instrumenter.rbs:63
└── def run_method_probe: (::Array[untyped] args, ::Hash[::Symbol, untyped] kwargs, ::Proc? target_block, Object target_self, Probe probe, untyped responder, [::String, ::Integer]? loc, ::String method_name, ?::Array[::Symbol?]? positional_param_names) { () -> untyped } -> untyped
sig/datadog/di/instrumenter.rbs:75
└── def line_trace_point_callback: (Probe probe, RubyVM::InstructionSequence? iseq, untyped responder, TracePoint tp) -> void
sig/datadog/di/instrumenter.rbs:79
└── def check_and_disable_if_exceeded: (Probe probe, untyped responder, Float di_start_time, ?Float accumulated_duration) -> void
sig/datadog/di/serializer.rbs:25
└── def combine_args: (::Array[untyped] args, ::Hash[::Symbol, untyped] kwargs, Object target_self, ?::Array[::Symbol?]? param_names) -> ::Hash[::Symbol, untyped]
sig/datadog/di/serializer.rbs:26
└── def serialize_args: (::Array[untyped] args, ::Hash[::Symbol, untyped] kwargs, untyped instance_vars, ?::Array[::Symbol?]? param_names, ?depth: ::Integer, ?attribute_count: ::Integer?, ?length: ::Integer?, ?collection_size: ::Integer?) -> ::Hash[::Symbol, untyped]
Cleared:
sig/datadog/di/instrumenter.rbs:63
└── def run_method_probe: (::Array[untyped] args, ::Hash[::Symbol, untyped] kwargs, ::Proc? target_block, Object target_self, Probe probe, untyped responder, [::String, ::Integer]? loc, ::String method_name) { () -> untyped } -> untyped
sig/datadog/di/instrumenter.rbs:73
└── def line_trace_point_callback: (Probe probe, RubyVM::InstructionSequence? iseq, untyped responder, TracePoint tp) -> void
sig/datadog/di/instrumenter.rbs:77
└── def check_and_disable_if_exceeded: (Probe probe, untyped responder, Float di_start_time, ?Float accumulated_duration) -> void
sig/datadog/di/serializer.rbs:25
└── def combine_args: (Array[untyped] args, Hash[Symbol, untyped] kwargs, Object target_self) -> Hash[Symbol, untyped]
sig/datadog/di/serializer.rbs:26
└── def serialize_args: (Array[untyped] args, Hash[Symbol, untyped] kwargs, untyped instance_vars, ?depth: Integer, ?attribute_count: Integer?, ?length: Integer?, ?collection_size: Integer?) -> Hash[Symbol, untyped]

If you believe a method or an attribute is rightfully untyped or partially typed, you can add # untyped:accept on the line before the definition to remove it from the stats.

@datadog-official

datadog-official Bot commented Jul 28, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 1b5b038 | Docs | Datadog PR Page | Give us feedback!

p-ddsign and others added 2 commits July 28, 2026 13:40
Omit the explicit StandardError class in the positional-parameter-name
extraction rescue (rescue => e), fixing the standard/lint CI failure
(bundle exec rubocop -D).

Verified under gemfiles/ruby-4.0.gemfile: `bundle exec rubocop -D`
reports no offenses and `bundle exec rake standard` passes.

Co-Authored-By: Claude <noreply@anthropic.com>
…cs, RBS prefix

- extract_positional_param_names: rescue Exception + reraise_if_fatal + telemetry
  (matches the DI catch-all convention used elsewhere in the file)
- combine_args / serialize_args: full YARD param/return docs after signature change
- instrumenter.rbs: ::UnboundMethod prefix
@p-datadog p-datadog changed the title DI: label method-probe positional args with real parameter names DI: capture real positional parameter names in non-virtual method probes Jul 28, 2026
@pr-commenter

pr-commenter Bot commented Jul 28, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-08-12 22:04:07

Comparing candidate commit 1b5b038 in PR branch di-method-probe-real-arg-names with baseline commit e1e1961 in branch master.

📊 Benchmarking dashboard

Found 0 performance improvements and 0 performance regressions! Performance is the same for 48 metrics, 1 unstable metrics.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

Unstable benchmarks

These benchmarks have a confidence interval too wide to call a change; treat them as noise rather than signal.

scenario:tracing - trace.to_digest - Continue

  • unstable throughput [-1473.562op/s; +1605.675op/s] or [-5.017%; +5.467%]

…d arg

Keying positional method-probe arguments by their real parameter name let
Serializer#combine_args's `combined.update(kwargs)` overwrite a positional
value when a keyword argument shares the positional's name (e.g.
`def foo(path, **opts)` called `foo('/a', path: 'x')`): the positional value
was silently dropped from the snapshot. Fall back to the arg-N label for a
positional whose name collides with a keyword key so both values are kept.

Also from review:
- serializer_spec: cover the collision fallback and confirm a positional whose
  real name is a redacted identifier is now redacted (previously the arg-N
  label captured it in the clear).
- instrumenter_spec: cover extract_positional_param_names for an attr_writer-
  generated method (nil parameter name) and its rescue branch.
- serializer.rbs: :: prefix on the stdlib types in the changed combine_args /
  serialize_args signatures.
…ames

When a method already carries a probe, hook_method's instance_method call
resolves to the earlier probe's prepended wrapper, whose signature is
*args, **kwargs and whose source location is inside instrumenter.rb. A
later probe on that method then read empty positional parameter names and
labeled captured arguments arg1, arg2, ...; capture expressions naming the
real parameters resolved to nil.

hook_method now walks target_method through DI wrapper modules via
super_method before reading source location and parameter names, so every
probe reads the original definition regardless of probe order. Each
wrapper module carries InstrumentedMethodMarker; other prepended modules
stop the walk and are honored as-is.
Explain the wrapper-resolution mechanism once, on original_target_method;
drop the type-only YARD tags it duplicated from the rbs signature and the
restating comment at the call site.
combine_args and serialize_args accepted param_names as a defaulted
positional argument, mixing positional defaults with keyword parameters
(prohibited by the repo's No Mixing Default Positional and Keyword
Arguments rule). Change param_names to a keyword argument in both
methods, update all call sites in instrumenter.rb and serializer_spec.rb,
and add a YARD docstring to the InstrumentedMethodMarker module (required
by the repo's every module must have a docstring rule).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos debugger Live Debugger (+Dynamic Instrumentation, +Symbol Database)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants