datadog 2.18.0 → 2.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +51 -10
  4. data/ext/datadog_profiling_native_extension/collectors_stack.c +58 -49
  5. data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -1
  6. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +5 -6
  7. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -1
  8. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +37 -26
  9. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +0 -1
  10. data/ext/datadog_profiling_native_extension/ruby_helpers.h +1 -1
  11. data/lib/datadog/appsec/api_security/route_extractor.rb +7 -1
  12. data/lib/datadog/appsec/instrumentation/gateway/argument.rb +1 -1
  13. data/lib/datadog/core/configuration/settings.rb +20 -0
  14. data/lib/datadog/core/telemetry/component.rb +8 -4
  15. data/lib/datadog/core/telemetry/event/app_started.rb +21 -3
  16. data/lib/datadog/di/instrumenter.rb +11 -18
  17. data/lib/datadog/di/probe_notification_builder.rb +21 -16
  18. data/lib/datadog/di/serializer.rb +6 -2
  19. data/lib/datadog/di.rb +0 -6
  20. data/lib/datadog/kit/appsec/events/v2.rb +195 -0
  21. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +2 -0
  22. data/lib/datadog/profiling/collectors/info.rb +41 -0
  23. data/lib/datadog/profiling/component.rb +1 -0
  24. data/lib/datadog/profiling/exporter.rb +9 -3
  25. data/lib/datadog/profiling/sequence_tracker.rb +44 -0
  26. data/lib/datadog/profiling/tag_builder.rb +2 -0
  27. data/lib/datadog/profiling.rb +1 -0
  28. data/lib/datadog/single_step_instrument.rb +9 -0
  29. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +7 -1
  30. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +13 -0
  31. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +16 -6
  32. data/lib/datadog/tracing/contrib/rails/patcher.rb +4 -1
  33. data/lib/datadog/tracing/contrib/rails/runner.rb +61 -40
  34. data/lib/datadog/tracing/diagnostics/environment_logger.rb +3 -1
  35. data/lib/datadog/tracing/span_event.rb +1 -1
  36. data/lib/datadog/tracing/span_operation.rb +22 -0
  37. data/lib/datadog/version.rb +1 -1
  38. data/lib/datadog.rb +7 -0
  39. metadata +8 -6
@@ -212,21 +212,6 @@ uint64_t native_thread_id_for(VALUE thread) {
212
212
  #endif
213
213
  }
214
214
 
215
- // Returns the stack depth by using the same approach as rb_profile_frames and backtrace_each: get the positions
216
- // of the end and current frame pointers and subtracting them.
217
- ptrdiff_t stack_depth_for(VALUE thread) {
218
- const rb_execution_context_t *ec = thread_struct_from_object(thread)->ec;
219
- const rb_control_frame_t *cfp = ec->cfp, *end_cfp = RUBY_VM_END_CONTROL_FRAME(ec);
220
-
221
- if (end_cfp == NULL) return 0;
222
-
223
- // Skip dummy frame, as seen in `backtrace_each` (`vm_backtrace.c`) and our custom rb_profile_frames
224
- // ( https://github.com/ruby/ruby/blob/4bd38e8120f2fdfdd47a34211720e048502377f1/vm_backtrace.c#L890-L914 )
225
- end_cfp = RUBY_VM_NEXT_CONTROL_FRAME(end_cfp);
226
-
227
- return end_cfp <= cfp ? 0 : end_cfp - cfp - 1;
228
- }
229
-
230
215
  // This was renamed in Ruby 3.2
231
216
  #if !defined(ccan_list_for_each) && defined(list_for_each)
232
217
  #define ccan_list_for_each list_for_each
@@ -360,11 +345,16 @@ calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id)
360
345
  }
361
346
  #endif
362
347
 
