datadog 2.3.0 → 2.5.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 +64 -2
- data/ext/datadog_profiling_loader/datadog_profiling_loader.c +9 -1
- data/ext/datadog_profiling_loader/extconf.rb +10 -22
- data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +3 -3
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +198 -41
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +4 -2
- data/ext/datadog_profiling_native_extension/collectors_stack.c +89 -46
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +645 -107
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +15 -1
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +0 -27
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -4
- data/ext/datadog_profiling_native_extension/extconf.rb +42 -25
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +50 -0
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +75 -0
- data/ext/datadog_profiling_native_extension/heap_recorder.c +194 -34
- data/ext/datadog_profiling_native_extension/heap_recorder.h +11 -0
- data/ext/datadog_profiling_native_extension/http_transport.c +38 -6
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +1 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +53 -2
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -0
- data/ext/datadog_profiling_native_extension/profiling.c +1 -1
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +14 -11
- data/ext/datadog_profiling_native_extension/stack_recorder.c +58 -22
- data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -0
- data/ext/libdatadog_api/crashtracker.c +20 -18
- data/ext/libdatadog_api/datadog_ruby_common.c +0 -27
- data/ext/libdatadog_api/datadog_ruby_common.h +0 -4
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +2184 -108
- data/lib/datadog/appsec/assets/waf_rules/strict.json +1430 -2
- data/lib/datadog/appsec/component.rb +29 -8
- data/lib/datadog/appsec/configuration/settings.rb +10 -2
- data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +1 -0
- data/lib/datadog/appsec/contrib/devise/patcher/rememberable_patch.rb +21 -0
- data/lib/datadog/appsec/contrib/devise/patcher.rb +12 -2
- data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +0 -14
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +67 -31
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +14 -15
- data/lib/datadog/appsec/contrib/graphql/integration.rb +14 -1
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +7 -20
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +2 -5
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -15
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +6 -18
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +7 -20
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +5 -18
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +3 -1
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +3 -5
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +5 -18
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +6 -10
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +7 -20
- data/lib/datadog/appsec/event.rb +25 -1
- data/lib/datadog/appsec/ext.rb +4 -0
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +3 -5
- data/lib/datadog/appsec/monitor/reactive/set_user.rb +7 -20
- data/lib/datadog/appsec/processor/context.rb +109 -0
- data/lib/datadog/appsec/processor/rule_loader.rb +3 -1
- data/lib/datadog/appsec/processor/rule_merger.rb +33 -15
- data/lib/datadog/appsec/processor.rb +42 -107
- data/lib/datadog/appsec/rate_limiter.rb +25 -40
- data/lib/datadog/appsec/remote.rb +7 -3
- data/lib/datadog/appsec/scope.rb +1 -4
- data/lib/datadog/appsec/utils/trace_operation.rb +15 -0
- data/lib/datadog/appsec/utils.rb +2 -0
- data/lib/datadog/appsec.rb +3 -2
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +26 -25
- data/lib/datadog/core/configuration/components.rb +4 -3
- data/lib/datadog/core/configuration/settings.rb +96 -5
- data/lib/datadog/core/configuration.rb +1 -3
- data/lib/datadog/core/crashtracking/component.rb +9 -6
- data/lib/datadog/core/environment/execution.rb +5 -5
- data/lib/datadog/core/environment/yjit.rb +5 -0
- data/lib/datadog/core/metrics/client.rb +7 -0
- data/lib/datadog/core/rate_limiter.rb +183 -0
- data/lib/datadog/core/remote/client/capabilities.rb +4 -3
- data/lib/datadog/core/remote/component.rb +4 -2
- data/lib/datadog/core/remote/negotiation.rb +4 -4
- data/lib/datadog/core/remote/tie.rb +2 -0
- data/lib/datadog/core/remote/transport/http.rb +5 -0
- data/lib/datadog/core/remote/worker.rb +1 -1
- data/lib/datadog/core/runtime/ext.rb +1 -0
- data/lib/datadog/core/runtime/metrics.rb +5 -1
- data/lib/datadog/core/semaphore.rb +35 -0
- data/lib/datadog/core/telemetry/component.rb +2 -0
- data/lib/datadog/core/telemetry/event.rb +12 -7
- data/lib/datadog/core/telemetry/logger.rb +51 -0
- data/lib/datadog/core/telemetry/logging.rb +50 -14
- data/lib/datadog/core/telemetry/request.rb +13 -1
- data/lib/datadog/core/transport/ext.rb +1 -0
- data/lib/datadog/core/utils/time.rb +12 -0
- data/lib/datadog/core/workers/async.rb +1 -1
- data/lib/datadog/di/code_tracker.rb +166 -0
- data/lib/datadog/di/configuration/settings.rb +163 -0
- data/lib/datadog/di/configuration.rb +11 -0
- data/lib/datadog/di/error.rb +31 -0
- data/lib/datadog/di/extensions.rb +16 -0
- data/lib/datadog/di/instrumenter.rb +301 -0
- data/lib/datadog/di/probe.rb +162 -0
- data/lib/datadog/di/probe_builder.rb +47 -0
- data/lib/datadog/di/probe_notification_builder.rb +207 -0
- data/lib/datadog/di/probe_notifier_worker.rb +244 -0
- data/lib/datadog/di/redactor.rb +188 -0
- data/lib/datadog/di/serializer.rb +215 -0
- data/lib/datadog/di/transport.rb +67 -0
- data/lib/datadog/di/utils.rb +39 -0
- data/lib/datadog/di.rb +57 -0
- data/lib/datadog/opentelemetry/sdk/propagator.rb +2 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +12 -10
- data/lib/datadog/profiling/collectors/info.rb +12 -3
- data/lib/datadog/profiling/collectors/thread_context.rb +32 -8
- data/lib/datadog/profiling/component.rb +21 -4
- data/lib/datadog/profiling/http_transport.rb +6 -1
- data/lib/datadog/profiling/scheduler.rb +2 -0
- data/lib/datadog/profiling/stack_recorder.rb +40 -9
- data/lib/datadog/single_step_instrument.rb +12 -0
- data/lib/datadog/tracing/component.rb +13 -0
- data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +8 -12
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +5 -0
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +78 -0
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +33 -0
- data/lib/datadog/tracing/contrib/action_pack/patcher.rb +2 -0
- data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +4 -0
- data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +3 -1
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +3 -1
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +5 -1
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +5 -0
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -0
- data/lib/datadog/tracing/contrib/excon/middleware.rb +3 -0
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +12 -0
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +24 -2
- data/lib/datadog/tracing/contrib/graphql/patcher.rb +9 -12
- data/lib/datadog/tracing/contrib/graphql/trace_patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/graphql/tracing_patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +13 -9
- data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +6 -3
- data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +9 -0
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +22 -15
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +10 -5
- data/lib/datadog/tracing/contrib/httpclient/patcher.rb +1 -14
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +9 -0
- data/lib/datadog/tracing/contrib/httprb/patcher.rb +1 -14
- data/lib/datadog/tracing/contrib/lograge/patcher.rb +1 -2
- data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +2 -0
- data/lib/datadog/tracing/contrib/opensearch/patcher.rb +13 -6
- data/lib/datadog/tracing/contrib/patcher.rb +2 -1
- data/lib/datadog/tracing/contrib/presto/patcher.rb +1 -13
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +27 -0
- data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
- data/lib/datadog/tracing/contrib/redis/tags.rb +4 -0
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +3 -0
- data/lib/datadog/tracing/contrib/sinatra/tracer.rb +4 -0
- data/lib/datadog/tracing/contrib/stripe/request.rb +3 -2
- data/lib/datadog/tracing/distributed/propagation.rb +7 -0
- data/lib/datadog/tracing/metadata/ext.rb +2 -0
- data/lib/datadog/tracing/remote.rb +5 -2
- data/lib/datadog/tracing/sampling/matcher.rb +6 -1
- data/lib/datadog/tracing/sampling/rate_sampler.rb +1 -1
- data/lib/datadog/tracing/sampling/rule.rb +2 -0
- data/lib/datadog/tracing/sampling/rule_sampler.rb +15 -9
- data/lib/datadog/tracing/sampling/span/ext.rb +1 -1
- data/lib/datadog/tracing/sampling/span/rule.rb +2 -2
- data/lib/datadog/tracing/trace_operation.rb +26 -2
- data/lib/datadog/tracing/tracer.rb +29 -22
- data/lib/datadog/tracing/transport/http/client.rb +1 -0
- data/lib/datadog/tracing/transport/http.rb +4 -0
- data/lib/datadog/tracing/transport/io/client.rb +1 -0
- data/lib/datadog/tracing/workers/trace_writer.rb +1 -1
- data/lib/datadog/tracing/workers.rb +2 -2
- data/lib/datadog/tracing/writer.rb +26 -28
- data/lib/datadog/version.rb +1 -1
- metadata +40 -15
- data/lib/datadog/tracing/sampling/rate_limiter.rb +0 -185
@@ -0,0 +1,162 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "error"
|
4
|
+
require_relative "utils"
|
5
|
+
require_relative "../core/rate_limiter"
|
6
|
+
|
7
|
+
module Datadog
|
8
|
+
module DI
|
9
|
+
# Encapsulates probe information (as received via remote config)
|
10
|
+
# and state (e.g. whether the probe was installed, or executed).
|
11
|
+
#
|
12
|
+
# It is possible that remote configuration will specify an unsupported
|
13
|
+
# probe type or attribute, due to new DI functionality being added
|
14
|
+
# over time. We want to have predictable behavior in such cases, and
|
15
|
+
# since we can't guarantee that there will be enough information in
|
16
|
+
# a remote config payload to construct a functional probe, ProbeBuilder
|
17
|
+
# and remote config code must be prepared to deal with exceptions
|
18
|
+
# raised by Probe constructor in particular. Therefore, Probe constructor
|
19
|
+
# will raise an exception if it determines that there is not enough
|
20
|
+
# information (or confilcting information) in the arguments to create a
|
21
|
+
# functional probe, and upstream code is tasked with not spamming logs
|
22
|
+
# with notifications of such errors (and potentially limiting the
|
23
|
+
# attempts to construct probe from a given payload).
|
24
|
+
#
|
25
|
+
# Note that, while remote configuration provides line numbers as an
|
26
|
+
# array, the only supported line number configuration is a single line
|
27
|
+
# (this is the case for all languages currently). Therefore Probe
|
28
|
+
# only supports one line number, and ProbeBuilder is responsible for
|
29
|
+
# extracting that one line number out of the array received from RC.
|
30
|
+
#
|
31
|
+
# Note: only some of the parameter/attribute values are currently validated.
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
class Probe
|
35
|
+
KNOWN_TYPES = %i[log].freeze
|
36
|
+
|
37
|
+
def initialize(id:, type:,
|
38
|
+
file: nil, line_no: nil, type_name: nil, method_name: nil,
|
39
|
+
template: nil, capture_snapshot: false, max_capture_depth: nil, rate_limit: nil)
|
40
|
+
# Perform some sanity checks here to detect unexpected attribute
|
41
|
+
# combinations, in order to not do them in subsequent code.
|
42
|
+
unless KNOWN_TYPES.include?(type)
|
43
|
+
raise ArgumentError, "Unknown probe type: #{type}"
|
44
|
+
end
|
45
|
+
|
46
|
+
if line_no && method_name
|
47
|
+
raise ArgumentError, "Probe contains both line number and method name: #{id}"
|
48
|
+
end
|
49
|
+
|
50
|
+
if type_name && !method_name || method_name && !type_name
|
51
|
+
raise ArgumentError, "Partial method probe definition: #{id}"
|
52
|
+
end
|
53
|
+
|
54
|
+
@id = id
|
55
|
+
@type = type
|
56
|
+
@file = file
|
57
|
+
@line_no = line_no
|
58
|
+
@type_name = type_name
|
59
|
+
@method_name = method_name
|
60
|
+
@template = template
|
61
|
+
@capture_snapshot = !!capture_snapshot
|
62
|
+
@max_capture_depth = max_capture_depth
|
63
|
+
|
64
|
+
# These checks use instance methods that have more complex logic
|
65
|
+
# than checking a single argument value. To avoid duplicating
|
66
|
+
# the logic here, use the methods and perform these checks after
|
67
|
+
# instance variable assignment.
|
68
|
+
unless method? || line?
|
69
|
+
raise ArgumentError, "Unhandled probe type: neither method nor line probe: #{id}"
|
70
|
+
end
|
71
|
+
|
72
|
+
@rate_limit = rate_limit || (@capture_snapshot ? 1 : 5000)
|
73
|
+
@rate_limiter = Datadog::Core::TokenBucket.new(@rate_limit)
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_reader :id
|
77
|
+
attr_reader :type
|
78
|
+
attr_reader :file
|
79
|
+
attr_reader :line_no
|
80
|
+
attr_reader :type_name
|
81
|
+
attr_reader :method_name
|
82
|
+
attr_reader :template
|
83
|
+
|
84
|
+
# Configured maximum capture depth. Can be nil in which case
|
85
|
+
# the global default will be used.
|
86
|
+
attr_reader :max_capture_depth
|
87
|
+
|
88
|
+
# Rate limit in effect, in invocations per second. Always present.
|
89
|
+
attr_reader :rate_limit
|
90
|
+
|
91
|
+
# Rate limiter object. For internal DI use only.
|
92
|
+
attr_reader :rate_limiter
|
93
|
+
|
94
|
+
def capture_snapshot?
|
95
|
+
@capture_snapshot
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns whether the probe is a line probe.
|
99
|
+
#
|
100
|
+
# Method probes may still specify a file name (to aid in locating the
|
101
|
+
# method or for stack traversal purposes?), therefore we do not check
|
102
|
+
# for file name/path presence here and just consider the line number.
|
103
|
+
def line?
|
104
|
+
!line_no.nil?
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns whether the probe is a method probe.
|
108
|
+
def method?
|
109
|
+
!!(type_name && method_name)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns the line number associated with the probe, raising
|
113
|
+
# Error::MissingLineNumber if the probe does not have a line number
|
114
|
+
# associated with it.
|
115
|
+
#
|
116
|
+
# This method is used by instrumentation driver to ensure a line number
|
117
|
+
# that is passed into the instrumentation logic is actually a line number
|
118
|
+
# and not nil.
|
119
|
+
def line_no!
|
120
|
+
if line_no.nil?
|
121
|
+
raise Error::MissingLineNumber, "Probe #{id} does not have a line number associated with it"
|
122
|
+
end
|
123
|
+
line_no
|
124
|
+
end
|
125
|
+
|
126
|
+
# Source code location of the probe, for diagnostic reporting.
|
127
|
+
def location
|
128
|
+
if method?
|
129
|
+
"#{type_name}.#{method_name}"
|
130
|
+
elsif line?
|
131
|
+
"#{file}:#{line_no}"
|
132
|
+
else
|
133
|
+
# This case should not be possible because constructor verifies that
|
134
|
+
# the probe is a method or a line probe.
|
135
|
+
raise NotImplementedError
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns whether the provided +path+ matches the user-designated
|
140
|
+
# file (of a line probe).
|
141
|
+
#
|
142
|
+
# If file is an absolute path (i.e., it starts with a slash), the file
|
143
|
+
# must be identical to path to match.
|
144
|
+
#
|
145
|
+
# If file is not an absolute path, the path matches if the file is its suffix,
|
146
|
+
# at a path component boundary.
|
147
|
+
def file_matches?(path)
|
148
|
+
unless file
|
149
|
+
raise ArgumentError, "Probe does not have a file to match against"
|
150
|
+
end
|
151
|
+
Utils.path_matches_suffix?(path, file)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Instrumentation module for method probes.
|
155
|
+
attr_accessor :instrumentation_module
|
156
|
+
|
157
|
+
# Line trace point for line probes. Normally this would be a targeted
|
158
|
+
# trace point.
|
159
|
+
attr_accessor :instrumentation_trace_point
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "probe"
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
module DI
|
7
|
+
# Creates Probe instances from remote configuration payloads.
|
8
|
+
#
|
9
|
+
# Due to the dynamic instrumentation product evolving over time,
|
10
|
+
# it is possible that the payload corresponds to a type of probe that the
|
11
|
+
# current version of the library does not handle.
|
12
|
+
# For now ArgumentError is raised in such cases (by ProbeBuilder or
|
13
|
+
# Probe constructor), since generally DI is meant to rescue all exceptions
|
14
|
+
# internally and not propagate any exceptions to applications.
|
15
|
+
# A dedicated exception could be added in the future if there is a use case
|
16
|
+
# for it.
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
module ProbeBuilder
|
20
|
+
PROBE_TYPES = {
|
21
|
+
'LOG_PROBE' => :log,
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
module_function def build_from_remote_config(config)
|
25
|
+
# The validations here are not yet comprehensive.
|
26
|
+
type = config.fetch('type')
|
27
|
+
type_symbol = PROBE_TYPES[type] or raise ArgumentError, "Unrecognized probe type: #{type}"
|
28
|
+
Probe.new(
|
29
|
+
id: config.fetch("id"),
|
30
|
+
type: type_symbol,
|
31
|
+
file: config["where"]&.[]("sourceFile"),
|
32
|
+
# Sometimes lines are sometimes received as an array of nil
|
33
|
+
# for some reason.
|
34
|
+
line_no: config["where"]&.[]("lines")&.compact&.map(&:to_i)&.first,
|
35
|
+
type_name: config["where"]&.[]("typeName"),
|
36
|
+
method_name: config["where"]&.[]("methodName"),
|
37
|
+
template: config["template"],
|
38
|
+
capture_snapshot: !!config["captureSnapshot"],
|
39
|
+
max_capture_depth: config["capture"]&.[]("maxReferenceDepth"),
|
40
|
+
rate_limit: config["sampling"]&.[]("snapshotsPerSecond"),
|
41
|
+
)
|
42
|
+
rescue KeyError => exc
|
43
|
+
raise ArgumentError, "Malformed remote configuration entry for probe: #{exc.class}: #{exc}: #{config}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module DI
|
5
|
+
# Builds probe status notification and snapshot payloads.
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class ProbeNotificationBuilder
|
9
|
+
def initialize(settings, serializer)
|
10
|
+
@settings = settings
|
11
|
+
@serializer = serializer
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :settings
|
15
|
+
attr_reader :serializer
|
16
|
+
|
17
|
+
def build_received(probe)
|
18
|
+
build_status(probe,
|
19
|
+
message: "Probe #{probe.id} has been received correctly",
|
20
|
+
status: 'RECEIVED',)
|
21
|
+
end
|
22
|
+
|
23
|
+
def build_installed(probe)
|
24
|
+
build_status(probe,
|
25
|
+
message: "Probe #{probe.id} has been instrumented correctly",
|
26
|
+
status: 'INSTALLED',)
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_emitting(probe)
|
30
|
+
build_status(probe,
|
31
|
+
message: "Probe #{probe.id} is emitting",
|
32
|
+
status: 'EMITTING',)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Duration is in seconds.
|
36
|
+
def build_executed(probe,
|
37
|
+
trace_point: nil, rv: nil, duration: nil, caller_locations: nil,
|
38
|
+
args: nil, kwargs: nil, serialized_entry_args: nil)
|
39
|
+
snapshot = if probe.line? && probe.capture_snapshot?
|
40
|
+
if trace_point.nil?
|
41
|
+
raise "Cannot create snapshot because there is no trace point"
|
42
|
+
end
|
43
|
+
get_local_variables(trace_point)
|
44
|
+
end
|
45
|
+
# TODO check how many stack frames we should be keeping/sending,
|
46
|
+
# this should be all frames for enriched probes and no frames for
|
47
|
+
# non-enriched probes?
|
48
|
+
build_snapshot(probe, rv: rv, snapshot: snapshot,
|
49
|
+
duration: duration, caller_locations: caller_locations, args: args, kwargs: kwargs,
|
50
|
+
serialized_entry_args: serialized_entry_args)
|
51
|
+
end
|
52
|
+
|
53
|
+
def build_snapshot(probe, rv: nil, snapshot: nil,
|
54
|
+
duration: nil, caller_locations: nil, args: nil, kwargs: nil,
|
55
|
+
serialized_entry_args: nil)
|
56
|
+
# TODO also verify that non-capturing probe does not pass
|
57
|
+
# snapshot or vars/args into this method
|
58
|
+
captures = if probe.capture_snapshot?
|
59
|
+
if probe.method?
|
60
|
+
{
|
61
|
+
entry: {
|
62
|
+
# standard:disable all
|
63
|
+
arguments: if serialized_entry_args
|
64
|
+
serialized_entry_args
|
65
|
+
else
|
66
|
+
(args || kwargs) && serializer.serialize_args(args, kwargs)
|
67
|
+
end,
|
68
|
+
throwable: nil,
|
69
|
+
# standard:enable all
|
70
|
+
},
|
71
|
+
return: {
|
72
|
+
arguments: {
|
73
|
+
"@return": serializer.serialize_value(rv),
|
74
|
+
},
|
75
|
+
throwable: nil,
|
76
|
+
},
|
77
|
+
}
|
78
|
+
elsif probe.line?
|
79
|
+
{
|
80
|
+
lines: snapshot && {
|
81
|
+
probe.line_no => {locals: serializer.serialize_vars(snapshot)},
|
82
|
+
},
|
83
|
+
}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
location = if probe.line?
|
88
|
+
actual_file = if probe.file
|
89
|
+
# Normally caller_locations should always be filled for a line probe
|
90
|
+
# but in the test suite we don't always provide all arguments.
|
91
|
+
actual_file_basename = File.basename(probe.file)
|
92
|
+
caller_locations&.detect do |loc|
|
93
|
+
# TODO record actual path that probe was installed into,
|
94
|
+
# perform exact match here against that path.
|
95
|
+
File.basename(loc.path) == actual_file_basename
|
96
|
+
end&.path || probe.file
|
97
|
+
end
|
98
|
+
{
|
99
|
+
file: actual_file,
|
100
|
+
lines: [probe.line_no],
|
101
|
+
}
|
102
|
+
elsif probe.method?
|
103
|
+
{
|
104
|
+
method: probe.method_name,
|
105
|
+
type: probe.type_name,
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
stack = if caller_locations
|
110
|
+
format_caller_locations(caller_locations)
|
111
|
+
end
|
112
|
+
|
113
|
+
timestamp = timestamp_now
|
114
|
+
{
|
115
|
+
service: settings.service,
|
116
|
+
"debugger.snapshot": {
|
117
|
+
id: SecureRandom.uuid,
|
118
|
+
timestamp: timestamp,
|
119
|
+
evaluationErrors: [],
|
120
|
+
probe: {
|
121
|
+
id: probe.id,
|
122
|
+
version: 0,
|
123
|
+
location: location,
|
124
|
+
},
|
125
|
+
language: 'ruby',
|
126
|
+
# TODO add test coverage for callers being nil
|
127
|
+
stack: stack,
|
128
|
+
captures: captures,
|
129
|
+
},
|
130
|
+
# In python tracer duration is under debugger.snapshot,
|
131
|
+
# but UI appears to expect it here at top level.
|
132
|
+
duration: duration ? (duration * 10**9).to_i : nil,
|
133
|
+
host: nil,
|
134
|
+
logger: {
|
135
|
+
name: probe.file,
|
136
|
+
method: probe.method_name || 'no_method',
|
137
|
+
thread_name: Thread.current.name,
|
138
|
+
# Dynamic instrumentation currently does not need thread_id for
|
139
|
+
# anything. It can be sent if a customer requests it at which point
|
140
|
+
# we can also determine which thread identifier to send
|
141
|
+
# (Thread#native_thread_id or something else).
|
142
|
+
thread_id: nil,
|
143
|
+
version: 2,
|
144
|
+
},
|
145
|
+
# TODO add tests that the trace/span id is correctly propagated
|
146
|
+
"dd.trace_id": Datadog::Tracing.active_trace&.id,
|
147
|
+
"dd.span_id": Datadog::Tracing.active_span&.id,
|
148
|
+
ddsource: 'dd_debugger',
|
149
|
+
message: probe.template && evaluate_template(probe.template,
|
150
|
+
duration: duration ? duration * 1000 : nil),
|
151
|
+
timestamp: timestamp,
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
def build_status(probe, message:, status:)
|
156
|
+
{
|
157
|
+
service: settings.service,
|
158
|
+
timestamp: timestamp_now,
|
159
|
+
message: message,
|
160
|
+
ddsource: 'dd_debugger',
|
161
|
+
debugger: {
|
162
|
+
diagnostics: {
|
163
|
+
probeId: probe.id,
|
164
|
+
probeVersion: 0,
|
165
|
+
runtimeId: Core::Environment::Identity.id,
|
166
|
+
parentId: nil,
|
167
|
+
status: status,
|
168
|
+
},
|
169
|
+
},
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
def format_caller_locations(caller_locations)
|
174
|
+
caller_locations.map do |loc|
|
175
|
+
{fileName: loc.path, function: loc.label, lineNumber: loc.lineno}
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def evaluate_template(template, **vars)
|
180
|
+
message = template.dup
|
181
|
+
vars.each do |key, value|
|
182
|
+
message.gsub!("{@#{key}}", value.to_s)
|
183
|
+
end
|
184
|
+
message
|
185
|
+
end
|
186
|
+
|
187
|
+
def timestamp_now
|
188
|
+
(Time.now.to_f * 1000).to_i
|
189
|
+
end
|
190
|
+
|
191
|
+
def get_local_variables(trace_point)
|
192
|
+
# binding appears to be constructed on access, therefore
|
193
|
+
# 1) we should attempt to cache it and
|
194
|
+
# 2) we should not call +binding+ until we actually need variable values.
|
195
|
+
binding = trace_point.binding
|
196
|
+
|
197
|
+
# steep hack - should never happen
|
198
|
+
return {} unless binding
|
199
|
+
|
200
|
+
binding.local_variables.each_with_object({}) do |name, map|
|
201
|
+
value = binding.local_variable_get(name)
|
202
|
+
map[name] = value
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../core/semaphore'
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
module DI
|
7
|
+
# Background worker thread for sending probe statuses and snapshots
|
8
|
+
# to the backend (via the agent).
|
9
|
+
#
|
10
|
+
# The loop inside the worker rescues all exceptions to prevent termination
|
11
|
+
# due to unhandled exceptions raised by any downstream code.
|
12
|
+
# This includes communication and protocol errors when sending the
|
13
|
+
# payloads to the agent.
|
14
|
+
#
|
15
|
+
# The worker groups the data to send into batches. The goal is to perform
|
16
|
+
# no more than one network operation per event type per second.
|
17
|
+
# There is also a limit on the length of the sending queue to prevent
|
18
|
+
# it from growing without bounds if upstream code generates an enormous
|
19
|
+
# number of events for some reason.
|
20
|
+
#
|
21
|
+
# Wake-up events are used (via ConditionVariable) to keep the thread
|
22
|
+
# asleep if there is no work to be done.
|
23
|
+
#
|
24
|
+
# @api private
|
25
|
+
class ProbeNotifierWorker
|
26
|
+
# Minimum interval between submissions.
|
27
|
+
# TODO make this into an internal setting and increase default to 2 or 3.
|
28
|
+
MIN_SEND_INTERVAL = 1
|
29
|
+
|
30
|
+
def initialize(settings, transport, logger)
|
31
|
+
@settings = settings
|
32
|
+
@status_queue = []
|
33
|
+
@snapshot_queue = []
|
34
|
+
@transport = transport
|
35
|
+
@logger = logger
|
36
|
+
@lock = Mutex.new
|
37
|
+
@wake = Core::Semaphore.new
|
38
|
+
@io_in_progress = false
|
39
|
+
@sleep_remaining = nil
|
40
|
+
@wake_scheduled = false
|
41
|
+
@thread = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_reader :settings
|
45
|
+
attr_reader :logger
|
46
|
+
|
47
|
+
def start
|
48
|
+
return if @thread
|
49
|
+
@thread = Thread.new do
|
50
|
+
loop do
|
51
|
+
# TODO If stop is requested, we stop immediately without
|
52
|
+
# flushing the submissions. Should we send pending submissions
|
53
|
+
# and then quit?
|
54
|
+
break if @stop_requested
|
55
|
+
|
56
|
+
sleep_remaining = @lock.synchronize do
|
57
|
+
if sleep_remaining && sleep_remaining > 0
|
58
|
+
# Recalculate how much sleep time is remaining, then sleep that long.
|
59
|
+
set_sleep_remaining
|
60
|
+
else
|
61
|
+
0
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
if sleep_remaining > 0
|
66
|
+
# Do not need to update @wake_scheduled here because
|
67
|
+
# wake-up is already scheduled for the earliest possible time.
|
68
|
+
wake.wait(sleep_remaining)
|
69
|
+
next
|
70
|
+
end
|
71
|
+
|
72
|
+
begin
|
73
|
+
more = maybe_send
|
74
|
+
rescue => exc
|
75
|
+
raise if settings.dynamic_instrumentation.propagate_all_exceptions
|
76
|
+
|
77
|
+
logger.warn("Error in probe notifier worker: #{exc.class}: #{exc} (at #{exc.backtrace.first})")
|
78
|
+
end
|
79
|
+
@lock.synchronize do
|
80
|
+
@wake_scheduled = more
|
81
|
+
end
|
82
|
+
wake.wait(more ? MIN_SEND_INTERVAL : nil)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Stops the background thread.
|
88
|
+
#
|
89
|
+
# Attempts a graceful stop with the specified timeout, then falls back
|
90
|
+
# to killing the thread using Thread#kill.
|
91
|
+
def stop(timeout = 1)
|
92
|
+
@stop_requested = true
|
93
|
+
wake.signal
|
94
|
+
if thread
|
95
|
+
unless thread.join(timeout)
|
96
|
+
thread.kill
|
97
|
+
end
|
98
|
+
@thread = nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Waits for background thread to send pending notifications.
|
103
|
+
#
|
104
|
+
# This method waits for the notification queue to become empty
|
105
|
+
# rather than for a particular set of notifications to be sent out,
|
106
|
+
# therefore, it should only be called when there is no parallel
|
107
|
+
# activity (in another thread) that causes more notifications
|
108
|
+
# to be generated.
|
109
|
+
def flush
|
110
|
+
loop do
|
111
|
+
if @thread.nil? || !@thread.alive?
|
112
|
+
return
|
113
|
+
end
|
114
|
+
|
115
|
+
io_in_progress, queues_empty = @lock.synchronize do
|
116
|
+
[io_in_progress?, status_queue.empty? && snapshot_queue.empty?]
|
117
|
+
end
|
118
|
+
|
119
|
+
if io_in_progress
|
120
|
+
# If we just call Thread.pass we could be in a busy loop -
|
121
|
+
# add a sleep.
|
122
|
+
sleep 0.25
|
123
|
+
next
|
124
|
+
elsif queues_empty
|
125
|
+
break
|
126
|
+
else
|
127
|
+
sleep 0.25
|
128
|
+
next
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
attr_reader :transport
|
136
|
+
attr_reader :wake
|
137
|
+
attr_reader :thread
|
138
|
+
|
139
|
+
# This method should be called while @lock is held.
|
140
|
+
def io_in_progress?
|
141
|
+
@io_in_progress
|
142
|
+
end
|
143
|
+
|
144
|
+
attr_reader :last_sent
|
145
|
+
|
146
|
+
[
|
147
|
+
[:status, 'probe status'],
|
148
|
+
[:snapshot, 'snapshot'],
|
149
|
+
].each do |(event_type, event_name)|
|
150
|
+
attr_reader "#{event_type}_queue"
|
151
|
+
|
152
|
+
# Adds a status or a snapshot to the queue to be sent to the agent
|
153
|
+
# at the next opportunity.
|
154
|
+
#
|
155
|
+
# If the queue is too large, the event will not be added.
|
156
|
+
#
|
157
|
+
# Signals the background thread to wake up (and do the sending)
|
158
|
+
# if it has been more than 1 second since the last send of the same
|
159
|
+
# event type.
|
160
|
+
define_method("add_#{event_type}") do |event|
|
161
|
+
@lock.synchronize do
|
162
|
+
queue = send("#{event_type}_queue")
|
163
|
+
# TODO determine a suitable limit via testing/benchmarking
|
164
|
+
if queue.length > 100
|
165
|
+
logger.warn("#{self.class.name}: dropping #{event_type} because queue is full")
|
166
|
+
else
|
167
|
+
queue << event
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Figure out whether to wake up the worker thread.
|
172
|
+
# If minimum send interval has elapsed since the last send,
|
173
|
+
# wake up immediately.
|
174
|
+
@lock.synchronize do
|
175
|
+
unless @wake_scheduled
|
176
|
+
@wake_scheduled = true
|
177
|
+
set_sleep_remaining
|
178
|
+
wake.signal
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Determine how much longer the worker thread should sleep
|
184
|
+
# so as not to send in less than MIN_SEND_INTERVAL since the last send.
|
185
|
+
# Important: this method must be called when @lock is held.
|
186
|
+
#
|
187
|
+
# Returns the time remaining to sleep.
|
188
|
+
def set_sleep_remaining
|
189
|
+
now = Core::Utils::Time.get_time
|
190
|
+
@sleep_remaining = if last_sent
|
191
|
+
[last_sent + MIN_SEND_INTERVAL - now, 0].max
|
192
|
+
else
|
193
|
+
0
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
public "add_#{event_type}"
|
198
|
+
|
199
|
+
# Sends pending probe statuses or snapshots.
|
200
|
+
#
|
201
|
+
# This method should ideally only be called when there are actually
|
202
|
+
# events to send, but it can be called when there is nothing to do.
|
203
|
+
# Currently we only have one wake-up signaling object and two
|
204
|
+
# types of events. Therefore on most wake-ups we expect to only
|
205
|
+
# send one type of events.
|
206
|
+
define_method("maybe_send_#{event_type}") do
|
207
|
+
batch = nil
|
208
|
+
@lock.synchronize do
|
209
|
+
batch = instance_variable_get("@#{event_type}_queue")
|
210
|
+
instance_variable_set("@#{event_type}_queue", [])
|
211
|
+
@io_in_progress = batch.any? # steep:ignore
|
212
|
+
end
|
213
|
+
if batch.any? # steep:ignore
|
214
|
+
begin
|
215
|
+
transport.public_send("send_#{event_type}", batch)
|
216
|
+
time = Core::Utils::Time.get_time
|
217
|
+
@lock.synchronize do
|
218
|
+
@last_sent = time
|
219
|
+
end
|
220
|
+
rescue => exc
|
221
|
+
raise if settings.dynamic_instrumentation.propagate_all_exceptions
|
222
|
+
logger.warn("failed to send #{event_name}: #{exc.class}: #{exc} (at #{exc.backtrace.first})")
|
223
|
+
end
|
224
|
+
end
|
225
|
+
batch.any? # steep:ignore
|
226
|
+
rescue ThreadError
|
227
|
+
# Normally the queue should only be consumed in this method,
|
228
|
+
# however if anyone consumes it elsewhere we don't want to block
|
229
|
+
# while consuming it here. Rescue ThreadError and return.
|
230
|
+
logger.warn("unexpected #{event_name} queue underflow - consumed elsewhere?")
|
231
|
+
ensure
|
232
|
+
@lock.synchronize do
|
233
|
+
@io_in_progress = false
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def maybe_send
|
239
|
+
rv = maybe_send_status
|
240
|
+
rv || maybe_send_snapshot
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|