Skip to content

Commit

Permalink
DEBUG-2334 Ruby 2 compatibility for dynamic instrumentation (#4120)
Browse files Browse the repository at this point in the history
  • Loading branch information
p-datadog authored Nov 15, 2024
1 parent 09cc6dc commit a7c5f81
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 10 deletions.
13 changes: 12 additions & 1 deletion lib/datadog/di/instrumenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,19 @@ def hook_method(probe, &block)
serializer.serialize_args(args, kwargs)
end
rv = nil
# Under Ruby 2.6 we cannot just call super(*args, **kwargs)
duration = Benchmark.realtime do # steep:ignore
rv = super(*args, **kwargs)
rv = if args.any?
if kwargs.any?
super(*args, **kwargs)
else
super(*args)
end
elsif kwargs.any?
super(**kwargs)
else
super()
end
end
# The method itself is not part of the stack trace because
# we are getting the stack trace from outside of the method.
Expand Down
4 changes: 4 additions & 0 deletions spec/datadog/di/hook_method.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ def hook_test_method_with_kwarg(kwarg:)
kwarg
end

def hook_test_method_with_pos_and_kwarg(arg, kwarg:)
[arg, kwarg]
end

def recursive(depth)
if depth > 0
recursive(depth - 1) + '-'
Expand Down
123 changes: 114 additions & 9 deletions spec/datadog/di/instrumenter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,19 +102,124 @@
capture_snapshot: true}
end

it 'invokes callback and captures parameters' do
instrumenter.hook_method(probe) do |payload|
observed_calls << payload
let(:target_call) do
expect(HookTestClass.new.hook_test_method_with_arg(2)).to eq 2
end

shared_examples 'invokes callback and captures parameters' do
it 'invokes callback and captures parameters' do
instrumenter.hook_method(probe) do |payload|
observed_calls << payload
end

target_call

expect(observed_calls.length).to eq 1
expect(observed_calls.first.keys.sort).to eq call_keys
expect(observed_calls.first[:rv]).to eq 2
expect(observed_calls.first[:duration]).to be_a(Float)

expect(observed_calls.first[:serialized_entry_args]).to eq(arg1: {type: 'Integer', value: '2'})
end
end

expect(HookTestClass.new.hook_test_method_with_arg(2)).to eq 2
include_examples 'invokes callback and captures parameters'

expect(observed_calls.length).to eq 1
expect(observed_calls.first.keys.sort).to eq call_keys
expect(observed_calls.first[:rv]).to eq 2
expect(observed_calls.first[:duration]).to be_a(Float)
context 'when passed via a splat' do
let(:target_call) do
args = [2]
expect(HookTestClass.new.hook_test_method_with_arg(*args)).to eq 2
end

include_examples 'invokes callback and captures parameters'
end
end
end

context 'keyword args' do
context 'with snapshot capture' do
let(:probe_args) do
{type_name: 'HookTestClass', method_name: 'hook_test_method_with_kwarg',
capture_snapshot: true}
end

let(:target_call) do
expect(HookTestClass.new.hook_test_method_with_kwarg(kwarg: 42)).to eq 42
end

shared_examples 'invokes callback and captures parameters' do
it 'invokes callback and captures parameters' do
instrumenter.hook_method(probe) do |payload|
observed_calls << payload
end

target_call

expect(observed_calls.length).to eq 1
expect(observed_calls.first.keys.sort).to eq call_keys
expect(observed_calls.first[:rv]).to eq 42
expect(observed_calls.first[:duration]).to be_a(Float)

expect(observed_calls.first[:serialized_entry_args]).to eq(kwarg: {type: 'Integer', value: '42'})
end
end

include_examples 'invokes callback and captures parameters'

context 'when passed via a splat' do
let(:target_call) do
kwargs = {kwarg: 42}
expect(HookTestClass.new.hook_test_method_with_kwarg(**kwargs)).to eq 42
end

include_examples 'invokes callback and captures parameters'
end
end
end

context 'positional and keyword args' do
context 'with snapshot capture' do
let(:probe_args) do
{type_name: 'HookTestClass', method_name: 'hook_test_method_with_pos_and_kwarg',
capture_snapshot: true}
end

let(:target_call) do
expect(HookTestClass.new.hook_test_method_with_pos_and_kwarg(41, kwarg: 42)).to eq [41, 42]
end

shared_examples 'invokes callback and captures parameters' do
it 'invokes callback and captures parameters' do
instrumenter.hook_method(probe) do |payload|
observed_calls << payload
end

target_call

expect(observed_calls.length).to eq 1
expect(observed_calls.first.keys.sort).to eq call_keys
expect(observed_calls.first[:rv]).to eq [41, 42]
expect(observed_calls.first[:duration]).to be_a(Float)

expect(observed_calls.first[:serialized_entry_args]).to eq(
# TODO actual argument name not captured yet,
# requires method call trace point.
arg1: {type: 'Integer', value: '41'},
kwarg: {type: 'Integer', value: '42'}
)
end
end

include_examples 'invokes callback and captures parameters'

context 'when passed via a splat' do
let(:target_call) do
args = [41]
kwargs = {kwarg: 42}
expect(HookTestClass.new.hook_test_method_with_pos_and_kwarg(*args, **kwargs)).to eq [41, 42]
end

expect(observed_calls.first[:serialized_entry_args]).to eq(arg1: {type: 'Integer', value: '2'})
include_examples 'invokes callback and captures parameters'
end
end
end
Expand Down

0 comments on commit a7c5f81

Please sign in to comment.