363
- // In PROF-11475 we spotted a crash when calling `rb_iseq_line_no` from this method. We couldn't reproduce or
364
- // figure out the root cause, but "just in case", we're validating that the iseq looks valid and that the
365
- // `n` used for the position is also sane, and if they don't look good, we don't calculate the line, rather
366
- // than potentially trigger any issues.
367
- if (RB_UNLIKELY(!RB_TYPE_P((VALUE) iseq, T_IMEMO) || n < 0 || n > ISEQ_BODY(iseq)->iseq_size)) return 0;
348
+ // In PROF-11475 we spotted a crash when calling `rb_iseq_line_no` from this method.
349
+ // We were only able to reproduce this issue on Ruby 2.6 and 2.7, not 2.5 or the 3.x series (tried 3.0, 3.2 and 3.4).
350
+ // Note that going out of bounds doesn't crash every time, as usual with C we may just read garbage or get lucky.
351
+ //
352
+ // For those problematic Rubies, we observed that when we try to take a sample in the middle of processing the
353
+ // VM `LEAVE` instruction, the value of `n` can violate the documented assumptions above and be
354
+ // `n > ISEQ_BODY(iseq)->iseq_size)`.
355
+ //
356
+ // To work around this and any other potential issues, we validate here that the bytecode position is sane.
357
+ if (RB_UNLIKELY(n < 0 || n > ISEQ_BODY(iseq)->iseq_size)) return 0;
368
358
 
369
359
  if (lineno) *lineno = rb_iseq_line_no(iseq, pos);
370
360
  #ifdef USE_ISEQ_NODE_ID
@@ -410,6 +400,9 @@ calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
410
400
  // * Add frame_flags.same_frame and logic to skip redoing work if the buffer already contains the same data we're collecting
411
401
  // * Skipped use of rb_callable_method_entry_t (cme) for Ruby frames as it doesn't impact us.
412
402
  // * Imported fix from https://github.com/ruby/ruby/pull/8280 to keep us closer to upstream
403
+ // * Added potential fix for https://github.com/ruby/ruby/pull/13643 (this one is a just-in-case, unclear if it happens
404
+ // for ddtrace)
405
+ // * Reversed order of iteration to better enable caching
413
406
  //
414
407
  // What is rb_profile_frames?
415
408
  // `rb_profile_frames` is a Ruby VM debug API added for use by profilers for sampling the stack trace of a Ruby thread.
@@ -445,6 +438,16 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
445
438
  // support sampling any thread (including the current) passed as an argument
446
439
  rb_thread_t *th = thread_struct_from_object(thread);
447
440
  const rb_execution_context_t *ec = th->ec;
441
+
442
+ // As of this writing, we don't support profiling with MN enabled, and this only happens in that mode, but as we
443
+ // probably want to experiment with it in the future, I've decided to import https://github.com/ruby/ruby/pull/9310
444
+ // here.
445
+ if (ec == NULL) return 0;
446
+
447
+ // I suspect this won't happen for ddtrace, but just-in-case we've imported a potential fix for
448
+ // https://github.com/ruby/ruby/pull/13643 by assuming that these can be NULL/zero with the cfp being non-NULL yet.
449
+ if (ec->vm_stack == NULL || ec->vm_stack_size == 0) return 0;
450
+
448
451
  const rb_control_frame_t *cfp = ec->cfp, *end_cfp = RUBY_VM_END_CONTROL_FRAME(ec);
449
452
  #ifndef NO_JIT_RETURN
450
453
  const rb_control_frame_t *top = cfp;
@@ -462,11 +465,6 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
462
465
  // it from https://github.com/ruby/ruby/pull/7116 in a "just in case" kind of mindset.
463
466
  if (cfp == NULL) return 0;
464
467
 
465
- // As of this writing, we don't support profiling with MN enabled, and this only happens in that mode, but as we
466
- // probably want to experiment with it in the future, I've decided to import https://github.com/ruby/ruby/pull/9310
467
- // here.
468
- if (ec == NULL) return 0;
469
-
470
468
  // Fix: Skip dummy frame that shows up in main thread.
