ddtrace 1.4.1 → 1.6.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|