datadog 2.41.0 → 2.42.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 +36 -2
- data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +110 -61
- data/ext/datadog_profiling_native_extension/collectors_stack.c +11 -3
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +141 -103
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +7 -3
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -10
- data/ext/datadog_profiling_native_extension/extconf.rb +52 -93
- data/ext/datadog_profiling_native_extension/heap_recorder.c +338 -255
- data/ext/datadog_profiling_native_extension/heap_recorder.h +46 -31
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -24
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +509 -440
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +15 -3
- data/ext/datadog_profiling_native_extension/profiling.c +2 -0
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +1 -79
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +0 -7
- data/ext/datadog_profiling_native_extension/stack_recorder.c +93 -61
- data/ext/datadog_profiling_native_extension/stack_recorder.h +12 -4
- data/ext/libdatadog_api/datadog_ruby_common.h +0 -10
- data/ext/libdatadog_api/di.c +10 -0
- data/ext/libdatadog_api/extconf.rb +3 -0
- data/ext/libdatadog_api/init.c +2 -0
- data/ext/libdatadog_api/otel_thread_context.c +232 -0
- data/ext/libdatadog_api/otel_thread_context.h +5 -0
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/assets/blocked.html +1 -108
- data/lib/datadog/core/configuration/components.rb +1 -0
- data/lib/datadog/core/crashtracking/component.rb +5 -1
- data/lib/datadog/data_streams/pathway_context.rb +20 -22
- data/lib/datadog/data_streams/processor.rb +31 -0
- data/lib/datadog/di/instrumenter.rb +41 -1
- data/lib/datadog/di/logger.rb +2 -2
- data/lib/datadog/di/probe.rb +9 -1
- data/lib/datadog/di/probe_notification_builder.rb +1 -0
- data/lib/datadog/di/remote.rb +3 -3
- data/lib/datadog/open_feature/evaluation_engine.rb +29 -3
- data/lib/datadog/open_feature/exposures/event.rb +10 -3
- data/lib/datadog/open_feature/ext.rb +19 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +236 -80
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +179 -68
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +24 -21
- data/lib/datadog/open_feature/native_evaluator.rb +33 -6
- data/lib/datadog/open_feature/noop_evaluator.rb +5 -0
- data/lib/datadog/open_feature/provider.rb +11 -2
- data/lib/datadog/opentelemetry/sdk/propagator.rb +1 -1
- data/lib/datadog/opentelemetry/trace.rb +3 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +3 -0
- data/lib/datadog/profiling/collectors/thread_context.rb +0 -4
- data/lib/datadog/profiling/component.rb +8 -16
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
- data/lib/datadog/tracing/distributed/baggage.rb +0 -1
- data/lib/datadog/tracing/distributed/datadog.rb +3 -3
- data/lib/datadog/tracing/distributed/propagation.rb +3 -0
- data/lib/datadog/tracing/distributed/trace_context.rb +14 -271
- data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
- data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
- data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
- data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
- data/lib/datadog/tracing/otel_thread_context.rb +30 -0
- data/lib/datadog/tracing/remote.rb +195 -27
- data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -0
- data/lib/datadog/tracing/trace_digest.rb +22 -4
- data/lib/datadog/tracing/trace_operation.rb +22 -10
- data/lib/datadog/tracing/tracer.rb +5 -5
- data/lib/datadog/version.rb +1 -1
- metadata +14 -8
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -69
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "ext"
|
|
4
|
+
|
|
5
|
+
module Datadog
|
|
6
|
+
module Tracing
|
|
7
|
+
module Distributed
|
|
8
|
+
class TraceState
|
|
9
|
+
# Encodes and decodes Datadog propagation state.
|
|
10
|
+
class Datadog
|
|
11
|
+
class << self
|
|
12
|
+
def from_digest(digest)
|
|
13
|
+
ts_parent_id = if !digest.span_remote
|
|
14
|
+
format("%016x", digest.span_id || 0)
|
|
15
|
+
elsif digest.trace_distributed_tags&.key?(Tracing::Metadata::Ext::Distributed::TAG_DD_PARENT_ID)
|
|
16
|
+
digest.trace_distributed_tags[Tracing::Metadata::Ext::Distributed::TAG_DD_PARENT_ID]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
new(
|
|
20
|
+
sampling_priority: digest.trace_sampling_priority,
|
|
21
|
+
origin: digest.trace_origin,
|
|
22
|
+
ts_parent_id: ts_parent_id,
|
|
23
|
+
tags: digest.trace_distributed_tags,
|
|
24
|
+
unknown_fields: digest.trace_state_unknown_fields,
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def from_tracestate_member(value)
|
|
29
|
+
return new unless value
|
|
30
|
+
|
|
31
|
+
# @type var sampling_priority: ::Integer?
|
|
32
|
+
# @type var origin: ::String?
|
|
33
|
+
# @type var ts_parent_id: ::String?
|
|
34
|
+
# @type var tags: ::Hash[::String, ::String]?
|
|
35
|
+
# @type var unknown_fields: ::String?
|
|
36
|
+
sampling_priority = nil
|
|
37
|
+
origin = nil
|
|
38
|
+
ts_parent_id = nil
|
|
39
|
+
tags = nil
|
|
40
|
+
unknown_fields = nil
|
|
41
|
+
|
|
42
|
+
value.split(";").each do |pair|
|
|
43
|
+
key, field = pair.split(":", 2)
|
|
44
|
+
case key
|
|
45
|
+
when "s"
|
|
46
|
+
sampling_priority = begin
|
|
47
|
+
Integer(field)
|
|
48
|
+
rescue
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
when "o"
|
|
52
|
+
origin = field
|
|
53
|
+
when "p"
|
|
54
|
+
ts_parent_id = field
|
|
55
|
+
when /^t\./
|
|
56
|
+
key = key.to_s
|
|
57
|
+
field = field.to_s
|
|
58
|
+
key.slice!(0..1)
|
|
59
|
+
next if key == Tracing::Metadata::Ext::Distributed::TID
|
|
60
|
+
|
|
61
|
+
field.tr!("~", "=")
|
|
62
|
+
current_tags = tags || {}
|
|
63
|
+
current_tags["#{Tracing::Metadata::Ext::Distributed::TAGS_PREFIX}#{key}"] = field
|
|
64
|
+
tags = current_tags
|
|
65
|
+
else
|
|
66
|
+
current_unknown_fields = unknown_fields || +""
|
|
67
|
+
current_unknown_fields << pair << ";"
|
|
68
|
+
unknown_fields = current_unknown_fields
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
new(
|
|
73
|
+
sampling_priority: sampling_priority,
|
|
74
|
+
origin: origin,
|
|
75
|
+
ts_parent_id: ts_parent_id,
|
|
76
|
+
tags: tags,
|
|
77
|
+
unknown_fields: unknown_fields,
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Serializes a {Hash<String,String>} into a `x-datadog-tags`-compatible
|
|
82
|
+
# String.
|
|
83
|
+
#
|
|
84
|
+
# @param tags [Hash<String,String>] trace tag hash
|
|
85
|
+
# @return [String] serialized tags hash
|
|
86
|
+
# @raise [EncodingError] if tags cannot be serialized to the `x-datadog-tags` format
|
|
87
|
+
def encode(tags)
|
|
88
|
+
tags.map do |raw_key, raw_value|
|
|
89
|
+
key = raw_key.to_s
|
|
90
|
+
value = raw_value.to_s
|
|
91
|
+
|
|
92
|
+
unless Ext::Datadog::VALID_KEY_CHARS.match?(key)
|
|
93
|
+
raise EncodingError, "Invalid key `#{key}` for value `#{value}`"
|
|
94
|
+
end
|
|
95
|
+
unless Ext::Datadog::VALID_VALUE_CHARS.match?(value)
|
|
96
|
+
raise EncodingError, "Invalid value `#{value}` for key `#{key}`"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
"#{key}=#{value.strip}"
|
|
100
|
+
end.join(",")
|
|
101
|
+
rescue => e
|
|
102
|
+
raise EncodingError, "Error encoding tags `#{tags}`: `#{e.class}: #{e.message}`"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Deserializes a `x-datadog-tags`-formatted String into a {Hash<String,String>}.
|
|
106
|
+
#
|
|
107
|
+
# @param string [String] tags as serialized by {#encode}
|
|
108
|
+
# @return [Hash<String,String>] decoded input as a hash of strings
|
|
109
|
+
# @raise [DecodingError] if string does not conform to the `x-datadog-tags` format
|
|
110
|
+
def decode(string)
|
|
111
|
+
result = (string.split(",").map do |raw_tag|
|
|
112
|
+
raw_tag.split("=", 2).tap do |raw_key, raw_value|
|
|
113
|
+
key = raw_key.to_s
|
|
114
|
+
value = raw_value.to_s
|
|
115
|
+
|
|
116
|
+
raise DecodingError, "Invalid key: #{key}" unless Ext::Datadog::VALID_KEY_CHARS.match?(key)
|
|
117
|
+
raise DecodingError, "Invalid value: #{value}" unless Ext::Datadog::VALID_VALUE_CHARS.match?(value)
|
|
118
|
+
|
|
119
|
+
value.strip!
|
|
120
|
+
end
|
|
121
|
+
end).to_h
|
|
122
|
+
|
|
123
|
+
raise DecodingError, "Invalid empty tags: #{string}" if result.empty? && !string.empty?
|
|
124
|
+
|
|
125
|
+
result
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# An error occurred during distributed tags encoding.
|
|
130
|
+
# See {#message} for more information.
|
|
131
|
+
class EncodingError < StandardError
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# An error occurred during distributed tags decoding.
|
|
135
|
+
# See {#message} for more information.
|
|
136
|
+
class DecodingError < StandardError
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
attr_reader :sampling_priority, :origin, :ts_parent_id, :tags, :unknown_fields
|
|
140
|
+
|
|
141
|
+
def initialize(sampling_priority: nil, origin: nil, ts_parent_id: nil, tags: nil, unknown_fields: nil)
|
|
142
|
+
@sampling_priority = sampling_priority
|
|
143
|
+
@origin = origin
|
|
144
|
+
@ts_parent_id = ts_parent_id
|
|
145
|
+
@tags = tags
|
|
146
|
+
@unknown_fields = unknown_fields
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Builds the `dd=` tracestate member.
|
|
150
|
+
def to_s
|
|
151
|
+
vendor = +"dd="
|
|
152
|
+
if ts_parent_id
|
|
153
|
+
field = "p:#{ts_parent_id};"
|
|
154
|
+
vendor << field if vendor.bytesize + field.bytesize <= (Ext::TRACESTATE_VALUE_SIZE_LIMIT + 1)
|
|
155
|
+
end
|
|
156
|
+
if sampling_priority
|
|
157
|
+
field = "s:#{sampling_priority};"
|
|
158
|
+
vendor << field if vendor.bytesize + field.bytesize <= (Ext::TRACESTATE_VALUE_SIZE_LIMIT + 1)
|
|
159
|
+
end
|
|
160
|
+
if origin
|
|
161
|
+
field = "o:#{serialize_origin(origin)};"
|
|
162
|
+
vendor << field if vendor.bytesize + field.bytesize <= (Ext::TRACESTATE_VALUE_SIZE_LIMIT + 1)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Replacing this with safe navigation causes a LocalJumpError on Rubies <= 3.0.
|
|
166
|
+
if tags # rubocop:disable Style/SafeNavigation
|
|
167
|
+
tags.each do |name, value|
|
|
168
|
+
tag_field = "t.#{serialize_tag_key(name)}:#{serialize_tag_value(value)};"
|
|
169
|
+
break if vendor.bytesize + tag_field.bytesize > (Ext::TRACESTATE_VALUE_SIZE_LIMIT + 1)
|
|
170
|
+
|
|
171
|
+
vendor << tag_field
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
if unknown_fields && vendor.bytesize + unknown_fields.bytesize <= (Ext::TRACESTATE_VALUE_SIZE_LIMIT + 1)
|
|
176
|
+
vendor << unknown_fields
|
|
177
|
+
end
|
|
178
|
+
return "" if vendor.bytesize == 3
|
|
179
|
+
|
|
180
|
+
vendor.chop!
|
|
181
|
+
vendor
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
private
|
|
185
|
+
|
|
186
|
+
# If any characters in <origin_value> are invalid, replace each invalid character with 0x5F (underscore).
|
|
187
|
+
# Invalid characters are: characters outside the ASCII range 0x20 to 0x7E,
|
|
188
|
+
# 0x2C (comma), 0x3B (semi-colon), and 0x7E (tilde).
|
|
189
|
+
# Then, remap 0x3D (equals) to 0x7E (tilde)
|
|
190
|
+
def serialize_origin(value)
|
|
191
|
+
# DEV: It's unlikely that characters will be out of range, as they mostly
|
|
192
|
+
# DEV: come from Datadog-controlled sources.
|
|
193
|
+
# DEV: Trying to `match?` is measurably faster than a `gsub` that does not match.
|
|
194
|
+
invalid_chars = Ext::Datadog::INVALID_ORIGIN_CHARS
|
|
195
|
+
value = invalid_chars.match?(value) ? value.gsub(invalid_chars, "_") : value
|
|
196
|
+
remapped_chars = Ext::Datadog::REMAP_ORIGIN_CHARS
|
|
197
|
+
remapped_chars.match?(value) ? value.gsub(remapped_chars, "~") : value
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Serialize `_dd.p.{key}` by first removing the `_dd.p.` prefix.
|
|
201
|
+
# Then replacing invalid characters with `_`.
|
|
202
|
+
#
|
|
203
|
+
# The argument `name` is always frozen.
|
|
204
|
+
# Returns a new String object for the serialized key.
|
|
205
|
+
def serialize_tag_key(name)
|
|
206
|
+
key = name.delete_prefix(Tracing::Metadata::Ext::Distributed::TAGS_PREFIX)
|
|
207
|
+
|
|
208
|
+
# DEV: It's unlikely that characters will be out of range, as they mostly
|
|
209
|
+
# DEV: come from Datadog-controlled sources.
|
|
210
|
+
# DEV: Trying to `match?` is measurably faster than a `gsub!` that does not match.
|
|
211
|
+
invalid_chars = Ext::Datadog::INVALID_TAG_KEY_CHARS
|
|
212
|
+
key.gsub!(invalid_chars, "_") if invalid_chars.match?(key)
|
|
213
|
+
key
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Replaces invalid characters with `_`, then replaces `=` with `~`.
|
|
217
|
+
#
|
|
218
|
+
# The argument `value` belongs to {TraceDigest}, thus should not be directly modified.
|
|
219
|
+
# Returns a new String object for the serialized value.
|
|
220
|
+
def serialize_tag_value(value)
|
|
221
|
+
# DEV: It's unlikely that characters will be out of range, as they mostly
|
|
222
|
+
# DEV: come from Datadog-controlled sources.
|
|
223
|
+
# DEV: Trying to `match?` is measurably faster than a `gsub` that does not match.
|
|
224
|
+
invalid_chars = Ext::Datadog::INVALID_TAG_VALUE_CHARS
|
|
225
|
+
serialized = invalid_chars.match?(value) ? value.gsub(invalid_chars, "_") : value
|
|
226
|
+
# DEV: Checking for an unlikely '=' is faster than a no-op `tr`.
|
|
227
|
+
serialized.include?("=") ? serialized.tr("=", "~") : serialized
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
5
|
+
require_relative "../../sampling/ext"
|
|
6
|
+
|
|
7
|
+
module Datadog
|
|
8
|
+
module Tracing
|
|
9
|
+
module Distributed
|
|
10
|
+
class TraceState
|
|
11
|
+
module Ext
|
|
12
|
+
TRACESTATE_MAX_SIZE_LIMIT = 512
|
|
13
|
+
TRACESTATE_MAX_LIST_VENDORS = 32
|
|
14
|
+
TRACESTATE_VALUE_SIZE_LIMIT = 256
|
|
15
|
+
|
|
16
|
+
module Datadog
|
|
17
|
+
# ASCII characters 32-126, except `,`, `=`, and ` `. At least one character.
|
|
18
|
+
VALID_KEY_CHARS = /\A(?:(?![,= ])[\u0020-\u007E])+\Z/.freeze
|
|
19
|
+
# ASCII characters 32-126, except `,`. At least one character.
|
|
20
|
+
VALID_VALUE_CHARS = /\A(?:(?!,)[\u0020-\u007E])+\Z/.freeze
|
|
21
|
+
INVALID_ORIGIN_CHARS = /[\u0000-\u0019,;~\u007F-\u{10FFFF}]/.freeze
|
|
22
|
+
REMAP_ORIGIN_CHARS = /=/.freeze
|
|
23
|
+
INVALID_TAG_KEY_CHARS = /[\u0000-\u0020,=\u007F-\u{10FFFF}]/.freeze
|
|
24
|
+
INVALID_TAG_VALUE_CHARS = /[\u0000-\u001F,;\u007E-\u{10FFFF}]/.freeze
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module OpenTelemetry
|
|
28
|
+
MAX_THRESHOLD = 1 << 56
|
|
29
|
+
MAX_ENCODABLE_THRESHOLD = MAX_THRESHOLD - 1
|
|
30
|
+
UINT64_MODULO = 1 << 64
|
|
31
|
+
UINT64_MASK = UINT64_MODULO - 1
|
|
32
|
+
VALID_THRESHOLD = /\A[0-9a-f]{1,14}\z/
|
|
33
|
+
VALID_RANDOM_VALUE = /\A[0-9a-f]{14}\z/
|
|
34
|
+
NON_PROBABILITY_DECISIONS = Set[
|
|
35
|
+
Sampling::Ext::Decision::MANUAL,
|
|
36
|
+
Sampling::Ext::Decision::ASM,
|
|
37
|
+
Sampling::Ext::Decision::AI_GUARD,
|
|
38
|
+
].freeze
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../sampling/rate_sampler"
|
|
4
|
+
require_relative "ext"
|
|
5
|
+
|
|
6
|
+
module Datadog
|
|
7
|
+
module Tracing
|
|
8
|
+
module Distributed
|
|
9
|
+
class TraceState
|
|
10
|
+
# Encodes consistent probability sampling in the OpenTelemetry `ot=` tracestate member.
|
|
11
|
+
# @see https://opentelemetry.io/docs/specs/otel/trace/tracestate-probability-sampling/
|
|
12
|
+
class OpenTelemetry
|
|
13
|
+
class << self
|
|
14
|
+
def from_digest(digest, propagate_sampling: true)
|
|
15
|
+
new(
|
|
16
|
+
random_value: (digest.trace_otel_random_value if propagate_sampling),
|
|
17
|
+
threshold: (digest.trace_otel_threshold if propagate_sampling),
|
|
18
|
+
unknown_fields: digest.trace_otel_unknown_fields,
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Parses known fields and retains unknown fields for propagation.
|
|
23
|
+
def from_tracestate_member(value)
|
|
24
|
+
return new unless value
|
|
25
|
+
|
|
26
|
+
# @type var random_value: ::String?
|
|
27
|
+
# @type var threshold: ::String?
|
|
28
|
+
random_value = nil
|
|
29
|
+
threshold = nil
|
|
30
|
+
unknown = []
|
|
31
|
+
|
|
32
|
+
value.split(";").each do |pair|
|
|
33
|
+
key, field = pair.split(":", 2)
|
|
34
|
+
case key
|
|
35
|
+
when "rv"
|
|
36
|
+
random_value = field
|
|
37
|
+
when "th"
|
|
38
|
+
threshold = field
|
|
39
|
+
else
|
|
40
|
+
unknown << pair
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
threshold = nil if threshold && !Ext::OpenTelemetry::VALID_THRESHOLD.match?(threshold)
|
|
45
|
+
random_value = nil if random_value && !Ext::OpenTelemetry::VALID_RANDOM_VALUE.match?(random_value)
|
|
46
|
+
|
|
47
|
+
new(
|
|
48
|
+
random_value: random_value,
|
|
49
|
+
threshold: threshold,
|
|
50
|
+
unknown_fields: unknown.empty? ? nil : "#{unknown.join(";")};",
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
attr_reader :random_value, :threshold, :unknown_fields
|
|
56
|
+
|
|
57
|
+
def initialize(random_value: nil, threshold: nil, unknown_fields: nil)
|
|
58
|
+
@random_value = random_value
|
|
59
|
+
@threshold = threshold
|
|
60
|
+
@unknown_fields = unknown_fields
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Resolves fields that represent the trace's current sampling decision.
|
|
64
|
+
def outbound(
|
|
65
|
+
trace_id:,
|
|
66
|
+
sampling_priority:,
|
|
67
|
+
decision_maker:,
|
|
68
|
+
applied_rate:,
|
|
69
|
+
rate_limiter_rate:,
|
|
70
|
+
distributed_sampling_priority:
|
|
71
|
+
)
|
|
72
|
+
if Ext::OpenTelemetry::NON_PROBABILITY_DECISIONS.include?(decision_maker)
|
|
73
|
+
return self.class.new(random_value: random_value, unknown_fields: unknown_fields)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
trace_kept = sampling_priority && sampling_priority >= Sampling::Ext::Priority::AUTO_KEEP
|
|
77
|
+
if rate_limiter_rate && !trace_kept
|
|
78
|
+
return self.class.new(random_value: random_value, unknown_fields: unknown_fields)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
return self if random_value || threshold
|
|
82
|
+
return self.class.new(unknown_fields: unknown_fields) if distributed_sampling_priority || !applied_rate
|
|
83
|
+
|
|
84
|
+
outbound_threshold = threshold_for(applied_rate)
|
|
85
|
+
outbound_random_value = reconcile_random_value(
|
|
86
|
+
random_value_for(trace_id),
|
|
87
|
+
outbound_threshold,
|
|
88
|
+
!!trace_kept
|
|
89
|
+
)
|
|
90
|
+
self.class.new(
|
|
91
|
+
random_value: format_random_value(outbound_random_value),
|
|
92
|
+
threshold: format_threshold(outbound_threshold),
|
|
93
|
+
unknown_fields: unknown_fields,
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def to_s
|
|
98
|
+
fields = []
|
|
99
|
+
fields << "rv:#{random_value}" if random_value
|
|
100
|
+
fields << "th:#{threshold}" if threshold
|
|
101
|
+
fields.concat(unknown_fields.chomp(";").split(";")) if unknown_fields
|
|
102
|
+
|
|
103
|
+
value = +""
|
|
104
|
+
fields.each do |field|
|
|
105
|
+
separator = value.empty? ? "" : ";"
|
|
106
|
+
break if value.bytesize + separator.bytesize + field.bytesize > Ext::TRACESTATE_VALUE_SIZE_LIMIT
|
|
107
|
+
|
|
108
|
+
value << separator << field
|
|
109
|
+
end
|
|
110
|
+
value.empty? ? "" : "ot=#{value}"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
# Converts Datadog's Knuth hash into OTel's 56-bit random value.
|
|
116
|
+
def random_value_for(trace_id)
|
|
117
|
+
hash = (trace_id * Sampling::RateSampler::KNUTH_FACTOR) % Ext::OpenTelemetry::UINT64_MODULO
|
|
118
|
+
(~hash & Ext::OpenTelemetry::UINT64_MASK) >> 8
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Converts a sample rate into a 56-bit rejection threshold.
|
|
122
|
+
def threshold_for(rate)
|
|
123
|
+
threshold = ((1.0 - rate) * Ext::OpenTelemetry::MAX_THRESHOLD).round
|
|
124
|
+
return 0 if threshold < 0
|
|
125
|
+
return Ext::OpenTelemetry::MAX_ENCODABLE_THRESHOLD if threshold > Ext::OpenTelemetry::MAX_ENCODABLE_THRESHOLD
|
|
126
|
+
|
|
127
|
+
threshold
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Preserve Datadog's 64-bit decision after expressing it with 56-bit OTel values.
|
|
131
|
+
def reconcile_random_value(random_value, threshold, kept)
|
|
132
|
+
if kept && random_value < threshold
|
|
133
|
+
threshold
|
|
134
|
+
elsif !kept && random_value >= threshold
|
|
135
|
+
[threshold - 1, 0].max
|
|
136
|
+
else
|
|
137
|
+
random_value
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def format_random_value(random_value)
|
|
142
|
+
format("%014x", random_value)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# OTel thresholds omit trailing zero nibbles but retain leading zeroes.
|
|
146
|
+
def format_threshold(threshold)
|
|
147
|
+
return "0" if threshold.zero?
|
|
148
|
+
|
|
149
|
+
trailing_nibbles = ((threshold & -threshold).bit_length - 1) / 4
|
|
150
|
+
(threshold >> (trailing_nibbles * 4)).to_s(16).rjust(14 - trailing_nibbles, "0")
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../core/utils"
|
|
4
|
+
require_relative "trace_state/ext"
|
|
5
|
+
require_relative "trace_state/datadog"
|
|
6
|
+
require_relative "trace_state/open_telemetry"
|
|
7
|
+
|
|
8
|
+
module Datadog
|
|
9
|
+
module Tracing
|
|
10
|
+
module Distributed
|
|
11
|
+
# Parsed vendor state from a W3C `tracestate` header.
|
|
12
|
+
# @api private
|
|
13
|
+
class TraceState
|
|
14
|
+
class << self
|
|
15
|
+
def from_digest(digest, propagate_sampling: true)
|
|
16
|
+
new(
|
|
17
|
+
unknown_vendors: digest.trace_state,
|
|
18
|
+
datadog: Datadog.from_digest(digest),
|
|
19
|
+
open_telemetry: OpenTelemetry.from_digest(digest, propagate_sampling: propagate_sampling),
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def serialize_digest(digest)
|
|
24
|
+
serialize(
|
|
25
|
+
unknown_vendors: digest.trace_state,
|
|
26
|
+
datadog: Datadog.from_digest(digest),
|
|
27
|
+
open_telemetry: OpenTelemetry.from_digest(digest),
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Parses owned members while retaining other vendors unchanged.
|
|
32
|
+
def extract(tracestate)
|
|
33
|
+
vendors = split(tracestate) || []
|
|
34
|
+
datadog_index = vendors.index { |vendor| vendor.start_with?("dd=") }
|
|
35
|
+
datadog_value = vendors.delete_at(datadog_index).delete_prefix("dd=") if datadog_index
|
|
36
|
+
otel_index = vendors.index { |vendor| vendor.start_with?("ot=") }
|
|
37
|
+
otel_value = vendors.delete_at(otel_index).delete_prefix("ot=") if otel_index
|
|
38
|
+
|
|
39
|
+
new(
|
|
40
|
+
unknown_vendors: vendors.empty? ? nil : vendors.join(","),
|
|
41
|
+
datadog: Datadog.from_tracestate_member(datadog_value),
|
|
42
|
+
open_telemetry: OpenTelemetry.from_tracestate_member(otel_value),
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Partial members must not be propagated, but complete members within the limits are retained.
|
|
47
|
+
def split(tracestate)
|
|
48
|
+
return if tracestate.nil? || tracestate.empty?
|
|
49
|
+
|
|
50
|
+
remove_last_vendor = false
|
|
51
|
+
if tracestate.bytesize > Ext::TRACESTATE_MAX_SIZE_LIMIT
|
|
52
|
+
remove_last_vendor = tracestate.byteslice(Ext::TRACESTATE_MAX_SIZE_LIMIT, 1) != ","
|
|
53
|
+
tracestate = tracestate.byteslice(0, Ext::TRACESTATE_MAX_SIZE_LIMIT + 1) #: String
|
|
54
|
+
tracestate = tracestate.chop
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
tracestate = ::Datadog::Core::Utils.utf8_encode(tracestate, placeholder: nil)
|
|
58
|
+
return unless tracestate
|
|
59
|
+
|
|
60
|
+
vendors = tracestate.split(",", Ext::TRACESTATE_MAX_LIST_VENDORS + 1)
|
|
61
|
+
vendors.pop if vendors.length > Ext::TRACESTATE_MAX_LIST_VENDORS || remove_last_vendor
|
|
62
|
+
vendors.each(&:strip!)
|
|
63
|
+
vendors.pop while vendors.last == ""
|
|
64
|
+
vendors
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def serialize(unknown_vendors:, datadog:, open_telemetry:)
|
|
70
|
+
leading_vendors = select_leading_vendors(datadog, open_telemetry)
|
|
71
|
+
vendors = split(unknown_vendors)
|
|
72
|
+
|
|
73
|
+
if leading_vendors.empty?
|
|
74
|
+
return unless vendors && !vendors.empty?
|
|
75
|
+
|
|
76
|
+
return vendors.join(",")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
vendors&.reject! { |vendor| vendor.start_with?("dd=", "ot=") }
|
|
80
|
+
|
|
81
|
+
tracestate = leading_vendors.join(",")
|
|
82
|
+
if vendors && !vendors.empty?
|
|
83
|
+
vendors.first(Ext::TRACESTATE_MAX_LIST_VENDORS - leading_vendors.size).each do |vendor|
|
|
84
|
+
break if tracestate.bytesize + vendor.bytesize + 1 > Ext::TRACESTATE_MAX_SIZE_LIMIT
|
|
85
|
+
|
|
86
|
+
tracestate << "," << vendor
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
tracestate
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def select_leading_vendors(datadog, open_telemetry)
|
|
94
|
+
leading_vendors = []
|
|
95
|
+
add_leading_vendor(leading_vendors, datadog)
|
|
96
|
+
add_leading_vendor(leading_vendors, open_telemetry)
|
|
97
|
+
|
|
98
|
+
leading_vendors
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def add_leading_vendor(leading_vendors, state)
|
|
102
|
+
vendor = state.to_s
|
|
103
|
+
return if vendor.empty?
|
|
104
|
+
|
|
105
|
+
combined_size = leading_vendors.sum { |current| current.bytesize } +
|
|
106
|
+
leading_vendors.size + vendor.bytesize
|
|
107
|
+
leading_vendors << vendor if combined_size <= Ext::TRACESTATE_MAX_SIZE_LIMIT
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
attr_reader :unknown_vendors, :datadog, :open_telemetry
|
|
112
|
+
|
|
113
|
+
def initialize(unknown_vendors: nil, datadog: nil, open_telemetry: nil)
|
|
114
|
+
@unknown_vendors = unknown_vendors
|
|
115
|
+
@datadog = datadog || Datadog.new
|
|
116
|
+
@open_telemetry = open_telemetry || OpenTelemetry.new
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module Tracing
|
|
5
|
+
# Publishes a per-thread OpenTelemetry context record into a thread-local slot,
|
|
6
|
+
# so an out-of-process reader (e.g. the eBPF profiler) can discover it.
|
|
7
|
+
#
|
|
8
|
+
# See the "OTel Thread Context" OTEP:
|
|
9
|
+
# https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/profiles/4947-thread-ctx.md
|
|
10
|
+
#
|
|
11
|
+
# Native functions are implemented in ext/libdatadog_api/otel_thread_context.c
|
|
12
|
+
module OTelThreadContext
|
|
13
|
+
class << self
|
|
14
|
+
def supported?
|
|
15
|
+
Datadog::Core::LIBDATADOG_API_FAILURE.nil? && _native_supported?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def enable!
|
|
19
|
+
return false unless supported?
|
|
20
|
+
|
|
21
|
+
_native_enable
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def set(trace_id:, span_id:, local_root_span_id:)
|
|
25
|
+
_native_set(trace_id, span_id, local_root_span_id)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|