471
469
  //
472
470
  // According to a comment in `backtrace_each` (`vm_backtrace.c`), there's two dummy frames that we should ignore
@@ -483,7 +481,20 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
483
481
  // See comment on `record_placeholder_stack_in_native_code` for a full explanation of what this means (and why we don't just return 0)
484
482
  if (end_cfp <= cfp) return PLACEHOLDER_STACK_IN_NATIVE_CODE;
485
483
 
486
- for (i=0; i<limit && cfp != end_cfp; cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)) {
484
+ // This is the position just after the top of the stack -- e.g. where a new frame pushed on the stack would end up.
485
+ const rb_control_frame_t *top_sentinel = RUBY_VM_NEXT_CONTROL_FRAME(cfp);
486
+
487
+ // We iterate the stack from bottom (beginning of thread) to the top (currently-active frame). This is different
488
+ // from upstream rb_profile_frames, but actually matches what `backtrace_each` does (yes, different Ruby VM APIs
489
+ // iterate in different directions).
490
+ // We do this to better take advantage of the `same_frame` caching mechanism: By starting from the bottom of the
491
+ // stack towards the top, we can usually keep most of the stack intact when the code is only going up and down
492
+ // a few methods at the top. Before this change, the cache was really only useful if between samples the app had
493
+ // not moved from the current stack, as adding or removing one frame would invalidate the existing cache (because
494
+ // every position would shift).
495
+ cfp = RUBY_VM_NEXT_CONTROL_FRAME(end_cfp);
496
+
497
+ for (i=0; i<limit && cfp != top_sentinel; cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) {
487
498
  if (cfp->iseq && !cfp->pc) {
488
499
  // Fix: Do nothing -- this frame should not be used
489
500
  //
@@ -41,7 +41,6 @@ rb_nativethread_id_t pthread_id_for(VALUE thread);
41
41
  bool is_current_thread_holding_the_gvl(void);
42
42
  current_gvl_owner gvl_owner(void);
43
43
  uint64_t native_thread_id_for(VALUE thread);
44
- ptrdiff_t stack_depth_for(VALUE thread);
45
44
  void ddtrace_thread_list(VALUE result_array);
46
45
  bool is_thread_alive(VALUE thread);
47
46
  VALUE thread_name_for(VALUE thread);
@@ -72,7 +72,7 @@ NORETURN(void raise_syserr(
72
72
  // reference a valid object (in which case value is not changed).
73
73
  //
74
74
  // Note: GVL can be released and other threads may get to run before this method returns
75
- bool ruby_ref_from_id(size_t id, VALUE *value);
75
+ bool ruby_ref_from_id(VALUE obj_id, VALUE *value);
76
76
 
77
77
  // Native wrapper to get the approximate/estimated current size of the passed
78
78
  // object.
@@ -10,6 +10,7 @@ module Datadog
10
10
  GRAPE_ROUTE_KEY = 'grape.routing_args'
11
11
  RAILS_ROUTE_KEY = 'action_dispatch.route_uri_pattern'
12
12
  RAILS_ROUTES_KEY = 'action_dispatch.routes'
13
+ RAILS_PATH_PARAMS_KEY = 'action_dispatch.request.path_parameters'
13
14
  RAILS_FORMAT_SUFFIX = '(.:format)'
14
15
 
15
16
  # HACK: We rely on the fact that each contrib will modify `request.env`
@@ -36,6 +37,11 @@ module Datadog
36
37
  # "/users/:id(.:format)"
37
38
  #
38
39
  # WARNING: This method works only *after* the request has been routed.
40
+ #
41
+ # WARNING: In Rails > 7.1 when a route was not found,
42
+ # action_dispatch.route_uri_pattern will not be set.
43
+ # In Rails < 7.1 it also will not be set even if a route was found,
44
+ # but in this case action_dispatch.request.path_parameters won't be empty.
39
45
  def self.route_pattern(request)
40
46
  if request.env.key?(GRAPE_ROUTE_KEY)
41
47
  pattern = request.env[GRAPE_ROUTE_KEY][:route_info]&.pattern&.origin
@@ -45,7 +51,7 @@ module Datadog
45
51
  "#{request.script_name}#{pattern}"
46
52
  elsif request.env.key?(RAILS_ROUTE_KEY)
47
53
  request.env[RAILS_ROUTE_KEY].delete_suffix(RAILS_FORMAT_SUFFIX)
48
- elsif request.env.key?(RAILS_ROUTES_KEY)
54
+ elsif request.env.key?(RAILS_ROUTES_KEY) && !request.env.fetch(RAILS_PATH_PARAMS_KEY, {}).empty?
49
55
  pattern = request.env[RAILS_ROUTES_KEY].router
50
56
  .recognize(request) { |route, _| break route.path.spec.to_s }
51
57
 
@@ -13,7 +13,7 @@ module Datadog
13
13
  class User < Argument
14
14
  attr_reader :id, :login, :session_id
15
15
 
16
- def initialize(id, login = nil, session_id = nil)
16
+ def initialize(id = nil, login = nil, session_id = nil)
17
17
  super()
18
18
 
19
19
  @id = id
@@ -554,6 +554,26 @@ module Datadog
554
554
  o.env 'DD_PROFILING_NATIVE_FILENAMES_ENABLED'
555
555
  o.default true
556
556
  end
557
+
558
+ # Controls if the profiler should sample directly from the signal handler.
559
+ # Sampling directly from the signal handler improves accuracy of the data collected.
560
+ #
561
+ # We recommend using this setting with Ruby 3.2.5+ / Ruby 3.3.4+ and above
562
+ # as they include additional safety measures added in https://github.com/ruby/ruby/pull/11036.
563
+ # We have not validated it thoroughly with earlier versions, but in practice it should work on Ruby 3.0+
564
+ # (the key change was https://github.com/ruby/ruby/pull/3296).
565
+ #
566
+ # Enabling this on Ruby 2 is not recommended as it may cause VM crashes and/or incorrect data.
567
+ #
568
+ # @default true on Ruby 3.2.5+ / Ruby 3.3.4+, false on older Rubies
569
+ option :sighandler_sampling_enabled do |o|
570
+ o.type :bool
571
+ o.env 'DD_PROFILING_SIGHANDLER_SAMPLING_ENABLED'
572
+ o.default do
573
+ Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.2.5') &&
574
+ !(RUBY_VERSION.start_with?('3.3.') && Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.3.4'))
575
+ end
576
+ end
557
577
  end
558
578
 
559
579
  # @public_api
@@ -62,7 +62,9 @@ module Datadog
62
62
  return unless @enabled
63
63
 
64
64
  @transport = if settings.telemetry.agentless_enabled
65
- agent_settings = Core::Configuration::AgentlessSettingsResolver.call(
65
+ # We don't touch the `agent_settings` since we still want the telemetry payloads to refer to the original
66
+ # settings, even though the telemetry itself may be using a different path.
67
+ telemetry_specific_agent_settings = Core::Configuration::AgentlessSettingsResolver.call(
66
68
  settings,
67
69
  host_prefix: 'instrumentation-telemetry-intake',
68
70
  url_override: settings.telemetry.agentless_url_override,
@@ -70,7 +72,7 @@ module Datadog
70
72
  logger: logger,
71
73
  )
72
74
  Telemetry::Transport::HTTP.agentless_telemetry(
73
- agent_settings: agent_settings,
75
+ agent_settings: telemetry_specific_agent_settings,
74
76
  logger: logger,
75
77
  # api_key should have already validated to be
76
78
  # not nil by +build+ method above.
@@ -96,6 +98,8 @@ module Datadog
96
98
  logger: logger,
97
99
  shutdown_timeout: settings.telemetry.shutdown_timeout_seconds,
98
100
  )
101
+
102
+ @agent_settings = agent_settings
99
103
  end
100
104
 
101
105
  def disable!
@@ -107,9 +111,9 @@ module Datadog
107
111
  return if !@enabled
108
112
 
109
113
  initial_event = if initial_event_is_change
110
- Event::SynthAppClientConfigurationChange.new
114
+ Event::SynthAppClientConfigurationChange.new(agent_settings: @agent_settings)
111
115
  else
112
- Event::AppStarted.new
116
+ Event::AppStarted.new(agent_settings: @agent_settings)
113
117
  end
114
118
 
115
119
  @worker.start(initial_event)
@@ -8,6 +8,10 @@ module Datadog
8
8
  module Event
9
9
  # Telemetry class for the 'app-started' event
10
10
  class AppStarted < Base
11
+ def initialize(agent_settings:)
12
+ @agent_settings = agent_settings
13
+ end
14
+
11
15
  def type
12
16
  'app-started'
13
17
  end
@@ -96,7 +100,7 @@ module Datadog
96
100
  ),
97
101
 
98
102
  # Mix of env var, programmatic and default config, so we use unknown
99
- conf_value('DD_AGENT_TRANSPORT', agent_transport(config), seq_id, 'unknown'),
103
+ conf_value('DD_AGENT_TRANSPORT', agent_transport, seq_id, 'unknown'),
100
104
 
101
105
  # writer_options is defined as an option that has a Hash value.
102
106
  conf_value(
@@ -162,6 +166,20 @@ module Datadog
162
166
  )
163
167
  end
164
168
 
169
+ instrumentation_source = if Datadog.const_defined?(:SingleStepInstrument, false) &&
170
+ Datadog::SingleStepInstrument.const_defined?(:LOADED, false) &&
171
+ Datadog::SingleStepInstrument::LOADED
172
+ 'ssi'
173
+ else
174
+ 'manual'
175
+ end
176
+ # Track ssi configurations
177
+ list.push(
178
+ conf_value('instrumentation_source', instrumentation_source, seq_id, 'code'),
179
+ conf_value('DD_INJECT_FORCE', Core::Environment::VariableHelpers.env_to_bool('DD_INJECT_FORCE', false), seq_id, 'env_var'),
180
+ conf_value('DD_INJECTION_ENABLED', ENV['DD_INJECTION_ENABLED'] || '', seq_id, 'env_var'),
181
+ )
182
+
165
183
  # Add some more custom additional payload values here
166
184
  if config.logger.instance
167
185
  list << conf_value(
@@ -200,8 +218,8 @@ module Datadog
200
218
  # standard:enable Metrics/AbcSize
201
219
  # standard:enable Metrics/MethodLength
202
220
 
203
- def agent_transport(config)
204
- adapter = Core::Configuration::AgentSettingsResolver.call(config).adapter
221
+ def agent_transport
222
+ adapter = @agent_settings.adapter
205
223
  if adapter == Datadog::Core::Transport::Ext::UnixSocket::ADAPTER
206
224
  'UDS'
207
225
  else
@@ -121,9 +121,8 @@ module Datadog
121
121
  if rate_limiter.nil? || rate_limiter.allow?
122
122
  # Arguments may be mutated by the method, therefore
123
123
  # they need to be serialized prior to method invocation.
124
- entry_args = if probe.capture_snapshot?
125
- instance_vars = Instrumenter.get_instance_variables(self)
126
- serializer.serialize_args(args, kwargs, instance_vars,
124
+ serialized_entry_args = if probe.capture_snapshot?
125
+ serializer.serialize_args(args, kwargs, self,
127
126
  depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
128
127
  attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
129
128
  end
@@ -160,9 +159,10 @@ module Datadog
160
159
  caller_locs = method_frame + caller_locations # steep:ignore
161
160
  # TODO capture arguments at exit
162
161
  # & is to stop steep complaints, block is always present here.
163
- block&.call(probe: probe, rv: rv, duration: duration, caller_locations: caller_locs,
164
- instance_vars: probe.capture_snapshot? ? Instrumenter.get_instance_variables(self) : nil,
165
- serialized_entry_args: entry_args)
162
+ block&.call(probe: probe, rv: rv,
163
+ duration: duration, caller_locations: caller_locs,
164
+ target_self: self,
165
+ serialized_entry_args: serialized_entry_args)
166
166
  rv
167
167
  else
168
168
  # stop standard from trying to mess up my code
@@ -311,19 +311,20 @@ module Datadog
311
311
  probe.file == tp.path || probe.file_matches?(tp.path)
312
312
  )
313
313
  if rate_limiter.nil? || rate_limiter.allow?
314
- locals = if probe.capture_snapshot?
314
+ serialized_locals = if probe.capture_snapshot?
315
315
  serializer.serialize_vars(Instrumenter.get_local_variables(tp),
316
316
  depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
317
317
  attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count,)
318
318
  end
319
- instance_vars = if probe.capture_snapshot?
320
- serializer.serialize_vars(Instrumenter.get_instance_variables(tp.self),
319
+ if probe.capture_snapshot?
320
+ serializer.serialize_value(tp.self,
321
321
  depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
322
322
  attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count,)
323
323
  end
324
324
  # & is to stop steep complaints, block is always present here.
325
325
  block&.call(probe: probe,
326
- locals: locals, instance_vars: instance_vars,
326
+ serialized_locals: serialized_locals,
327
+ target_self: tp.self,
327
328
  path: tp.path, caller_locations: caller_locations)
328
329
  end
329
330
  end
@@ -397,14 +398,6 @@ module Datadog
397
398
  end
398
399
 
399
400
  class << self
400
- def get_instance_variables(object)
401
- {}.tap do |hash|
402
- object.instance_variables.each do |var|
403
- hash[var] = object.instance_variable_get(var)
404
- end
405
- end
406
- end
407
-
408
401
  def get_local_variables(trace_point)
409
402
  # binding appears to be constructed on access, therefore
410
403
  # 1) we should attempt to cache it and
@@ -42,9 +42,9 @@ module Datadog
42
42
  # path is the actual path of the instrumented file.
43
43
  def build_executed(probe,
44
44
  path: nil, rv: nil, duration: nil, caller_locations: nil,
45
- locals: nil, args: nil, kwargs: nil, instance_vars: nil,
45
+ serialized_locals: nil, args: nil, kwargs: nil, target_self: nil,
46
46
  serialized_entry_args: nil)
47
- build_snapshot(probe, rv: rv, locals: locals,
47
+ build_snapshot(probe, rv: rv, serialized_locals: serialized_locals,
48
48
  # Actual path of the instrumented file.
49
49
  path: path,
50
50
  duration: duration,
@@ -52,14 +52,23 @@ module Datadog
52
52
  # this should be all frames for enriched probes and no frames for
53
53
  # non-enriched probes?
54
54
  caller_locations: caller_locations,
55
- args: args, kwargs: kwargs, instance_vars: instance_vars,
55
+ args: args, kwargs: kwargs,
56
+ target_self: target_self,
56
57
  serialized_entry_args: serialized_entry_args)
57
58
  end
58
59
 
59
- def build_snapshot(probe, rv: nil, locals: nil, path: nil,
60
+ def build_snapshot(probe, rv: nil, serialized_locals: nil, path: nil,
61
+ # In Ruby everything is a method, therefore we should always have
62
+ # a target self. However, if we are not capturing a snapshot,
63
+ # there is no need to pass in the target self.
64
+ target_self: nil,
60
65
  duration: nil, caller_locations: nil,
61
- args: nil, kwargs: nil, instance_vars: nil,
66
+ args: nil, kwargs: nil,
62
67
  serialized_entry_args: nil)
68
+ if probe.capture_snapshot? && !target_self
69
+ raise ArgumentError, "Asked to build snapshot with snapshot capture but target_self is nil"
70
+ end
71
+
63
72
  # TODO also verify that non-capturing probe does not pass
64
73
  # snapshot or vars/args into this method
65
74
  captures = if probe.capture_snapshot?
@@ -68,25 +77,18 @@ module Datadog
68
77
  "@return": serializer.serialize_value(rv,
69
78
  depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
70
79
  attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count),
80
+ self: serializer.serialize_value(target_self),
71
81
  }
72
- if instance_vars
73
- return_arguments.update(
74
- serializer.serialize_vars(instance_vars,
75
- depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
76
- attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count,)
77
- )
78
- end
79
82
  {
80
83
  entry: {
81
84
  # standard:disable all
82
85
  arguments: if serialized_entry_args
83
86
  serialized_entry_args
84
87
  else
85
- (args || kwargs) && serializer.serialize_args(args, kwargs, instance_vars,
88
+ (args || kwargs) && serializer.serialize_args(args, kwargs, target_self,
86
89
  depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
87
90
  attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
88
91
  end,
89
- throwable: nil,
90
92
  # standard:enable all
91
93
  },
92
94
  return: {
@@ -96,8 +98,11 @@ module Datadog
96
98
  }
97
99
  elsif probe.line?
98
100
  {
99
- lines: locals && {
100
- probe.line_no => {locals: locals.merge(instance_vars || {})},
101
+ lines: serialized_locals && {
102
+ probe.line_no => {
103
+ locals: serialized_locals,
104
+ arguments: {self: serializer.serialize_value(target_self)},
105
+ },
101
106
  },
102
107
  }
103
108
  end
@@ -36,6 +36,10 @@ module Datadog
36
36
  # efficient but there would be additional overhead from passing this
37
37
  # parameter all the time and the API would get more complex.
38
38
  #
39
+ # Note: "self" cannot be used as a parameter name in Ruby, therefore
40
+ # there should never be a conflict between instance variable
41
+ # serialization and method parameters.
42
+ #
39
43
  # @api private
40
44
  class Serializer
41
45
  # Third-party library integration / custom serializers.
@@ -86,7 +90,7 @@ module Datadog
86
90
  # Instance variables are technically a hash just like kwargs,
87
91
  # we take them as a separate parameter to avoid a hash merge
88
92
  # in upstream code.
89
- def serialize_args(args, kwargs, instance_vars,
93
+ def serialize_args(args, kwargs, target_self,
90
94
  depth: settings.dynamic_instrumentation.max_capture_depth,
91
95
  attribute_count: settings.dynamic_instrumentation.max_capture_attribute_count)
92
96
  counter = 0
@@ -95,7 +99,7 @@ module Datadog
95
99
  # Conversion to symbol is needed here to put args ahead of
96
100
  # kwargs when they are merged below.
97
101
  c[:"arg#{counter}"] = value
98
- end.update(kwargs).update(instance_vars)
102
+ end.update(kwargs).update(self: target_self)
99
103
  serialize_vars(combined, depth: depth, attribute_count: attribute_count)
100
104
  end
101
105
 
data/lib/datadog/di.rb CHANGED
@@ -35,9 +35,3 @@ module Datadog
35
35
  end
36
36
  end
37
37
  end
38
-
39
- # Line probes will not work on Ruby < 2.6 because of lack of :script_compiled
40
- # trace point. Activate DI automatically on supported Ruby versions but
41
- # always load its settings so that, for example, turning DI off when
42
- # we are on Ruby 2.5 does not produce exceptions.
43
- require_relative 'di/boot' if RUBY_VERSION >= '2.6' && RUBY_ENGINE != 'jruby'