ddtrace 1.4.1 → 1.6.1
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 +144 -1
- data/LICENSE-3rdparty.csv +1 -0
- 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 -4
- 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/assets/waf_rules/recommended.json +1169 -275
- data/lib/datadog/appsec/assets/waf_rules/risky.json +78 -78
- data/lib/datadog/appsec/assets/waf_rules/strict.json +278 -88
- data/lib/datadog/appsec/configuration/settings.rb +0 -2
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +25 -20
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +11 -11
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +11 -11
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +11 -11
- data/lib/datadog/appsec/contrib/rack/request.rb +3 -0
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +46 -19
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +7 -6
- data/lib/datadog/appsec/contrib/rails/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +11 -11
- data/lib/datadog/appsec/contrib/rails/request.rb +3 -0
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +14 -12
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +11 -11
- data/lib/datadog/appsec/event.rb +6 -10
- data/lib/datadog/appsec/instrumentation/gateway.rb +16 -2
- data/lib/datadog/appsec/processor.rb +18 -2
- data/lib/datadog/ci/ext/environment.rb +16 -4
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +0 -3
- data/lib/datadog/core/configuration/components.rb +28 -16
- data/lib/datadog/core/configuration/settings.rb +127 -8
- data/lib/datadog/core/configuration.rb +1 -1
- data/lib/datadog/core/diagnostics/environment_logger.rb +5 -1
- data/lib/datadog/core/header_collection.rb +41 -0
- data/lib/datadog/core/telemetry/collector.rb +0 -2
- data/lib/datadog/core/utils/compression.rb +5 -1
- data/lib/datadog/core/workers/async.rb +0 -2
- data/lib/datadog/core.rb +0 -54
- data/lib/datadog/opentracer/tracer.rb +4 -6
- 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/collectors/old_stack.rb +1 -1
- data/lib/datadog/profiling/exporter.rb +2 -4
- data/lib/datadog/profiling/http_transport.rb +1 -1
- data/lib/datadog/profiling.rb +1 -1
- data/lib/datadog/tracing/client_ip.rb +164 -0
- data/lib/datadog/tracing/configuration/ext.rb +14 -0
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +2 -0
- data/lib/datadog/tracing/contrib/aws/services.rb +0 -2
- 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 -2
- 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 +25 -0
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +3 -2
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +0 -2
- data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +1 -1
- 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 +38 -21
- 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/header_collection.rb +35 -0
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +105 -43
- 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/integration.rb +2 -1
- data/lib/datadog/tracing/contrib/redis/patcher.rb +40 -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/utils/quantization/http.rb +92 -10
- 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 +57 -35
- data/lib/datadog/tracing/metadata/ext.rb +11 -9
- data/lib/datadog/tracing/metadata/tagging.rb +9 -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_limiter.rb +3 -0
- data/lib/datadog/tracing/sampling/rate_sampler.rb +20 -3
- data/lib/datadog/tracing/sampling/rule_sampler.rb +4 -3
- data/lib/datadog/tracing/sampling/span/ext.rb +25 -0
- data/lib/datadog/tracing/sampling/span/matcher.rb +9 -0
- data/lib/datadog/tracing/sampling/span/rule.rb +82 -0
- data/lib/datadog/tracing/sampling/span/rule_parser.rb +104 -0
- data/lib/datadog/tracing/sampling/span/sampler.rb +75 -0
- data/lib/datadog/tracing/span_operation.rb +0 -2
- data/lib/datadog/tracing/trace_digest.rb +3 -0
- data/lib/datadog/tracing/trace_operation.rb +32 -3
- data/lib/datadog/tracing/trace_segment.rb +7 -2
- data/lib/datadog/tracing/tracer.rb +34 -6
- data/lib/datadog/tracing/writer.rb +7 -0
- data/lib/ddtrace/transport/trace_formatter.rb +7 -0
- data/lib/ddtrace/transport/traces.rb +3 -1
- data/lib/ddtrace/version.rb +1 -1
- metadata +36 -18
- 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
|
@@ -3,71 +3,93 @@
|
|
|
3
3
|
module Datadog
|
|
4
4
|
module Tracing
|
|
5
5
|
module Flush
|
|
6
|
-
# Consumes
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
# Consumes and returns a {TraceSegment} to be flushed, from
|
|
7
|
+
# the provided {TraceSegment}.
|
|
8
|
+
#
|
|
9
|
+
# Only finished spans are consumed. Any spans consumed are
|
|
10
|
+
# removed from +trace_op+ as a side effect. Unfinished spans are
|
|
11
|
+
# unaffected.
|
|
12
|
+
#
|
|
13
|
+
# @abstract
|
|
14
|
+
class Base
|
|
15
|
+
# Consumes and returns a {TraceSegment} to be flushed, from
|
|
16
|
+
# the provided {TraceSegment}.
|
|
10
17
|
#
|
|
11
|
-
#
|
|
18
|
+
# Only finished spans are consumed. Any spans consumed are
|
|
19
|
+
# removed from +trace_op+ as a side effect. Unfinished spans are
|
|
20
|
+
# unaffected.
|
|
12
21
|
#
|
|
22
|
+
# @param [TraceOperation] trace_op
|
|
13
23
|
# @return [TraceSegment] trace to be flushed, or +nil+ if the trace is not finished
|
|
14
24
|
def consume!(trace_op)
|
|
15
|
-
return unless
|
|
25
|
+
return unless flush?(trace_op)
|
|
16
26
|
|
|
17
27
|
get_trace(trace_op)
|
|
18
28
|
end
|
|
19
29
|
|
|
20
|
-
|
|
21
|
-
|
|
30
|
+
# Should we consume spans from the +trace_op+?
|
|
31
|
+
# @abstract
|
|
32
|
+
def flush?(trace_op)
|
|
33
|
+
raise NotImplementedError
|
|
22
34
|
end
|
|
23
35
|
|
|
24
36
|
protected
|
|
25
37
|
|
|
38
|
+
# Consumes all finished spans from trace.
|
|
39
|
+
# @return [TraceSegment]
|
|
26
40
|
def get_trace(trace_op)
|
|
27
|
-
trace_op.flush!
|
|
41
|
+
trace_op.flush! do |spans|
|
|
42
|
+
spans.select! { |span| single_sampled?(span) } unless trace_op.sampled?
|
|
43
|
+
|
|
44
|
+
spans
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Single Span Sampling has chosen to keep this span
|
|
49
|
+
# regardless of the trace-level sampling decision
|
|
50
|
+
def single_sampled?(span)
|
|
51
|
+
span.get_metric(Sampling::Span::Ext::TAG_MECHANISM) == Sampling::Ext::Mechanism::SPAN_SAMPLING_RATE
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Consumes and returns completed traces (where all spans have finished),
|
|
56
|
+
# if any, from the provided +trace_op+.
|
|
57
|
+
#
|
|
58
|
+
# Spans consumed are removed from +trace_op+ as a side effect.
|
|
59
|
+
class Finished < Base
|
|
60
|
+
# Are all spans finished?
|
|
61
|
+
def flush?(trace_op)
|
|
62
|
+
trace_op && trace_op.finished?
|
|
28
63
|
end
|
|
29
64
|
end
|
|
30
65
|
|
|
31
|
-
#
|
|
32
|
-
|
|
66
|
+
# Consumes and returns completed or partially completed
|
|
67
|
+
# traces from the provided +trace_op+, if any.
|
|
68
|
+
#
|
|
69
|
+
# Partial trace flushing avoids large traces residing in memory for too long.
|
|
70
|
+
#
|
|
71
|
+
# Partially completed traces, where not all spans have finished,
|
|
72
|
+
# will only be returned if there are at least
|
|
73
|
+
# +@min_spans_for_partial+ finished spans.
|
|
74
|
+
#
|
|
75
|
+
# Spans consumed are removed from +trace_op+ as a side effect.
|
|
76
|
+
class Partial < Base
|
|
33
77
|
# Start flushing partial trace after this many active spans in one trace
|
|
34
78
|
DEFAULT_MIN_SPANS_FOR_PARTIAL_FLUSH = 500
|
|
35
79
|
|
|
36
80
|
attr_reader :min_spans_for_partial
|
|
37
81
|
|
|
38
82
|
def initialize(options = {})
|
|
83
|
+
super()
|
|
39
84
|
@min_spans_for_partial = options.fetch(:min_spans_before_partial_flush, DEFAULT_MIN_SPANS_FOR_PARTIAL_FLUSH)
|
|
40
85
|
end
|
|
41
86
|
|
|
42
|
-
|
|
43
|
-
# traces from the provided +trace_op+, if any.
|
|
44
|
-
#
|
|
45
|
-
# Partially completed traces, where not all spans have finished,
|
|
46
|
-
# will only be returned if there are at least
|
|
47
|
-
# +@min_spans_for_partial+ finished spans.
|
|
48
|
-
#
|
|
49
|
-
# Any spans consumed are removed from +trace_op+ as a side effect.
|
|
50
|
-
#
|
|
51
|
-
# @return [TraceSegment] partial or complete trace to be flushed, or +nil+ if no spans are finished
|
|
52
|
-
def consume!(trace_op)
|
|
53
|
-
return unless partial_flush?(trace_op)
|
|
54
|
-
|
|
55
|
-
get_trace(trace_op)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def partial_flush?(trace_op)
|
|
59
|
-
return false unless trace_op.sampled?
|
|
87
|
+
def flush?(trace_op)
|
|
60
88
|
return true if trace_op.finished?
|
|
61
89
|
return false if trace_op.finished_span_count < @min_spans_for_partial
|
|
62
90
|
|
|
63
91
|
true
|
|
64
92
|
end
|
|
65
|
-
|
|
66
|
-
protected
|
|
67
|
-
|
|
68
|
-
def get_trace(trace_op)
|
|
69
|
-
trace_op.flush!
|
|
70
|
-
end
|
|
71
93
|
end
|
|
72
94
|
end
|
|
73
95
|
end
|
|
@@ -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
|
|
@@ -63,11 +71,14 @@ module Datadog
|
|
|
63
71
|
TAG_BASE_URL = 'http.base_url'
|
|
64
72
|
TAG_METHOD = 'http.method'
|
|
65
73
|
TAG_STATUS_CODE = 'http.status_code'
|
|
74
|
+
TAG_USER_AGENT = 'http.useragent'
|
|
66
75
|
TAG_URL = 'http.url'
|
|
67
76
|
TYPE_INBOUND = AppTypes::TYPE_WEB.freeze
|
|
68
77
|
TYPE_OUTBOUND = 'http'
|
|
69
78
|
TYPE_PROXY = 'proxy'
|
|
70
79
|
TYPE_TEMPLATE = 'template'
|
|
80
|
+
TAG_CLIENT_IP = 'http.client_ip'
|
|
81
|
+
HEADER_USER_AGENT = 'User-Agent'
|
|
71
82
|
|
|
72
83
|
# General header functionality
|
|
73
84
|
module Headers
|
|
@@ -153,15 +164,6 @@ module Datadog
|
|
|
153
164
|
TAG_QUERY = 'sql.query'
|
|
154
165
|
end
|
|
155
166
|
|
|
156
|
-
# @public_api
|
|
157
|
-
module DB
|
|
158
|
-
TAG_INSTANCE = 'db.instance'
|
|
159
|
-
TAG_USER = 'db.user'
|
|
160
|
-
TAG_SYSTEM = 'db.system'
|
|
161
|
-
TAG_STATEMENT = 'db.statement'
|
|
162
|
-
TAG_ROW_COUNT = 'db.row_count'
|
|
163
|
-
end
|
|
164
|
-
|
|
165
167
|
# @public_api
|
|
166
168
|
module SpanKind
|
|
167
169
|
TAG_SERVER = 'server'
|
|
@@ -65,6 +65,15 @@ module Datadog
|
|
|
65
65
|
tags.each { |k, v| set_tag(k, v) }
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
+
# Returns true if the provided `tag` was set to a non-nil value.
|
|
69
|
+
# False otherwise.
|
|
70
|
+
#
|
|
71
|
+
# @param [String] tag the tag or metric to check for presence
|
|
72
|
+
# @return [Boolean] if the tag is present and not nil
|
|
73
|
+
def has_tag?(tag) # rubocop:disable Naming/PredicateName
|
|
74
|
+
!get_tag(tag).nil? # nil is considered not present, thus we can't use `Hash#has_key?`
|
|
75
|
+
end
|
|
76
|
+
|
|
68
77
|
# This method removes a tag for the given key.
|
|
69
78
|
def clear_tag(key)
|
|
70
79
|
meta.delete(key)
|
|
@@ -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
|
|
@@ -39,6 +39,9 @@ module Datadog
|
|
|
39
39
|
def initialize(rate, max_tokens = rate)
|
|
40
40
|
super()
|
|
41
41
|
|
|
42
|
+
raise ArgumentError, "rate must be a number: #{rate}" unless rate.is_a?(Numeric)
|
|
43
|
+
raise ArgumentError, "max_tokens must be a number: #{max_tokens}" unless max_tokens.is_a?(Numeric)
|
|
44
|
+
|
|
42
45
|
@rate = rate
|
|
43
46
|
@max_tokens = max_tokens
|
|
44
47
|
|
|
@@ -20,7 +20,17 @@ module Datadog
|
|
|
20
20
|
# * +sample_rate+: the sample rate as a {Float} between 0.0 and 1.0. 0.0
|
|
21
21
|
# means that no trace will be sampled; 1.0 means that all traces will be
|
|
22
22
|
# sampled.
|
|
23
|
-
|
|
23
|
+
#
|
|
24
|
+
# DEV-2.0: Allow for `sample_rate` zero (drop all) to be allowed. This eases
|
|
25
|
+
# DEV-2.0: usage for all internal users of the {RateSampler} class: both
|
|
26
|
+
# DEV-2.0: RuleSampler and Single Span Sampling leverage the RateSampler, but want
|
|
27
|
+
# DEV-2.0: `sample_rate` zero to mean "drop all". They work around this by hard-
|
|
28
|
+
# DEV-2.0: setting the `sample_rate` to zero like so:
|
|
29
|
+
# DEV-2.0: ```
|
|
30
|
+
# DEV-2.0: sampler = RateSampler.new
|
|
31
|
+
# DEV-2.0: sampler.sample_rate = sample_rate
|
|
32
|
+
# DEV-2.0: ```
|
|
33
|
+
def initialize(sample_rate = 1.0, decision: nil)
|
|
24
34
|
super()
|
|
25
35
|
|
|
26
36
|
unless sample_rate > 0.0 && sample_rate <= 1.0
|
|
@@ -29,6 +39,8 @@ module Datadog
|
|
|
29
39
|
end
|
|
30
40
|
|
|
31
41
|
self.sample_rate = sample_rate
|
|
42
|
+
|
|
43
|
+
@decision = decision
|
|
32
44
|
end
|
|
33
45
|
|
|
34
46
|
def sample_rate(*_)
|
|
@@ -46,8 +58,13 @@ module Datadog
|
|
|
46
58
|
|
|
47
59
|
def sample!(trace)
|
|
48
60
|
sampled = trace.sampled = sample?(trace)
|
|
49
|
-
|
|
50
|
-
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
|
|
51
68
|
end
|
|
52
69
|
end
|
|
53
70
|
end
|
|
@@ -48,7 +48,7 @@ module Datadog
|
|
|
48
48
|
nil
|
|
49
49
|
else
|
|
50
50
|
# TODO: Simplify .tags access, as `Tracer#tags` can't be arbitrarily changed anymore
|
|
51
|
-
RateByServiceSampler.new(1.0, env: -> { Tracing.send(:tracer).tags[
|
|
51
|
+
RateByServiceSampler.new(1.0, env: -> { Tracing.send(:tracer).tags['env'] })
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
|
|
@@ -76,10 +76,10 @@ module Datadog
|
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
# @!visibility private
|
|
79
|
-
def update(*args)
|
|
79
|
+
def update(*args, **kwargs)
|
|
80
80
|
return false unless @default_sampler.respond_to?(:update)
|
|
81
81
|
|
|
82
|
-
@default_sampler.update(*args)
|
|
82
|
+
@default_sampler.update(*args, **kwargs)
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
private
|
|
@@ -100,6 +100,7 @@ module Datadog
|
|
|
100
100
|
rate_limiter.allow?(1).tap do |allowed|
|
|
101
101
|
set_priority(trace, allowed)
|
|
102
102
|
set_limiter_metrics(trace, rate_limiter.effective_rate)
|
|
103
|
+
trace.set_tag(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER, Ext::Decision::TRACE_SAMPLING_RULE)
|
|
103
104
|
end
|
|
104
105
|
rescue StandardError => e
|
|
105
106
|
Datadog.logger.error(
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module Tracing
|
|
5
|
+
module Sampling
|
|
6
|
+
module Span
|
|
7
|
+
# Single Span Sampling constants.
|
|
8
|
+
module Ext
|
|
9
|
+
# Accept all spans (100% retention).
|
|
10
|
+
DEFAULT_SAMPLE_RATE = 1.0
|
|
11
|
+
# Unlimited.
|
|
12
|
+
# @see Datadog::Tracing::Sampling::TokenBucket
|
|
13
|
+
DEFAULT_MAX_PER_SECOND = -1
|
|
14
|
+
|
|
15
|
+
# Sampling decision method used to come to the sampling decision for this span
|
|
16
|
+
TAG_MECHANISM = '_dd.span_sampling.mechanism'
|
|
17
|
+
# Sampling rate applied to this span, if a rule applies
|
|
18
|
+
TAG_RULE_RATE = '_dd.span_sampling.rule_rate'
|
|
19
|
+
# Rate limit configured for this span, if a rule applies
|
|
20
|
+
TAG_MAX_PER_SECOND = '_dd.span_sampling.max_per_second'
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -6,6 +6,8 @@ module Datadog
|
|
|
6
6
|
module Span
|
|
7
7
|
# Checks if a span conforms to a matching criteria.
|
|
8
8
|
class Matcher
|
|
9
|
+
attr_reader :name, :service
|
|
10
|
+
|
|
9
11
|
# Pattern that matches any string
|
|
10
12
|
MATCH_ALL_PATTERN = '*'
|
|
11
13
|
|
|
@@ -54,6 +56,13 @@ module Datadog
|
|
|
54
56
|
end
|
|
55
57
|
end
|
|
56
58
|
|
|
59
|
+
def ==(other)
|
|
60
|
+
return super unless other.is_a?(Matcher)
|
|
61
|
+
|
|
62
|
+
name == other.name &&
|
|
63
|
+
service == other.service
|
|
64
|
+
end
|
|
65
|
+
|
|
57
66
|
private
|
|
58
67
|
|
|
59
68
|
# @param pattern [String]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'ext'
|
|
4
|
+
|
|
5
|
+
module Datadog
|
|
6
|
+
module Tracing
|
|
7
|
+
module Sampling
|
|
8
|
+
module Span
|
|
9
|
+
# Span sampling rule that applies a sampling rate if the span
|
|
10
|
+
# matches the provided {Matcher}.
|
|
11
|
+
# Additionally, a rate limiter is also applied.
|
|
12
|
+
#
|
|
13
|
+
# If a span does not conform to the matcher, no changes are made.
|
|
14
|
+
class Rule
|
|
15
|
+
attr_reader :matcher, :sample_rate, :rate_limit
|
|
16
|
+
|
|
17
|
+
# Creates a new span sampling rule.
|
|
18
|
+
#
|
|
19
|
+
# @param [Sampling::Span::Matcher] matcher whether this rule applies to a specific span
|
|
20
|
+
# @param [Float] sample_rate span sampling ratio, between 0.0 (0%) and 1.0 (100%).
|
|
21
|
+
# @param [Numeric] rate_limit maximum number of spans sampled per second. Negative numbers mean unlimited spans.
|
|
22
|
+
def initialize(
|
|
23
|
+
matcher,
|
|
24
|
+
sample_rate: Span::Ext::DEFAULT_SAMPLE_RATE,
|
|
25
|
+
rate_limit: Span::Ext::DEFAULT_MAX_PER_SECOND
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
@matcher = matcher
|
|
29
|
+
@sample_rate = sample_rate
|
|
30
|
+
@rate_limit = rate_limit
|
|
31
|
+
|
|
32
|
+
@sampler = Sampling::RateSampler.new
|
|
33
|
+
# Set the sample_rate outside of the initializer to allow for
|
|
34
|
+
# zero to be a "drop all".
|
|
35
|
+
# The RateSampler initializer enforces non-zero, falling back to 100% sampling
|
|
36
|
+
# if zero is provided.
|
|
37
|
+
@sampler.sample_rate = sample_rate
|
|
38
|
+
@rate_limiter = Sampling::TokenBucket.new(rate_limit)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# This method should only be invoked for spans that are part
|
|
42
|
+
# of a trace that has been dropped by trace-level sampling.
|
|
43
|
+
# Invoking it for other spans will cause incorrect sampling
|
|
44
|
+
# metrics to be reported by the Datadog App.
|
|
45
|
+
#
|
|
46
|
+
# Returns `true` if the provided span is sampled.
|
|
47
|
+
# If the span is dropped due to sampling rate or rate limiting,
|
|
48
|
+
# it returns `false`.
|
|
49
|
+
#
|
|
50
|
+
# Returns `nil` if the span did not meet the matching criteria by the
|
|
51
|
+
# provided matcher.
|
|
52
|
+
#
|
|
53
|
+
# This method modifies the `span` if it matches the provided matcher.
|
|
54
|
+
#
|
|
55
|
+
# @param [Datadog::Tracing::SpanOperation] span_op span to be sampled
|
|
56
|
+
# @return [:kept,:rejected] should this span be sampled?
|
|
57
|
+
# @return [:not_matched] span did not satisfy the matcher, no changes are made to the span
|
|
58
|
+
def sample!(span_op)
|
|
59
|
+
return :not_matched unless @matcher.match?(span_op)
|
|
60
|
+
|
|
61
|
+
if @sampler.sample?(span_op) && @rate_limiter.allow?(1)
|
|
62
|
+
span_op.set_metric(Span::Ext::TAG_MECHANISM, Sampling::Ext::Mechanism::SPAN_SAMPLING_RATE)
|
|
63
|
+
span_op.set_metric(Span::Ext::TAG_RULE_RATE, @sample_rate)
|
|
64
|
+
span_op.set_metric(Span::Ext::TAG_MAX_PER_SECOND, @rate_limit)
|
|
65
|
+
:kept
|
|
66
|
+
else
|
|
67
|
+
:rejected
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def ==(other)
|
|
72
|
+
return super unless other.is_a?(Rule)
|
|
73
|
+
|
|
74
|
+
matcher == other.matcher &&
|
|
75
|
+
sample_rate == other.sample_rate &&
|
|
76
|
+
rate_limit == other.rate_limit
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|