ddtrace 1.5.2 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -1
- data/ext/ddtrace_profiling_loader/ddtrace_profiling_loader.c +9 -2
- data/ext/ddtrace_profiling_loader/extconf.rb +17 -0
- data/ext/ddtrace_profiling_native_extension/NativeExtensionDesign.md +38 -2
- data/ext/ddtrace_profiling_native_extension/clock_id.h +1 -0
- data/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c +1 -0
- data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time.c +517 -42
- data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time.h +3 -0
- data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +208 -30
- data/ext/ddtrace_profiling_native_extension/collectors_stack.c +156 -46
- data/ext/ddtrace_profiling_native_extension/collectors_stack.h +11 -2
- data/ext/ddtrace_profiling_native_extension/extconf.rb +11 -1
- data/ext/ddtrace_profiling_native_extension/http_transport.c +83 -64
- data/ext/ddtrace_profiling_native_extension/libdatadog_helpers.h +4 -4
- data/ext/ddtrace_profiling_native_extension/native_extension_helpers.rb +3 -2
- data/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +59 -0
- data/ext/ddtrace_profiling_native_extension/private_vm_api_access.h +3 -0
- data/ext/ddtrace_profiling_native_extension/profiling.c +10 -0
- data/ext/ddtrace_profiling_native_extension/ruby_helpers.c +0 -1
- data/ext/ddtrace_profiling_native_extension/ruby_helpers.h +4 -2
- data/ext/ddtrace_profiling_native_extension/stack_recorder.c +45 -29
- data/ext/ddtrace_profiling_native_extension/stack_recorder.h +7 -7
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +4 -0
- data/lib/datadog/appsec/event.rb +6 -0
- data/lib/datadog/core/configuration/components.rb +20 -14
- data/lib/datadog/core/configuration/settings.rb +42 -4
- data/lib/datadog/core/diagnostics/environment_logger.rb +5 -1
- data/lib/datadog/core/utils/compression.rb +5 -1
- data/lib/datadog/core.rb +0 -54
- data/lib/datadog/profiling/collectors/cpu_and_wall_time.rb +12 -2
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +5 -3
- data/lib/datadog/profiling/exporter.rb +2 -4
- data/lib/datadog/profiling/http_transport.rb +1 -1
- data/lib/datadog/tracing/configuration/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +2 -0
- data/lib/datadog/tracing/contrib/dalli/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +4 -0
- data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +2 -0
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +3 -0
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +2 -0
- data/lib/datadog/tracing/contrib/ethon/multi_patch.rb +2 -0
- data/lib/datadog/tracing/contrib/excon/middleware.rb +2 -0
- data/lib/datadog/tracing/contrib/ext.rb +6 -0
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +2 -0
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +5 -0
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +7 -1
- data/lib/datadog/tracing/contrib/grpc/ext.rb +2 -0
- data/lib/datadog/tracing/contrib/hanami/action_tracer.rb +47 -0
- data/lib/datadog/tracing/contrib/hanami/configuration/settings.rb +22 -0
- data/lib/datadog/tracing/contrib/hanami/ext.rb +24 -0
- data/lib/datadog/tracing/contrib/hanami/integration.rb +44 -0
- data/lib/datadog/tracing/contrib/hanami/patcher.rb +33 -0
- data/lib/datadog/tracing/contrib/hanami/plugin.rb +23 -0
- data/lib/datadog/tracing/contrib/hanami/renderer_policy_tracing.rb +41 -0
- data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +44 -0
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +2 -0
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +2 -0
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +2 -0
- data/lib/datadog/tracing/contrib/mongodb/ext.rb +7 -0
- data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +4 -0
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +12 -0
- data/lib/datadog/tracing/contrib/mysql2/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +16 -0
- data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +12 -0
- data/lib/datadog/tracing/contrib/pg/ext.rb +2 -1
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +34 -18
- data/lib/datadog/tracing/contrib/propagation/sql_comment/comment.rb +43 -0
- data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +32 -0
- data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +28 -0
- data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +49 -0
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +11 -5
- data/lib/datadog/tracing/contrib/redis/ext.rb +2 -0
- data/lib/datadog/tracing/contrib/redis/instrumentation.rb +4 -2
- data/lib/datadog/tracing/contrib/redis/patcher.rb +41 -0
- data/lib/datadog/tracing/contrib/redis/tags.rb +5 -0
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +2 -0
- data/lib/datadog/tracing/contrib/sinatra/env.rb +12 -23
- data/lib/datadog/tracing/contrib/sinatra/ext.rb +7 -3
- data/lib/datadog/tracing/contrib/sinatra/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/sinatra/tracer.rb +8 -80
- data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +14 -9
- data/lib/datadog/tracing/contrib.rb +1 -0
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +84 -0
- data/lib/datadog/tracing/distributed/headers/datadog.rb +122 -30
- data/lib/datadog/tracing/distributed/headers/ext.rb +2 -0
- data/lib/datadog/tracing/flush.rb +1 -1
- data/lib/datadog/tracing/metadata/ext.rb +8 -0
- data/lib/datadog/tracing/propagation/http.rb +9 -1
- data/lib/datadog/tracing/sampling/ext.rb +31 -0
- data/lib/datadog/tracing/sampling/priority_sampler.rb +46 -4
- data/lib/datadog/tracing/sampling/rate_by_key_sampler.rb +8 -9
- data/lib/datadog/tracing/sampling/rate_by_service_sampler.rb +29 -5
- data/lib/datadog/tracing/sampling/rate_sampler.rb +10 -3
- data/lib/datadog/tracing/sampling/rule_sampler.rb +4 -3
- data/lib/datadog/tracing/sampling/span/ext.rb +0 -4
- data/lib/datadog/tracing/sampling/span/rule.rb +1 -1
- data/lib/datadog/tracing/sampling/span/sampler.rb +14 -3
- data/lib/datadog/tracing/trace_digest.rb +3 -0
- data/lib/datadog/tracing/trace_operation.rb +10 -0
- data/lib/datadog/tracing/trace_segment.rb +6 -0
- data/lib/datadog/tracing/tracer.rb +3 -1
- data/lib/datadog/tracing/writer.rb +7 -0
- data/lib/ddtrace/transport/trace_formatter.rb +7 -0
- data/lib/ddtrace/transport/traces.rb +1 -1
- data/lib/ddtrace/version.rb +2 -2
- metadata +18 -14
- data/lib/datadog/profiling/old_ext.rb +0 -42
- data/lib/datadog/profiling/transport/http/api/endpoint.rb +0 -85
- data/lib/datadog/profiling/transport/http/api/instance.rb +0 -38
- data/lib/datadog/profiling/transport/http/api/spec.rb +0 -42
- data/lib/datadog/profiling/transport/http/api.rb +0 -45
- data/lib/datadog/profiling/transport/http/builder.rb +0 -30
- data/lib/datadog/profiling/transport/http/client.rb +0 -37
- data/lib/datadog/profiling/transport/http/response.rb +0 -21
- data/lib/datadog/profiling/transport/http.rb +0 -118
@@ -25,10 +25,12 @@ module Datadog
|
|
25
25
|
def call(env)
|
26
26
|
# Set the trace context (e.g. distributed tracing)
|
27
27
|
if configuration[:distributed_tracing] && Tracing.active_trace.nil?
|
28
|
-
original_trace = Propagation::HTTP.extract(env)
|
28
|
+
original_trace = Tracing::Propagation::HTTP.extract(env)
|
29
29
|
Tracing.continue_trace!(original_trace)
|
30
30
|
end
|
31
31
|
|
32
|
+
return @app.call(env) if Sinatra::Env.datadog_span(env)
|
33
|
+
|
32
34
|
Tracing.trace(
|
33
35
|
Ext::SPAN_REQUEST,
|
34
36
|
service: configuration[:service_name],
|
@@ -39,7 +41,7 @@ module Datadog
|
|
39
41
|
# the nil signals that there's no good one yet and is also seen by profiler, when sampling the resource
|
40
42
|
span.resource = nil
|
41
43
|
|
42
|
-
Sinatra::Env.set_datadog_span(env,
|
44
|
+
Sinatra::Env.set_datadog_span(env, span)
|
43
45
|
|
44
46
|
response = @app.call(env)
|
45
47
|
ensure
|
@@ -51,14 +53,18 @@ module Datadog
|
|
51
53
|
span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_REQUEST)
|
52
54
|
|
53
55
|
request = ::Sinatra::Request.new(env)
|
56
|
+
|
54
57
|
span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, request.path)
|
55
58
|
span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_METHOD, request.request_method)
|
59
|
+
|
60
|
+
datadog_route = Sinatra::Env.route_path(env)
|
61
|
+
|
62
|
+
span.set_tag(Ext::TAG_ROUTE_PATH, datadog_route) if datadog_route
|
63
|
+
|
56
64
|
if request.script_name && !request.script_name.empty?
|
57
65
|
span.set_tag(Ext::TAG_SCRIPT_NAME, request.script_name)
|
58
66
|
end
|
59
67
|
|
60
|
-
span.set_tag(Ext::TAG_APP_NAME, @app_instance.settings.name)
|
61
|
-
|
62
68
|
# If this app handled the request, then Contrib::Sinatra::Tracer OR Contrib::Sinatra::Base set the
|
63
69
|
# resource; if no resource was set, let's use a fallback
|
64
70
|
span.resource = env['REQUEST_METHOD'] if span.resource.nil?
|
@@ -79,7 +85,10 @@ module Datadog
|
|
79
85
|
end
|
80
86
|
|
81
87
|
if (headers = response[1])
|
82
|
-
Sinatra::Headers.response_header_tags(
|
88
|
+
Sinatra::Headers.response_header_tags(
|
89
|
+
headers,
|
90
|
+
configuration[:headers][:response]
|
91
|
+
).each do |name, value|
|
83
92
|
span.set_tag(name, value) if span.get_tag(name).nil?
|
84
93
|
end
|
85
94
|
end
|
@@ -111,10 +120,6 @@ module Datadog
|
|
111
120
|
def configuration
|
112
121
|
Datadog.configuration.tracing[:sinatra]
|
113
122
|
end
|
114
|
-
|
115
|
-
def header_to_rack_header(name)
|
116
|
-
"HTTP_#{name.to_s.upcase.gsub(/[-\s]/, '_')}"
|
117
|
-
end
|
118
123
|
end
|
119
124
|
end
|
120
125
|
end
|
@@ -49,6 +49,7 @@ require_relative 'contrib/faraday/integration'
|
|
49
49
|
require_relative 'contrib/grape/integration'
|
50
50
|
require_relative 'contrib/graphql/integration'
|
51
51
|
require_relative 'contrib/grpc/integration'
|
52
|
+
require_relative 'contrib/hanami/integration'
|
52
53
|
require_relative 'contrib/http/integration'
|
53
54
|
require_relative 'contrib/httpclient/integration'
|
54
55
|
require_relative 'contrib/httprb/integration'
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module Tracing
|
5
|
+
module Distributed
|
6
|
+
# Encodes and decodes distributed 'x-datadog-tags' tags for transport
|
7
|
+
# to and from external processes.
|
8
|
+
module DatadogTagsCodec
|
9
|
+
# Backport `Regexp::match?` because it is measurably the most performant
|
10
|
+
# way to check if a string matches a regular expression.
|
11
|
+
module RefineRegexp
|
12
|
+
unless Regexp.method_defined?(:match?)
|
13
|
+
refine ::Regexp do
|
14
|
+
def match?(*args)
|
15
|
+
!match(*args).nil?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
using RefineRegexp
|
21
|
+
|
22
|
+
# ASCII characters 32-126, except `,`, `=`, and ` `. At least one character.
|
23
|
+
VALID_KEY_CHARS = /\A(?:(?![,= ])[\u0020-\u007E])+\Z/.freeze
|
24
|
+
# ASCII characters 32-126, except `,`. At least one character.
|
25
|
+
VALID_VALUE_CHARS = /\A(?:(?!,)[\u0020-\u007E])+\Z/.freeze
|
26
|
+
|
27
|
+
# Serializes a {Hash<String,String>} into a `x-datadog-tags`-compatible
|
28
|
+
# String.
|
29
|
+
#
|
30
|
+
# @param tags [Hash<String,String>] trace tag hash
|
31
|
+
# @return [String] serialized tags hash
|
32
|
+
# @raise [EncodingError] if tags cannot be serialized to the `x-datadog-tags` format
|
33
|
+
def self.encode(tags)
|
34
|
+
begin
|
35
|
+
tags.map do |raw_key, raw_value|
|
36
|
+
key = raw_key.to_s
|
37
|
+
value = raw_value.to_s
|
38
|
+
|
39
|
+
raise EncodingError, "Invalid key `#{key}` for value `#{value}`" unless VALID_KEY_CHARS.match?(key)
|
40
|
+
raise EncodingError, "Invalid value `#{value}` for key `#{key}`" unless VALID_VALUE_CHARS.match?(value)
|
41
|
+
|
42
|
+
"#{key}=#{value.strip}"
|
43
|
+
end.join(',')
|
44
|
+
rescue => e
|
45
|
+
raise EncodingError, "Error encoding tags `#{tags}`: `#{e}`"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Deserializes a `x-datadog-tags`-formatted String into a {Hash<String,String>}.
|
50
|
+
#
|
51
|
+
# @param string [String] tags as serialized by {#encode}
|
52
|
+
# @return [Hash<String,String>] decoded input as a hash of strings
|
53
|
+
# @raise [DecodingError] if string does not conform to the `x-datadog-tags` format
|
54
|
+
def self.decode(string)
|
55
|
+
result = Hash[string.split(',').map do |raw_tag|
|
56
|
+
raw_tag.split('=', 2).tap do |raw_key, raw_value|
|
57
|
+
key = raw_key.to_s
|
58
|
+
value = raw_value.to_s
|
59
|
+
|
60
|
+
raise DecodingError, "Invalid key: #{key}" unless VALID_KEY_CHARS.match?(key)
|
61
|
+
raise DecodingError, "Invalid value: #{value}" unless VALID_VALUE_CHARS.match?(value)
|
62
|
+
|
63
|
+
value.strip!
|
64
|
+
end
|
65
|
+
end]
|
66
|
+
|
67
|
+
raise DecodingError, "Invalid empty tags: #{string}" if result.empty? && !string.empty?
|
68
|
+
|
69
|
+
result
|
70
|
+
end
|
71
|
+
|
72
|
+
# An error occurred during distributed tags encoding.
|
73
|
+
# See {#message} for more information.
|
74
|
+
class EncodingError < StandardError
|
75
|
+
end
|
76
|
+
|
77
|
+
# An error occurred during distributed tags decoding.
|
78
|
+
# See {#message} for more information.
|
79
|
+
class DecodingError < StandardError
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -1,8 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
# typed: true
|
2
3
|
|
3
4
|
require_relative 'parser'
|
4
5
|
require_relative 'ext'
|
6
|
+
require_relative '../../metadata/ext'
|
5
7
|
require_relative '../../trace_digest'
|
8
|
+
require_relative '../datadog_tags_codec'
|
6
9
|
|
7
10
|
module Datadog
|
8
11
|
module Tracing
|
@@ -10,40 +13,129 @@ module Datadog
|
|
10
13
|
module Headers
|
11
14
|
# Datadog provides helpers to inject or extract headers for Datadog style headers
|
12
15
|
module Datadog
|
13
|
-
|
16
|
+
class << self
|
17
|
+
include Ext
|
14
18
|
|
15
|
-
|
16
|
-
|
19
|
+
def inject!(digest, env)
|
20
|
+
return if digest.nil?
|
17
21
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
+
env[HTTP_HEADER_TRACE_ID] = digest.trace_id.to_s
|
23
|
+
env[HTTP_HEADER_PARENT_ID] = digest.span_id.to_s
|
24
|
+
env[HTTP_HEADER_SAMPLING_PRIORITY] = digest.trace_sampling_priority.to_s if digest.trace_sampling_priority
|
25
|
+
env[HTTP_HEADER_ORIGIN] = digest.trace_origin.to_s unless digest.trace_origin.nil?
|
22
26
|
|
23
|
-
|
24
|
-
|
27
|
+
inject_tags(digest, env)
|
28
|
+
|
29
|
+
env
|
30
|
+
end
|
31
|
+
|
32
|
+
def extract(env)
|
33
|
+
# Extract values from headers
|
34
|
+
headers = Parser.new(env)
|
35
|
+
trace_id = headers.id(HTTP_HEADER_TRACE_ID)
|
36
|
+
parent_id = headers.id(HTTP_HEADER_PARENT_ID)
|
37
|
+
origin = headers.header(HTTP_HEADER_ORIGIN)
|
38
|
+
sampling_priority = headers.number(HTTP_HEADER_SAMPLING_PRIORITY)
|
39
|
+
|
40
|
+
# Return early if this propagation is not valid
|
41
|
+
# DEV: To be valid we need to have a trace id and a parent id
|
42
|
+
# or when it is a synthetics trace, just the trace id.
|
43
|
+
# DEV: `Parser#id` will not return 0
|
44
|
+
return unless (trace_id && parent_id) || (origin && trace_id)
|
45
|
+
|
46
|
+
trace_distributed_tags = extract_tags(headers)
|
47
|
+
|
48
|
+
# Return new trace headers
|
49
|
+
TraceDigest.new(
|
50
|
+
span_id: parent_id,
|
51
|
+
trace_id: trace_id,
|
52
|
+
trace_origin: origin,
|
53
|
+
trace_sampling_priority: sampling_priority,
|
54
|
+
trace_distributed_tags: trace_distributed_tags,
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# Export trace distributed tags through the `x-datadog-tags` header.
|
61
|
+
#
|
62
|
+
# DEV: This method accesses global state (the active trace) to record its error state as a trace tag.
|
63
|
+
# DEV: This means errors cannot be reported if there's not active span.
|
64
|
+
# DEV: Ideally, we'd have a dedicated error reporting stream for all of ddtrace.
|
65
|
+
# DEV: The same comment applies to the {.extract_tags}.
|
66
|
+
def inject_tags(digest, env)
|
67
|
+
return if digest.trace_distributed_tags.nil? || digest.trace_distributed_tags.empty?
|
68
|
+
|
69
|
+
if ::Datadog.configuration.tracing.x_datadog_tags_max_length <= 0
|
70
|
+
active_trace = Tracing.active_trace
|
71
|
+
active_trace.set_tag('_dd.propagation_error', 'disabled') if active_trace
|
72
|
+
return
|
73
|
+
end
|
74
|
+
|
75
|
+
encoded_tags = DatadogTagsCodec.encode(digest.trace_distributed_tags)
|
76
|
+
|
77
|
+
if encoded_tags.size > ::Datadog.configuration.tracing.x_datadog_tags_max_length
|
78
|
+
active_trace = Tracing.active_trace
|
79
|
+
active_trace.set_tag('_dd.propagation_error', 'inject_max_size') if active_trace
|
80
|
+
|
81
|
+
::Datadog.logger.warn(
|
82
|
+
"Failed to inject x-datadog-tags: tags are too large (size:#{encoded_tags.size} " \
|
83
|
+
"limit:#{::Datadog.configuration.tracing.x_datadog_tags_max_length}). This limit can be configured " \
|
84
|
+
'through the DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH environment variable.'
|
85
|
+
)
|
86
|
+
return
|
87
|
+
end
|
88
|
+
|
89
|
+
env[HTTP_HEADER_TAGS] = encoded_tags
|
90
|
+
rescue => e
|
91
|
+
active_trace = Tracing.active_trace
|
92
|
+
active_trace.set_tag('_dd.propagation_error', 'encoding_error') if active_trace
|
93
|
+
::Datadog.logger.warn(
|
94
|
+
"Failed to inject x-datadog-tags: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}"
|
95
|
+
)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Import `x-datadog-tags` header tags as trace distributed tags.
|
99
|
+
# Only tags that have the `_dd.p.` prefix are processed.
|
100
|
+
def extract_tags(headers)
|
101
|
+
tags_header = headers.header(HTTP_HEADER_TAGS)
|
102
|
+
return unless tags_header
|
103
|
+
|
104
|
+
if ::Datadog.configuration.tracing.x_datadog_tags_max_length <= 0
|
105
|
+
active_trace = Tracing.active_trace
|
106
|
+
active_trace.set_tag('_dd.propagation_error', 'disabled') if active_trace
|
107
|
+
return
|
108
|
+
end
|
109
|
+
|
110
|
+
if tags_header.size > ::Datadog.configuration.tracing.x_datadog_tags_max_length
|
111
|
+
active_trace = Tracing.active_trace
|
112
|
+
active_trace.set_tag('_dd.propagation_error', 'extract_max_size') if active_trace
|
113
|
+
|
114
|
+
::Datadog.logger.warn(
|
115
|
+
"Failed to extract x-datadog-tags: tags are too large (size:#{tags_header.size} " \
|
116
|
+
"limit:#{::Datadog.configuration.tracing.x_datadog_tags_max_length}). This limit can be configured " \
|
117
|
+
'through the DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH environment variable.'
|
118
|
+
)
|
119
|
+
return
|
120
|
+
end
|
121
|
+
|
122
|
+
tags = DatadogTagsCodec.decode(tags_header)
|
123
|
+
# Only extract keys with the expected Datadog prefix
|
124
|
+
tags.select! do |key, _|
|
125
|
+
key.start_with?(Tracing::Metadata::Ext::Distributed::TAGS_PREFIX) && key != EXCLUDED_TAG
|
126
|
+
end
|
127
|
+
tags
|
128
|
+
rescue => e
|
129
|
+
active_trace = Tracing.active_trace
|
130
|
+
active_trace.set_tag('_dd.propagation_error', 'decoding_error') if active_trace
|
131
|
+
::Datadog.logger.warn(
|
132
|
+
"Failed to extract x-datadog-tags: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}"
|
133
|
+
)
|
134
|
+
end
|
25
135
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
trace_id = headers.id(HTTP_HEADER_TRACE_ID)
|
30
|
-
parent_id = headers.id(HTTP_HEADER_PARENT_ID)
|
31
|
-
origin = headers.header(HTTP_HEADER_ORIGIN)
|
32
|
-
sampling_priority = headers.number(HTTP_HEADER_SAMPLING_PRIORITY)
|
33
|
-
|
34
|
-
# Return early if this propagation is not valid
|
35
|
-
# DEV: To be valid we need to have a trace id and a parent id
|
36
|
-
# or when it is a synthetics trace, just the trace id.
|
37
|
-
# DEV: `Parser#id` will not return 0
|
38
|
-
return unless (trace_id && parent_id) || (origin && trace_id)
|
39
|
-
|
40
|
-
# Return new trace headers
|
41
|
-
TraceDigest.new(
|
42
|
-
span_id: parent_id,
|
43
|
-
trace_id: trace_id,
|
44
|
-
trace_origin: origin,
|
45
|
-
trace_sampling_priority: sampling_priority
|
46
|
-
)
|
136
|
+
# We want to exclude tags that we don't want to propagate downstream.
|
137
|
+
EXCLUDED_TAG = '_dd.p.upstream_services'
|
138
|
+
private_constant :EXCLUDED_TAG
|
47
139
|
end
|
48
140
|
end
|
49
141
|
end
|
@@ -12,6 +12,8 @@ module Datadog
|
|
12
12
|
HTTP_HEADER_PARENT_ID = 'x-datadog-parent-id'.freeze
|
13
13
|
HTTP_HEADER_SAMPLING_PRIORITY = 'x-datadog-sampling-priority'.freeze
|
14
14
|
HTTP_HEADER_ORIGIN = 'x-datadog-origin'.freeze
|
15
|
+
# Distributed trace-level tags
|
16
|
+
HTTP_HEADER_TAGS = 'x-datadog-tags'.freeze
|
15
17
|
|
16
18
|
# B3 headers used for distributed tracing
|
17
19
|
B3_HEADER_TRACE_ID = 'x-b3-traceid'.freeze
|
@@ -48,7 +48,7 @@ module Datadog
|
|
48
48
|
# Single Span Sampling has chosen to keep this span
|
49
49
|
# regardless of the trace-level sampling decision
|
50
50
|
def single_sampled?(span)
|
51
|
-
span.get_metric(Sampling::Span::Ext::TAG_MECHANISM) == Sampling::
|
51
|
+
span.get_metric(Sampling::Span::Ext::TAG_MECHANISM) == Sampling::Ext::Mechanism::SPAN_SAMPLING_RATE
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
@@ -45,8 +45,16 @@ module Datadog
|
|
45
45
|
# @public_api
|
46
46
|
# Tags related to distributed tracing
|
47
47
|
module Distributed
|
48
|
+
# What mechanism was used to make this trace's sampling decision.
|
49
|
+
# @see Datadog::Tracing::Sampling::Ext::Mechanism
|
50
|
+
TAG_DECISION_MAKER = '_dd.p.dm'
|
51
|
+
|
48
52
|
TAG_ORIGIN = '_dd.origin'
|
49
53
|
TAG_SAMPLING_PRIORITY = '_sampling_priority_v1'
|
54
|
+
|
55
|
+
# Trace tags with this prefix will propagate from a trace through distributed tracing.
|
56
|
+
# Distributed headers tags with this prefix will be injected into the active trace.
|
57
|
+
TAGS_PREFIX = '_dd.p.'
|
50
58
|
end
|
51
59
|
|
52
60
|
# @public_api
|
@@ -23,7 +23,15 @@ module Datadog
|
|
23
23
|
Configuration::Ext::Distributed::PROPAGATION_STYLE_DATADOG => Distributed::Headers::Datadog
|
24
24
|
}.freeze
|
25
25
|
|
26
|
-
# inject!
|
26
|
+
# inject! populates the env with span ID, trace ID and sampling priority
|
27
|
+
#
|
28
|
+
# DEV-2.0: inject! should work without arguments, injecting the active_trace's digest
|
29
|
+
# DEV-2.0: and returning a new Hash with the injected headers.
|
30
|
+
# DEV-2.0: inject! should also accept either a `trace` or a `digest`, as a `trace`
|
31
|
+
# DEV-2.0: argument is the common use case, but also allows us to set error tags in the `trace`
|
32
|
+
# DEV-2.0: if needed.
|
33
|
+
# DEV-2.0: Ideally, we'd have a separate stream to report tracer errors and never
|
34
|
+
# DEV-2.0: touch the active span.
|
27
35
|
def self.inject!(digest, env)
|
28
36
|
# Prevent propagation from being attempted if trace headers provided are nil.
|
29
37
|
if digest.nil?
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# typed: strict
|
2
4
|
|
3
5
|
module Datadog
|
@@ -21,6 +23,35 @@ module Datadog
|
|
21
23
|
# through the {Datadog::Tracing::Sampling::RuleSampler}.
|
22
24
|
USER_KEEP = 2
|
23
25
|
end
|
26
|
+
|
27
|
+
# List of what mechanism was used to make the trace-level sampling decision.
|
28
|
+
module Mechanism
|
29
|
+
# Single Span Sampled.
|
30
|
+
SPAN_SAMPLING_RATE = 8
|
31
|
+
end
|
32
|
+
|
33
|
+
# List of how the decision was made for the trace-level sampling.
|
34
|
+
#
|
35
|
+
# These values used to populate the {Datadog::Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER} tag.
|
36
|
+
#
|
37
|
+
# The decision has two parts, separated by a `-`:
|
38
|
+
# `part1-sampling_mechanism`. `part1` is currently not populated, thus
|
39
|
+
# this tag is currently formatted as `"-sampling_mechanism"`.
|
40
|
+
module Decision
|
41
|
+
# Used before the tracer receives any rates from agent and there are no rules configured.
|
42
|
+
DEFAULT = '-0'
|
43
|
+
# The sampling rate received in the agent's http response.
|
44
|
+
AGENT_RATE = '-1'
|
45
|
+
# Sampling rule or sampling rate based on tracer config.
|
46
|
+
TRACE_SAMPLING_RULE = '-3'
|
47
|
+
# User directly sets sampling priority via {Tracing.reject!} or {Tracing.keep!},
|
48
|
+
# or by a custom sampler implementation.
|
49
|
+
MANUAL = '-4'
|
50
|
+
# Formerly AppSec.
|
51
|
+
ASM = '-5'
|
52
|
+
# Single Span Sampled.
|
53
|
+
SPAN_SAMPLING_RATE = '-8'
|
54
|
+
end
|
24
55
|
end
|
25
56
|
end
|
26
57
|
end
|
@@ -21,17 +21,26 @@ module Datadog
|
|
21
21
|
|
22
22
|
def initialize(opts = {})
|
23
23
|
@pre_sampler = opts[:base_sampler] || AllSampler.new
|
24
|
-
@priority_sampler = opts[:post_sampler] || RateByServiceSampler.new
|
24
|
+
@priority_sampler = opts[:post_sampler] || RateByServiceSampler.new(decision: Sampling::Ext::Decision::AGENT_RATE)
|
25
25
|
end
|
26
26
|
|
27
27
|
def sample?(trace)
|
28
28
|
@pre_sampler.sample?(trace)
|
29
29
|
end
|
30
30
|
|
31
|
+
# DEV-2.0:We should get rid of this complicated interaction between @pre_sampler and @priority_sampler.
|
32
|
+
# DEV-2.0:If the user wants to configure a custom sampler, we should only allow them to provide a complete
|
33
|
+
# DEV-2.0:sampling suite, not having this convoluted support for mixing arbitrary provided samplers in
|
34
|
+
# DEV-2.0:the PrioritySampler. Ideally, the PrioritySampler is only used by Datadog.
|
35
|
+
# DEV-2.0:There are too many edge cases and combinations to work around currently in this class.
|
31
36
|
def sample!(trace)
|
37
|
+
# The priority that was set before the sampler ran.
|
38
|
+
# This comes from distributed tracing priority propagation.
|
39
|
+
distributed_sampling_priority = priority_assigned?(trace)
|
40
|
+
|
32
41
|
# If pre-sampling is configured, do it first. (By default, this will sample at 100%.)
|
33
42
|
# NOTE: Pre-sampling at rates < 100% may result in partial traces; not recommended.
|
34
|
-
trace.sampled = pre_sample?(trace) ? @pre_sampler.sample!(trace) : true
|
43
|
+
trace.sampled = pre_sample?(trace) ? preserving_priority_sampling(trace) { @pre_sampler.sample!(trace) } : true
|
35
44
|
|
36
45
|
if trace.sampled?
|
37
46
|
# If priority sampling has already been applied upstream, use that value.
|
@@ -53,11 +62,27 @@ module Datadog
|
|
53
62
|
end
|
54
63
|
|
55
64
|
trace.sampled?
|
65
|
+
ensure
|
66
|
+
if trace.sampling_priority && trace.sampling_priority > 0
|
67
|
+
# Don't modify decision if priority was set upstream.
|
68
|
+
if !distributed_sampling_priority && !trace.has_tag?(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER)
|
69
|
+
# If no sampling priority being assigned at this point, a custom
|
70
|
+
# sampler implementation is configured: this means the user has
|
71
|
+
# full control over the sampling decision.
|
72
|
+
trace.set_tag(
|
73
|
+
Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER,
|
74
|
+
Sampling::Ext::Decision::MANUAL
|
75
|
+
)
|
76
|
+
end
|
77
|
+
else
|
78
|
+
# The sampler decided to not keep this span, removing sampling decision.
|
79
|
+
trace.clear_tag(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER)
|
80
|
+
end
|
56
81
|
end
|
57
82
|
|
58
83
|
# (see Datadog::Tracing::Sampling::RateByServiceSampler#update)
|
59
|
-
def update(rate_by_service)
|
60
|
-
@priority_sampler.update(rate_by_service)
|
84
|
+
def update(rate_by_service, decision: nil)
|
85
|
+
@priority_sampler.update(rate_by_service, decision: decision)
|
61
86
|
end
|
62
87
|
|
63
88
|
private
|
@@ -83,6 +108,23 @@ module Datadog
|
|
83
108
|
end
|
84
109
|
end
|
85
110
|
|
111
|
+
# Ensures the trace's priority sampling decision is not changed by the @pre_sampler.
|
112
|
+
# The @pre_sampler should only change `trace.sampled`.
|
113
|
+
def preserving_priority_sampling(trace)
|
114
|
+
sampling_priority = trace.sampling_priority
|
115
|
+
sampling_decision = trace.get_tag(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER)
|
116
|
+
|
117
|
+
yield.tap do
|
118
|
+
trace.sampling_priority = sampling_priority
|
119
|
+
|
120
|
+
if sampling_decision
|
121
|
+
trace.set_tag(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER, sampling_decision)
|
122
|
+
else
|
123
|
+
trace.clear_tag(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
86
128
|
# Ensures the trace is always propagated to the writer and that
|
87
129
|
# the sample rate metric represents the true client-side sampling.
|
88
130
|
def preserving_sampling(trace)
|
@@ -12,7 +12,7 @@ module Datadog
|
|
12
12
|
attr_reader \
|
13
13
|
:default_key
|
14
14
|
|
15
|
-
def initialize(default_key, default_rate = 1.0, &block)
|
15
|
+
def initialize(default_key, default_rate = 1.0, decision: nil, &block)
|
16
16
|
super()
|
17
17
|
|
18
18
|
raise ArgumentError, 'No resolver given!' unless block
|
@@ -22,7 +22,7 @@ module Datadog
|
|
22
22
|
@mutex = Mutex.new
|
23
23
|
@samplers = {}
|
24
24
|
|
25
|
-
set_rate(default_key, default_rate)
|
25
|
+
set_rate(default_key, default_rate, decision)
|
26
26
|
end
|
27
27
|
|
28
28
|
def resolve(trace)
|
@@ -57,15 +57,15 @@ module Datadog
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
-
def update(key, rate)
|
60
|
+
def update(key, rate, decision: nil)
|
61
61
|
@mutex.synchronize do
|
62
|
-
set_rate(key, rate)
|
62
|
+
set_rate(key, rate, decision)
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
-
def update_all(rate_by_key)
|
66
|
+
def update_all(rate_by_key, decision: nil)
|
67
67
|
@mutex.synchronize do
|
68
|
-
rate_by_key.each { |key, rate| set_rate(key, rate) }
|
68
|
+
rate_by_key.each { |key, rate| set_rate(key, rate, decision) }
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
@@ -87,9 +87,8 @@ module Datadog
|
|
87
87
|
|
88
88
|
private
|
89
89
|
|
90
|
-
def set_rate(key, rate)
|
91
|
-
@samplers[key]
|
92
|
-
@samplers[key].sample_rate = rate
|
90
|
+
def set_rate(key, rate, decision)
|
91
|
+
@samplers[key] = RateSampler.new(rate, decision: decision)
|
93
92
|
end
|
94
93
|
end
|
95
94
|
end
|
@@ -11,17 +11,23 @@ module Datadog
|
|
11
11
|
class RateByServiceSampler < RateByKeySampler
|
12
12
|
DEFAULT_KEY = 'service:,env:'.freeze
|
13
13
|
|
14
|
-
def initialize(default_rate = 1.0,
|
15
|
-
super(
|
16
|
-
|
14
|
+
def initialize(default_rate = 1.0, env: nil, decision: Datadog::Tracing::Sampling::Ext::Decision::DEFAULT)
|
15
|
+
super(
|
16
|
+
DEFAULT_KEY,
|
17
|
+
default_rate,
|
18
|
+
decision: decision,
|
19
|
+
&method(:key_for)
|
20
|
+
)
|
21
|
+
|
22
|
+
@env = env
|
17
23
|
end
|
18
24
|
|
19
|
-
def update(rate_by_service)
|
25
|
+
def update(rate_by_service, decision: nil)
|
20
26
|
# Remove any old services
|
21
27
|
delete_if { |key, _| key != DEFAULT_KEY && !rate_by_service.key?(key) }
|
22
28
|
|
23
29
|
# Update each service rate
|
24
|
-
update_all(rate_by_service)
|
30
|
+
update_all(rate_by_service, decision: decision)
|
25
31
|
|
26
32
|
# Emit metric for service cache size
|
27
33
|
Datadog.health_metrics.sampling_service_cache_length(length)
|
@@ -29,6 +35,24 @@ module Datadog
|
|
29
35
|
|
30
36
|
private
|
31
37
|
|
38
|
+
# DEV: Creating a string on every trace to perform a single Hash lookup is expensive.
|
39
|
+
#
|
40
|
+
# Using 2 nested hashes: 1 for env and 1 for service is the fastest option.
|
41
|
+
# This approach requires large API changes to `RateByKeySampler`.
|
42
|
+
#
|
43
|
+
# Reducing the interpolated string size, by using a 1 character separator,
|
44
|
+
# is also measurably faster than the current method. This approach does not
|
45
|
+
# require changes to `RateByKeySampler`.
|
46
|
+
#
|
47
|
+
# Keep in mind that these changes also require changes to `#update`.
|
48
|
+
#
|
49
|
+
# Comparison:
|
50
|
+
# 2 nested hashes: `service_hash.fetch(service, {}).fetch(env, default_rate)`
|
51
|
+
# 7730045 i/s
|
52
|
+
# 1 char separator: `hash.fetch("#{service}\0#{env}", default_rate)`
|
53
|
+
# 4302801 i/s - 1.80x slower
|
54
|
+
# current: `hash.fetch("service:#{service},env:#{env}", default_rate)`
|
55
|
+
# 2720459 i/s - 2.84x slower
|
32
56
|
def key_for(trace)
|
33
57
|
# Resolve env dynamically, if Proc is given.
|
34
58
|
env = @env.is_a?(Proc) ? @env.call : @env
|
@@ -30,7 +30,7 @@ module Datadog
|
|
30
30
|
# DEV-2.0: sampler = RateSampler.new
|
31
31
|
# DEV-2.0: sampler.sample_rate = sample_rate
|
32
32
|
# DEV-2.0: ```
|
33
|
-
def initialize(sample_rate = 1.0)
|
33
|
+
def initialize(sample_rate = 1.0, decision: nil)
|
34
34
|
super()
|
35
35
|
|
36
36
|
unless sample_rate > 0.0 && sample_rate <= 1.0
|
@@ -39,6 +39,8 @@ module Datadog
|
|
39
39
|
end
|
40
40
|
|
41
41
|
self.sample_rate = sample_rate
|
42
|
+
|
43
|
+
@decision = decision
|
42
44
|
end
|
43
45
|
|
44
46
|
def sample_rate(*_)
|
@@ -56,8 +58,13 @@ module Datadog
|
|
56
58
|
|
57
59
|
def sample!(trace)
|
58
60
|
sampled = trace.sampled = sample?(trace)
|
59
|
-
|
60
|
-
sampled
|
61
|
+
|
62
|
+
return false unless sampled
|
63
|
+
|
64
|
+
trace.sample_rate = @sample_rate
|
65
|
+
trace.set_tag(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER, @decision) if @decision
|
66
|
+
|
67
|
+
true
|
61
68
|
end
|
62
69
|
end
|
63
70
|
end
|