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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +47 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +51 -10
- data/ext/datadog_profiling_native_extension/collectors_stack.c +58 -49
- data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -1
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +5 -6
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +37 -26
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +0 -1
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +1 -1
- data/lib/datadog/appsec/api_security/route_extractor.rb +7 -1
- data/lib/datadog/appsec/instrumentation/gateway/argument.rb +1 -1
- data/lib/datadog/core/configuration/settings.rb +20 -0
- data/lib/datadog/core/telemetry/component.rb +8 -4
- data/lib/datadog/core/telemetry/event/app_started.rb +21 -3
- data/lib/datadog/di/instrumenter.rb +11 -18
- data/lib/datadog/di/probe_notification_builder.rb +21 -16
- data/lib/datadog/di/serializer.rb +6 -2
- data/lib/datadog/di.rb +0 -6
- data/lib/datadog/kit/appsec/events/v2.rb +195 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +2 -0
- data/lib/datadog/profiling/collectors/info.rb +41 -0
- data/lib/datadog/profiling/component.rb +1 -0
- data/lib/datadog/profiling/exporter.rb +9 -3
- data/lib/datadog/profiling/sequence_tracker.rb +44 -0
- data/lib/datadog/profiling/tag_builder.rb +2 -0
- data/lib/datadog/profiling.rb +1 -0
- data/lib/datadog/single_step_instrument.rb +9 -0
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +7 -1
- data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +13 -0
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +16 -6
- data/lib/datadog/tracing/contrib/rails/patcher.rb +4 -1
- data/lib/datadog/tracing/contrib/rails/runner.rb +61 -40
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +3 -1
- data/lib/datadog/tracing/span_event.rb +1 -1
- data/lib/datadog/tracing/span_operation.rb +22 -0
- data/lib/datadog/version.rb +1 -1
- data/lib/datadog.rb +7 -0
- 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.
|
364
|
-
//
|
365
|
-
//
|
366
|
-
//
|
367
|
-
|
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
|
-
|
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(
|
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
|
|
@@ -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
|
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:
|
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
|
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
|
204
|
-
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
|
-
|
125
|
-
|
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,
|
164
|
-
|
165
|
-
|
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
|
-
|
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
|
-
|
320
|
-
serializer.
|
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
|
-
|
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
|
-
|
45
|
+
serialized_locals: nil, args: nil, kwargs: nil, target_self: nil,
|
46
46
|
serialized_entry_args: nil)
|
47
|
-
build_snapshot(probe, rv: rv,
|
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,
|
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,
|
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,
|
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,
|
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:
|
100
|
-
probe.line_no => {
|
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,
|
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(
|
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'
|