datadog 2.39.0 → 2.40.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 +27 -1
- data/ext/datadog_profiling_native_extension/collectors_stack.c +218 -147
- data/ext/datadog_profiling_native_extension/collectors_stack.h +16 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +12 -7
- data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +5 -4
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +19 -11
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +7 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +12 -7
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +259 -65
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +16 -10
- data/ext/libdatadog_api/datadog_ruby_common.c +19 -11
- data/ext/libdatadog_api/datadog_ruby_common.h +7 -0
- data/ext/libdatadog_api/init.c +4 -0
- data/ext/libdatadog_api/trace_exporter.c +974 -0
- data/ext/libdatadog_api/trace_exporter.h +5 -0
- data/lib/datadog/ai_guard/evaluation.rb +1 -0
- data/lib/datadog/ai_guard/ext.rb +2 -0
- data/lib/datadog/appsec/configuration.rb +9 -0
- data/lib/datadog/appsec/contrib/devise/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +6 -3
- data/lib/datadog/appsec/contrib/rails/request_middleware.rb +2 -1
- data/lib/datadog/appsec/contrib/sinatra/request_middleware.rb +2 -1
- data/lib/datadog/core/configuration/settings.rb +33 -41
- data/lib/datadog/core/configuration/supported_configurations.rb +3 -0
- data/lib/datadog/core/environment/gc.rb +1 -1
- data/lib/datadog/core/environment/vm_cache.rb +1 -1
- data/lib/datadog/core/environment/yjit.rb +2 -2
- data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +1 -1
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +80 -12
- data/lib/datadog/core/utils/sequence.rb +3 -6
- data/lib/datadog/core/utils/time.rb +16 -54
- data/lib/datadog/di/el/compiler.rb +65 -12
- data/lib/datadog/di/el/evaluator.rb +82 -2
- data/lib/datadog/di/el/expression.rb +10 -2
- data/lib/datadog/di/probe_builder.rb +6 -6
- data/lib/datadog/profiling/collectors/thread_context.rb +6 -13
- data/lib/datadog/profiling/component.rb +1 -0
- data/lib/datadog/profiling.rb +4 -0
- data/lib/datadog/tracing/component.rb +21 -0
- data/lib/datadog/tracing/configuration/ext.rb +1 -0
- data/lib/datadog/tracing/configuration/settings.rb +16 -0
- data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +2 -1
- data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +4 -0
- data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +7 -2
- data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +7 -2
- data/lib/datadog/tracing/contrib/sequel/database.rb +1 -5
- data/lib/datadog/tracing/distributed/propagation_policy.rb +5 -0
- data/lib/datadog/tracing/sync_writer.rb +8 -3
- data/lib/datadog/tracing/tracer.rb +8 -0
- data/lib/datadog/tracing/transport/native/response.rb +71 -0
- data/lib/datadog/tracing/transport/native.rb +363 -0
- data/lib/datadog/tracing/transport/trace_formatter.rb +1 -1
- data/lib/datadog/tracing/writer.rb +16 -3
- data/lib/datadog/version.rb +1 -1
- metadata +9 -5
|
@@ -16,8 +16,19 @@ module Datadog
|
|
|
16
16
|
#
|
|
17
17
|
# @api private
|
|
18
18
|
class Compiler
|
|
19
|
+
# Compiles +ast+ into eval'able Ruby source.
|
|
20
|
+
#
|
|
21
|
+
# Returns the compiled source and companion compiled Regexp
|
|
22
|
+
# objects.
|
|
23
|
+
#
|
|
24
|
+
# @param ast [untyped] expression AST from the probe definition.
|
|
25
|
+
# @return [Array(String, Array<Regexp>)] the compiled Ruby source and
|
|
26
|
+
# the precompiled Regexps, indexed in the order the compiled code
|
|
27
|
+
# references them.
|
|
19
28
|
def compile(ast)
|
|
20
|
-
|
|
29
|
+
regexps = []
|
|
30
|
+
code = compile_partial(ast, regexps)
|
|
31
|
+
[code, regexps]
|
|
21
32
|
end
|
|
22
33
|
|
|
23
34
|
private
|
|
@@ -37,9 +48,13 @@ module Datadog
|
|
|
37
48
|
len isEmpty isUndefined
|
|
38
49
|
].freeze # steep:ignore IncompatibleAssignment
|
|
39
50
|
|
|
51
|
+
# `matches` is also a two-argument method but is special-cased in
|
|
52
|
+
# #compile_partial so that regular expressions can be precompiled
|
|
53
|
+
# once, so it is not listed here.
|
|
54
|
+
#
|
|
40
55
|
# Steep: https://github.com/soutaro/steep/issues/363
|
|
41
56
|
TWO_ARG_METHODS = %w[
|
|
42
|
-
startsWith endsWith contains
|
|
57
|
+
startsWith endsWith contains
|
|
43
58
|
getmember index instanceof
|
|
44
59
|
].freeze # steep:ignore IncompatibleAssignment
|
|
45
60
|
|
|
@@ -49,13 +64,18 @@ module Datadog
|
|
|
49
64
|
"or" => "||",
|
|
50
65
|
}.freeze
|
|
51
66
|
|
|
52
|
-
|
|
67
|
+
# @param ast [untyped] AST node to compile.
|
|
68
|
+
# @param regexps [Array<Regexp>] output array that collects the
|
|
69
|
+
# companion precompiled Regexp objects.
|
|
70
|
+
# @return [String] compiled Ruby source for +ast+.
|
|
71
|
+
def compile_partial(ast, regexps)
|
|
53
72
|
case ast
|
|
54
73
|
when Hash
|
|
55
|
-
|
|
74
|
+
entry = ast.first
|
|
75
|
+
if ast.length != 1 || entry.nil?
|
|
56
76
|
raise DI::Error::InvalidExpression, "Expected hash of length 1: #{ast}"
|
|
57
77
|
end
|
|
58
|
-
op, target =
|
|
78
|
+
op, target = entry
|
|
59
79
|
case op
|
|
60
80
|
when "ref"
|
|
61
81
|
unless String === target
|
|
@@ -94,20 +114,33 @@ module Datadog
|
|
|
94
114
|
end
|
|
95
115
|
when *SINGLE_ARG_METHODS
|
|
96
116
|
method_name = op.gsub(/[A-Z]/) { |m| "_#{m.downcase}" }
|
|
97
|
-
"#{method_name}(#{compile_partial(target)}, '#{var_name_maybe(target)}')"
|
|
117
|
+
"#{method_name}(#{compile_partial(target, regexps)}, '#{var_name_maybe(target)}')"
|
|
118
|
+
when "matches"
|
|
119
|
+
unless Array === target && target.length == 2
|
|
120
|
+
raise DI::Error::InvalidExpression, "Improper matches syntax"
|
|
121
|
+
end
|
|
122
|
+
first, second = target
|
|
123
|
+
if String === second
|
|
124
|
+
# Match against a literal regular expression (string).
|
|
125
|
+
index = precompile_regexp(second, regexps)
|
|
126
|
+
"matches_compiled(#{compile_partial(first, regexps)}, #{index})"
|
|
127
|
+
else
|
|
128
|
+
# Match against a (complex) expression.
|
|
129
|
+
"matches(#{compile_partial(first, regexps)}, (#{compile_partial(second, regexps)}))"
|
|
130
|
+
end
|
|
98
131
|
when *TWO_ARG_METHODS
|
|
99
132
|
method_name = op.gsub(/[A-Z]/) { |m| "_#{m.downcase}" }
|
|
100
133
|
unless Array === target && target.length == 2
|
|
101
134
|
raise DI::Error::InvalidExpression, "Improper #{op} syntax"
|
|
102
135
|
end
|
|
103
136
|
first, second = target
|
|
104
|
-
"#{method_name}(#{compile_partial(first)}, (#{compile_partial(second)}))"
|
|
137
|
+
"#{method_name}(#{compile_partial(first, regexps)}, (#{compile_partial(second, regexps)}))"
|
|
105
138
|
when *MULTI_ARG_METHODS.keys
|
|
106
139
|
unless Array === target && target.length >= 1
|
|
107
140
|
raise DI::Error::InvalidExpression, "Improper #{op} syntax"
|
|
108
141
|
end
|
|
109
142
|
compiled_targets = target.map do |item|
|
|
110
|
-
"(#{compile_partial(item)})"
|
|
143
|
+
"(#{compile_partial(item, regexps)})"
|
|
111
144
|
end
|
|
112
145
|
compiled_op = MULTI_ARG_METHODS[op]
|
|
113
146
|
"(#{compiled_targets.join(" #{compiled_op} ")})"
|
|
@@ -115,18 +148,21 @@ module Datadog
|
|
|
115
148
|
unless Array === target && target.length == 3
|
|
116
149
|
raise DI::Error::InvalidExpression, "Improper #{op} syntax"
|
|
117
150
|
end
|
|
118
|
-
"#{op}(#{target.map { |arg| "(#{compile_partial(arg)})" }.join(", ")})"
|
|
151
|
+
"#{op}(#{target.map { |arg| "(#{compile_partial(arg, regexps)})" }.join(", ")})"
|
|
119
152
|
when "not"
|
|
120
|
-
"!(#{compile_partial(target)})"
|
|
153
|
+
"!(#{compile_partial(target, regexps)})"
|
|
121
154
|
when *OPERATORS.keys
|
|
122
155
|
unless Array === target && target.length == 2
|
|
123
156
|
raise DI::Error::InvalidExpression, "Improper #{op} syntax"
|
|
124
157
|
end
|
|
125
158
|
first, second = target
|
|
126
159
|
operator = OPERATORS.fetch(op)
|
|
127
|
-
"(#{compile_partial(first)}) #{operator} (#{compile_partial(second)})"
|
|
160
|
+
"(#{compile_partial(first, regexps)}) #{operator} (#{compile_partial(second, regexps)})"
|
|
128
161
|
when "any", "all", "filter"
|
|
129
|
-
|
|
162
|
+
unless Array === target && target.length == 2
|
|
163
|
+
raise DI::Error::InvalidExpression, "Improper #{op} syntax"
|
|
164
|
+
end
|
|
165
|
+
"#{op}(#{compile_partial(target.first, regexps)}) { |current_item, current_key, current_value| #{compile_partial(target.last, regexps)} }"
|
|
130
166
|
else
|
|
131
167
|
raise DI::Error::InvalidExpression, "Unknown operation: #{op}"
|
|
132
168
|
end
|
|
@@ -162,6 +198,23 @@ module Datadog
|
|
|
162
198
|
def escape(needle)
|
|
163
199
|
needle.gsub("\\") { "\\\\" }.gsub('"') { "\\\"" }.gsub("#") { "\\#" }
|
|
164
200
|
end
|
|
201
|
+
|
|
202
|
+
# Compile a literal regular expression +regexp_str+ at
|
|
203
|
+
# instrumentation time. Append it to +regexps+, returning its index for
|
|
204
|
+
# Evaluator#matches_compiled to look up.
|
|
205
|
+
#
|
|
206
|
+
# @param regexp_str [String] regular expression source.
|
|
207
|
+
# @param regexps [Array<Regexp>] output array to append the compiled
|
|
208
|
+
# regexp to.
|
|
209
|
+
# @return [Integer] index into +regexps+.
|
|
210
|
+
# @raise [DI::Error::InvalidExpression] if +needle+ is not a valid regexp.
|
|
211
|
+
def precompile_regexp(regexp_str, regexps)
|
|
212
|
+
index = regexps.length
|
|
213
|
+
regexps << Evaluator.compile_regexp(regexp_str)
|
|
214
|
+
index
|
|
215
|
+
rescue RegexpError => exc
|
|
216
|
+
raise DI::Error::InvalidExpression, "Invalid regular expression in matches: #{exc.class}: #{exc.message}"
|
|
217
|
+
end
|
|
165
218
|
end
|
|
166
219
|
end
|
|
167
220
|
end
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../../ruby_version"
|
|
4
|
+
|
|
5
|
+
require "timeout" if Datadog::RubyVersion.is?("< 3.2")
|
|
6
|
+
|
|
3
7
|
module Datadog
|
|
4
8
|
module DI
|
|
5
9
|
module EL
|
|
@@ -7,6 +11,20 @@ module Datadog
|
|
|
7
11
|
#
|
|
8
12
|
# @api private
|
|
9
13
|
class Evaluator
|
|
14
|
+
# Maximum wall-clock time allowed for evaluating a single `matches`
|
|
15
|
+
# operator.
|
|
16
|
+
MATCHES_TIMEOUT_SECONDS = 0.5
|
|
17
|
+
|
|
18
|
+
# @param regexps [Array<Regexp>] Regexps precompiled from literal
|
|
19
|
+
# `matches` patterns, looked up by index by #matches_compiled.
|
|
20
|
+
# Empty when the expression has no `matches` pattern with
|
|
21
|
+
# a direct regular expression argument.
|
|
22
|
+
def initialize(regexps = [])
|
|
23
|
+
@regexps = regexps
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
attr_reader :regexps
|
|
27
|
+
|
|
10
28
|
def ref(var)
|
|
11
29
|
@context.fetch(var)
|
|
12
30
|
end
|
|
@@ -48,10 +66,72 @@ module Datadog
|
|
|
48
66
|
end
|
|
49
67
|
end
|
|
50
68
|
|
|
69
|
+
# Build a Regexp from a pattern string. On Ruby 3.2+ the per-match
|
|
70
|
+
# timeout (MATCHES_TIMEOUT_SECONDS) is baked into the compiled
|
|
71
|
+
# Regexp via the in-engine `timeout:` keyword, which interrupts the
|
|
72
|
+
# matcher at every backtrack step and reliably bounds matcher
|
|
73
|
+
# runtime regardless of pattern shape. On older Rubies the Regexp
|
|
74
|
+
# carries no timeout; the bound is applied at match time by
|
|
75
|
+
# #bounded_match? instead.
|
|
76
|
+
#
|
|
77
|
+
# @param needle [String] regexp source.
|
|
78
|
+
# @return [Regexp] compiled regexp, with baked-in timeout on Ruby 3.2+.
|
|
79
|
+
if Datadog::RubyVersion.is?(">= 3.2")
|
|
80
|
+
def self.compile_regexp(needle)
|
|
81
|
+
Regexp.new(needle, timeout: MATCHES_TIMEOUT_SECONDS)
|
|
82
|
+
end
|
|
83
|
+
else
|
|
84
|
+
def self.compile_regexp(needle)
|
|
85
|
+
Regexp.compile(needle)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Match +haystack+ against a regexp whose needle is computed at
|
|
90
|
+
# evaluation time, so the Regexp cannot be precompiled. Literal needles are
|
|
91
|
+
# precompiled by the Compiler and dispatched to #matches_compiled.
|
|
92
|
+
#
|
|
93
|
+
# @param haystack [String] string to match against.
|
|
94
|
+
# @param needle [String] regexp source.
|
|
95
|
+
# @return [Boolean] whether the haystack matches the regexp.
|
|
96
|
+
# @raise [Regexp::TimeoutError] (Ruby 3.2+) regexp engine exceeded MATCHES_TIMEOUT_SECONDS.
|
|
97
|
+
# @raise [Timeout::Error] (Ruby < 3.2) Timeout.timeout fired.
|
|
51
98
|
def matches(haystack, needle)
|
|
52
|
-
|
|
53
|
-
|
|
99
|
+
bounded_match?(Evaluator.compile_regexp(needle), haystack)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Match +haystack+ against a Regexp precompiled at expression-compile
|
|
103
|
+
# time, looked up by index in +regexps+.
|
|
104
|
+
#
|
|
105
|
+
# @param haystack [String] string to match against.
|
|
106
|
+
# @param index [Integer] position of the precompiled Regexp in +regexps+.
|
|
107
|
+
# @return [Boolean] whether the haystack matches the regexp.
|
|
108
|
+
# @raise [Regexp::TimeoutError] (Ruby 3.2+) regexp engine exceeded MATCHES_TIMEOUT_SECONDS.
|
|
109
|
+
# @raise [Timeout::Error] (Ruby < 3.2) Timeout.timeout fired.
|
|
110
|
+
def matches_compiled(haystack, index)
|
|
111
|
+
bounded_match?(regexps.fetch(index), haystack)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Match +haystack+ against +re+, bounded at MATCHES_TIMEOUT_SECONDS
|
|
115
|
+
# wall-clock.
|
|
116
|
+
#
|
|
117
|
+
# @param re [Regexp] regexp to match against.
|
|
118
|
+
# @param haystack [String] string to match against.
|
|
119
|
+
# @return [Boolean] whether the haystack matches the regexp.
|
|
120
|
+
if Datadog::RubyVersion.is?(">= 3.2")
|
|
121
|
+
def bounded_match?(re, haystack)
|
|
122
|
+
# Uses Regexp#match? rather than =~ so that the
|
|
123
|
+
# thread-local match data ($~, $1, ...) is not mutated as a side
|
|
124
|
+
# effect of DI expression evaluation.
|
|
125
|
+
re.match?(haystack)
|
|
126
|
+
end
|
|
127
|
+
else
|
|
128
|
+
def bounded_match?(re, haystack)
|
|
129
|
+
Timeout.timeout(MATCHES_TIMEOUT_SECONDS) do
|
|
130
|
+
re.match?(haystack)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
54
133
|
end
|
|
134
|
+
private :bounded_match?
|
|
55
135
|
|
|
56
136
|
def getmember(object, field)
|
|
57
137
|
object.instance_variable_get("@#{field}")
|
|
@@ -7,7 +7,12 @@ module Datadog
|
|
|
7
7
|
#
|
|
8
8
|
# @api private
|
|
9
9
|
class Expression
|
|
10
|
-
|
|
10
|
+
# @param dsl_expr [String] human-readable DSL form, kept for debugging.
|
|
11
|
+
# @param compiled_expr [String] Ruby source produced by Compiler#compile.
|
|
12
|
+
# @param regexps [Array<Regexp>] precompiled `matches` regexps (see
|
|
13
|
+
# Compiler#precompile_regexp), the second element returned by
|
|
14
|
+
# Compiler#compile.
|
|
15
|
+
def initialize(dsl_expr, compiled_expr, regexps = [])
|
|
11
16
|
unless String === compiled_expr
|
|
12
17
|
raise ArgumentError, "compiled_expr must be a string"
|
|
13
18
|
end
|
|
@@ -23,7 +28,10 @@ module Datadog
|
|
|
23
28
|
end
|
|
24
29
|
RUBY
|
|
25
30
|
end
|
|
26
|
-
|
|
31
|
+
# cls inherits Evaluator#initialize(regexps), but Steep types
|
|
32
|
+
# Class#new as () -> untyped and cannot see that initializer
|
|
33
|
+
# through this dynamically created subclass.
|
|
34
|
+
@evaluator = cls.new(regexps) # steep:ignore UnexpectedPositionalArgument
|
|
27
35
|
end
|
|
28
36
|
|
|
29
37
|
attr_reader :dsl_expr
|
|
@@ -45,8 +45,8 @@ module Datadog
|
|
|
45
45
|
unless cond_spec["dsl"] && cond_spec["json"]
|
|
46
46
|
raise ArgumentError, "Malformed condition specification for probe: #{config}"
|
|
47
47
|
end
|
|
48
|
-
compiled = EL::Compiler.new.compile(cond_spec["json"])
|
|
49
|
-
EL::Expression.new(cond_spec["dsl"], compiled)
|
|
48
|
+
compiled, regexps = EL::Compiler.new.compile(cond_spec["json"])
|
|
49
|
+
EL::Expression.new(cond_spec["dsl"], compiled, regexps)
|
|
50
50
|
end
|
|
51
51
|
capture_expressions = build_capture_expressions(config["captureExpressions"])
|
|
52
52
|
capture_expressions = dedup_capture_expressions(capture_expressions, config["id"], logger)
|
|
@@ -100,8 +100,8 @@ module Datadog
|
|
|
100
100
|
unless Hash === expr_spec && expr_spec["dsl"] && expr_spec["json"]
|
|
101
101
|
raise ArgumentError, "captureExpressions entry #{name}: missing or malformed expr"
|
|
102
102
|
end
|
|
103
|
-
compiled = EL::Compiler.new.compile(expr_spec["json"])
|
|
104
|
-
expr = EL::Expression.new(expr_spec["dsl"], compiled)
|
|
103
|
+
compiled, regexps = EL::Compiler.new.compile(expr_spec["json"])
|
|
104
|
+
expr = EL::Expression.new(expr_spec["dsl"], compiled, regexps)
|
|
105
105
|
limits = build_capture_limits(entry["capture"])
|
|
106
106
|
CaptureExpression.new(name: name, expr: expr, limits: limits)
|
|
107
107
|
end
|
|
@@ -152,8 +152,8 @@ module Datadog
|
|
|
152
152
|
unless dsl = segment["dsl"]
|
|
153
153
|
raise ArgumentError, "Missing dsl for json in segment: #{segment}"
|
|
154
154
|
end
|
|
155
|
-
compiled = EL::Compiler.new.compile(ast)
|
|
156
|
-
EL::Expression.new(dsl, compiled)
|
|
155
|
+
compiled, regexps = EL::Compiler.new.compile(ast)
|
|
156
|
+
EL::Expression.new(dsl, compiled, regexps)
|
|
157
157
|
else
|
|
158
158
|
# TODO report to telemetry?
|
|
159
159
|
end
|
|
@@ -21,7 +21,8 @@ module Datadog
|
|
|
21
21
|
endpoint_collection_enabled:,
|
|
22
22
|
waiting_for_gvl_threshold_ns:,
|
|
23
23
|
otel_context_enabled:,
|
|
24
|
-
native_filenames_enabled
|
|
24
|
+
native_filenames_enabled:,
|
|
25
|
+
show_classes:
|
|
25
26
|
)
|
|
26
27
|
tracer_context_key = safely_extract_context_key_from(tracer)
|
|
27
28
|
self.class._native_initialize(
|
|
@@ -32,7 +33,8 @@ module Datadog
|
|
|
32
33
|
endpoint_collection_enabled: endpoint_collection_enabled,
|
|
33
34
|
waiting_for_gvl_threshold_ns: waiting_for_gvl_threshold_ns,
|
|
34
35
|
otel_context_enabled: otel_context_enabled,
|
|
35
|
-
native_filenames_enabled:
|
|
36
|
+
native_filenames_enabled: native_filenames_enabled,
|
|
37
|
+
show_classes: show_classes,
|
|
36
38
|
overhead_filename: __FILE__,
|
|
37
39
|
)
|
|
38
40
|
end
|
|
@@ -45,6 +47,7 @@ module Datadog
|
|
|
45
47
|
waiting_for_gvl_threshold_ns: 10_000_000,
|
|
46
48
|
otel_context_enabled: false,
|
|
47
49
|
native_filenames_enabled: true,
|
|
50
|
+
show_classes: false,
|
|
48
51
|
trigger_global_reset: true,
|
|
49
52
|
**options
|
|
50
53
|
)
|
|
@@ -56,6 +59,7 @@ module Datadog
|
|
|
56
59
|
waiting_for_gvl_threshold_ns: waiting_for_gvl_threshold_ns,
|
|
57
60
|
otel_context_enabled: otel_context_enabled,
|
|
58
61
|
native_filenames_enabled: native_filenames_enabled,
|
|
62
|
+
show_classes: show_classes,
|
|
59
63
|
**options,
|
|
60
64
|
)
|
|
61
65
|
|
|
@@ -90,17 +94,6 @@ module Datadog
|
|
|
90
94
|
context = provider.instance_variable_get(:@context)
|
|
91
95
|
context&.instance_variable_get(:@key)
|
|
92
96
|
end
|
|
93
|
-
|
|
94
|
-
def validate_native_filenames(native_filenames_enabled)
|
|
95
|
-
if native_filenames_enabled && !Datadog::Profiling::Collectors::Stack._native_filenames_available?
|
|
96
|
-
Datadog.logger.debug(
|
|
97
|
-
"Native filenames are enabled, but the required dladdr API was not available. Disabling native filenames."
|
|
98
|
-
)
|
|
99
|
-
false
|
|
100
|
-
else
|
|
101
|
-
native_filenames_enabled
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
97
|
end
|
|
105
98
|
end
|
|
106
99
|
end
|
|
@@ -105,6 +105,7 @@ module Datadog
|
|
|
105
105
|
waiting_for_gvl_threshold_ns: settings.profiling.advanced.waiting_for_gvl_threshold_ns,
|
|
106
106
|
otel_context_enabled: settings.profiling.advanced.preview_otel_context_enabled,
|
|
107
107
|
native_filenames_enabled: settings.profiling.advanced.native_filenames_enabled,
|
|
108
|
+
show_classes: settings.profiling.advanced.experimental_show_classes_enabled,
|
|
108
109
|
)
|
|
109
110
|
end
|
|
110
111
|
|
data/lib/datadog/profiling.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "rbconfig"
|
|
3
4
|
require_relative "core"
|
|
4
5
|
require_relative "core/environment/variable_helpers"
|
|
5
6
|
require_relative "core/utils/only_once"
|
|
@@ -7,6 +8,9 @@ require_relative "core/utils/only_once"
|
|
|
7
8
|
module Datadog
|
|
8
9
|
# Datadog Continuous Profiler implementation: https://docs.datadoghq.com/profiler/
|
|
9
10
|
module Profiling
|
|
11
|
+
STATIC_RUBY_PATH = (RbConfig::CONFIG["ENABLE_SHARED"] == "no") ? RbConfig.ruby.freeze : nil
|
|
12
|
+
private_constant :STATIC_RUBY_PATH
|
|
13
|
+
|
|
10
14
|
def self.supported?
|
|
11
15
|
unsupported_reason.nil?
|
|
12
16
|
end
|
|
@@ -104,9 +104,30 @@ module Datadog
|
|
|
104
104
|
return writer
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
+
if settings.tracing.native_transport && (transport = build_native_transport(agent_settings))
|
|
108
|
+
options = options.merge(transport: transport)
|
|
109
|
+
end
|
|
110
|
+
|
|
107
111
|
Tracing::Writer.new(agent_settings: agent_settings, **options)
|
|
108
112
|
end
|
|
109
113
|
|
|
114
|
+
def build_native_transport(agent_settings)
|
|
115
|
+
require_relative "transport/native"
|
|
116
|
+
|
|
117
|
+
unless Transport::Native.supported?
|
|
118
|
+
Datadog.logger.warn(
|
|
119
|
+
"Native transport requested but not available: #{Transport::Native::UNSUPPORTED_REASON}. " \
|
|
120
|
+
"Falling back to default HTTP transport."
|
|
121
|
+
)
|
|
122
|
+
return nil
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
Transport::Native::Transport.new(
|
|
126
|
+
agent_settings: agent_settings,
|
|
127
|
+
logger: Datadog.logger
|
|
128
|
+
)
|
|
129
|
+
end
|
|
130
|
+
|
|
110
131
|
def subscribe_to_writer_events!(writer, sampler_delegator, test_mode)
|
|
111
132
|
return unless writer.respond_to?(:events) # Check if it's a custom, external writer
|
|
112
133
|
|
|
@@ -16,6 +16,7 @@ module Datadog
|
|
|
16
16
|
ENV_RESOURCE_RENAMING_ENABLED = "DD_TRACE_RESOURCE_RENAMING_ENABLED"
|
|
17
17
|
ENV_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT = "DD_TRACE_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT"
|
|
18
18
|
ENV_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED = "DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED"
|
|
19
|
+
ENV_EXPERIMENTAL_NATIVE_TRANSPORT_ENABLED = "DD_EXPERIMENTAL_NATIVE_TRANSPORT_ENABLED"
|
|
19
20
|
|
|
20
21
|
# @public_api
|
|
21
22
|
module SpanAttributeSchema
|
|
@@ -482,6 +482,22 @@ module Datadog
|
|
|
482
482
|
end
|
|
483
483
|
end
|
|
484
484
|
|
|
485
|
+
# Use the native trace transport (Rust via C FFI) instead of
|
|
486
|
+
# the default pure-Ruby HTTP transport.
|
|
487
|
+
#
|
|
488
|
+
# The native transport delegates serialization, stats
|
|
489
|
+
# computation, and HTTP sending to the Rust data pipeline.
|
|
490
|
+
#
|
|
491
|
+
# This option is recommended for internal use only.
|
|
492
|
+
#
|
|
493
|
+
# @default `false`
|
|
494
|
+
# @return [Boolean]
|
|
495
|
+
option :native_transport do |o|
|
|
496
|
+
o.env Configuration::Ext::ENV_EXPERIMENTAL_NATIVE_TRANSPORT_ENABLED
|
|
497
|
+
o.default false
|
|
498
|
+
o.type :bool
|
|
499
|
+
end
|
|
500
|
+
|
|
485
501
|
# A custom writer instance.
|
|
486
502
|
# The object must respect the {Datadog::Tracing::Writer} interface.
|
|
487
503
|
#
|
|
@@ -10,7 +10,8 @@ module Datadog
|
|
|
10
10
|
# Hanami Instrumentation for `hanami.routing`
|
|
11
11
|
module RouterTracing
|
|
12
12
|
def call(env)
|
|
13
|
-
|
|
13
|
+
active_span = Tracing.active_span
|
|
14
|
+
return super if active_span && active_span.name == Ext::SPAN_ROUTING
|
|
14
15
|
|
|
15
16
|
Tracing.trace(
|
|
16
17
|
Ext::SPAN_ROUTING,
|
|
@@ -21,6 +21,10 @@ module Datadog
|
|
|
21
21
|
super
|
|
22
22
|
|
|
23
23
|
span.resource = payload[:api]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def on_finish(span, _event, _id, payload)
|
|
27
|
+
super
|
|
24
28
|
|
|
25
29
|
span.set_tag(Ext::TAG_REQUEST_SIZE, payload[:request_size]) if payload.key?(:request_size)
|
|
26
30
|
span.set_tag(Ext::TAG_RESPONSE_SIZE, payload[:response_size]) if payload.key?(:response_size)
|
|
@@ -17,12 +17,17 @@ module Datadog
|
|
|
17
17
|
|
|
18
18
|
module_function
|
|
19
19
|
|
|
20
|
-
def on_start(span, _event, _id,
|
|
20
|
+
def on_start(span, _event, _id, _payload)
|
|
21
|
+
super
|
|
22
|
+
|
|
23
|
+
span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_PRODUCER)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def on_finish(span, _event, _id, payload)
|
|
21
27
|
super
|
|
22
28
|
|
|
23
29
|
span.set_tag(Ext::TAG_MESSAGE_COUNT, payload[:message_count]) if payload.key?(:message_count)
|
|
24
30
|
span.set_tag(Ext::TAG_SENT_MESSAGE_COUNT, payload[:sent_message_count]) if payload.key?(:sent_message_count)
|
|
25
|
-
span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_PRODUCER)
|
|
26
31
|
end
|
|
27
32
|
|
|
28
33
|
def span_name
|
|
@@ -17,7 +17,13 @@ module Datadog
|
|
|
17
17
|
|
|
18
18
|
module_function
|
|
19
19
|
|
|
20
|
-
def on_start(span, _event, _id,
|
|
20
|
+
def on_start(span, _event, _id, _payload)
|
|
21
|
+
super
|
|
22
|
+
|
|
23
|
+
span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_PRODUCER)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def on_finish(span, _event, _id, payload)
|
|
21
27
|
super
|
|
22
28
|
|
|
23
29
|
span.set_tag(Ext::TAG_ATTEMPTS, payload[:attempts]) if payload.key?(:attempts)
|
|
@@ -25,7 +31,6 @@ module Datadog
|
|
|
25
31
|
if payload.key?(:delivered_message_count)
|
|
26
32
|
span.set_tag(Ext::TAG_DELIVERED_MESSAGE_COUNT, payload[:delivered_message_count])
|
|
27
33
|
end
|
|
28
|
-
span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_PRODUCER)
|
|
29
34
|
end
|
|
30
35
|
|
|
31
36
|
def span_name
|
|
@@ -46,11 +46,7 @@ module Datadog
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def parse_opts(sql, opts)
|
|
49
|
-
db_opts =
|
|
50
|
-
@opts
|
|
51
|
-
elsif instance_variable_defined?(:@pool) && @pool
|
|
52
|
-
@pool.db.opts
|
|
53
|
-
end
|
|
49
|
+
db_opts = @pool.db.opts if instance_variable_defined?(:@pool) && @pool
|
|
54
50
|
sql = sql.is_a?(::Sequel::SQL::Expression) ? literal(sql) : sql.to_s
|
|
55
51
|
|
|
56
52
|
Utils.parse_opts(sql, opts, db_opts)
|
|
@@ -46,10 +46,15 @@ module Datadog
|
|
|
46
46
|
logger.debug(e)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
#
|
|
50
|
-
# The {SyncWriter}
|
|
49
|
+
# Stops the {SyncWriter}.
|
|
50
|
+
# The {SyncWriter} holds no worker thread, but it owns its transport, so
|
|
51
|
+
# on teardown we deterministically release transports that hold native
|
|
52
|
+
# resources (e.g. the native trace exporter's Rust runtime and
|
|
53
|
+
# process-global fork hooks) rather than relying on the GC finalizer.
|
|
54
|
+
# The default HTTP transport has no `#close` and is left untouched, and
|
|
55
|
+
# `#close` is idempotent so repeated `#stop` calls are safe.
|
|
51
56
|
def stop
|
|
52
|
-
|
|
57
|
+
@transport.close if @transport.respond_to?(:close)
|
|
53
58
|
true
|
|
54
59
|
end
|
|
55
60
|
|
|
@@ -628,6 +628,10 @@ module Datadog
|
|
|
628
628
|
appsec_bit = upstream_tags[Tracing::Metadata::Ext::Distributed::TAG_TRACE_SOURCE].to_i(16) &
|
|
629
629
|
Datadog::AppSec::Ext::PRODUCT_BIT
|
|
630
630
|
return appsec_enabled if appsec_bit != 0
|
|
631
|
+
|
|
632
|
+
ai_guard_bit = upstream_tags[Tracing::Metadata::Ext::Distributed::TAG_TRACE_SOURCE].to_i(16) &
|
|
633
|
+
Datadog::AIGuard::Ext::PRODUCT_BIT
|
|
634
|
+
return ai_guard_enabled if ai_guard_bit != 0
|
|
631
635
|
end
|
|
632
636
|
|
|
633
637
|
false
|
|
@@ -642,6 +646,10 @@ module Datadog
|
|
|
642
646
|
@appsec_enabled ||= Datadog.configuration.appsec.enabled
|
|
643
647
|
end
|
|
644
648
|
|
|
649
|
+
def ai_guard_enabled
|
|
650
|
+
@ai_guard_enabled ||= Datadog.configuration.respond_to?(:ai_guard) && Datadog.configuration.ai_guard.enabled
|
|
651
|
+
end
|
|
652
|
+
|
|
645
653
|
# Due to APM Tracing (the product) and Tracing (the transport) being intertwined, we cannot completely disabled APM
|
|
646
654
|
# without also disabling the tracer. When setting `@apm_tracing_enabled` to `false`, it does not disable the tracer,
|
|
647
655
|
# but rather only sends heartbeat traces (1 per minutes), so that the service is considered alive in the backend.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Datadog
|
|
6
|
+
module Tracing
|
|
7
|
+
module Transport
|
|
8
|
+
module Native
|
|
9
|
+
# Response from the native trace exporter.
|
|
10
|
+
#
|
|
11
|
+
# Constructed by the C extension after a send completes (or fails).
|
|
12
|
+
# Implements the same predicate interface as the HTTP transport's
|
|
13
|
+
# response so callers can treat both uniformly.
|
|
14
|
+
class Response
|
|
15
|
+
SERVICE_RATE_KEY = "rate_by_service"
|
|
16
|
+
|
|
17
|
+
attr_reader :trace_count, :payload
|
|
18
|
+
|
|
19
|
+
def initialize(ok:, internal_error: false, server_error: false, client_error: false,
|
|
20
|
+
not_found: false, unsupported: false, trace_count: 0, payload: nil)
|
|
21
|
+
@ok = ok
|
|
22
|
+
@internal_error = internal_error
|
|
23
|
+
@server_error = server_error
|
|
24
|
+
@client_error = client_error
|
|
25
|
+
@not_found = not_found
|
|
26
|
+
@unsupported = unsupported
|
|
27
|
+
@trace_count = trace_count
|
|
28
|
+
@payload = payload
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def ok?
|
|
32
|
+
@ok
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def internal_error?
|
|
36
|
+
@internal_error
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def server_error?
|
|
40
|
+
@server_error
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def client_error?
|
|
44
|
+
@client_error
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def not_found?
|
|
48
|
+
@not_found
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def unsupported?
|
|
52
|
+
@unsupported
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Parse the agent's JSON response body and extract the
|
|
56
|
+
# +rate_by_service+ map. Returns +nil+ when the payload
|
|
57
|
+
# is absent or does not contain sampling rates.
|
|
58
|
+
def service_rates
|
|
59
|
+
payload = @payload
|
|
60
|
+
return nil if payload.nil? || payload.empty?
|
|
61
|
+
|
|
62
|
+
parsed = JSON.parse(payload)
|
|
63
|
+
parsed[SERVICE_RATE_KEY] if parsed.is_a?(Hash)
|
|
64
|
+
rescue JSON::ParserError
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|