ddtrace 1.4.1 → 1.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +144 -1
  3. data/LICENSE-3rdparty.csv +1 -0
  4. data/ext/ddtrace_profiling_loader/ddtrace_profiling_loader.c +9 -2
  5. data/ext/ddtrace_profiling_loader/extconf.rb +17 -0
  6. data/ext/ddtrace_profiling_native_extension/NativeExtensionDesign.md +38 -2
  7. data/ext/ddtrace_profiling_native_extension/clock_id.h +1 -0
  8. data/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c +1 -0
  9. data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time.c +517 -42
  10. data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time.h +3 -0
  11. data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +208 -30
  12. data/ext/ddtrace_profiling_native_extension/collectors_stack.c +156 -46
  13. data/ext/ddtrace_profiling_native_extension/collectors_stack.h +11 -2
  14. data/ext/ddtrace_profiling_native_extension/extconf.rb +11 -1
  15. data/ext/ddtrace_profiling_native_extension/http_transport.c +83 -64
  16. data/ext/ddtrace_profiling_native_extension/libdatadog_helpers.h +4 -4
  17. data/ext/ddtrace_profiling_native_extension/native_extension_helpers.rb +3 -4
  18. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +59 -0
  19. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.h +3 -0
  20. data/ext/ddtrace_profiling_native_extension/profiling.c +10 -0
  21. data/ext/ddtrace_profiling_native_extension/ruby_helpers.c +0 -1
  22. data/ext/ddtrace_profiling_native_extension/ruby_helpers.h +4 -2
  23. data/ext/ddtrace_profiling_native_extension/stack_recorder.c +45 -29
  24. data/ext/ddtrace_profiling_native_extension/stack_recorder.h +7 -7
  25. data/lib/datadog/appsec/assets/waf_rules/recommended.json +1169 -275
  26. data/lib/datadog/appsec/assets/waf_rules/risky.json +78 -78
  27. data/lib/datadog/appsec/assets/waf_rules/strict.json +278 -88
  28. data/lib/datadog/appsec/configuration/settings.rb +0 -2
  29. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +25 -20
  30. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +11 -11
  31. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +11 -11
  32. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +11 -11
  33. data/lib/datadog/appsec/contrib/rack/request.rb +3 -0
  34. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +46 -19
  35. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +7 -6
  36. data/lib/datadog/appsec/contrib/rails/integration.rb +1 -1
  37. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +11 -11
  38. data/lib/datadog/appsec/contrib/rails/request.rb +3 -0
  39. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +14 -12
  40. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +11 -11
  41. data/lib/datadog/appsec/event.rb +6 -10
  42. data/lib/datadog/appsec/instrumentation/gateway.rb +16 -2
  43. data/lib/datadog/appsec/processor.rb +18 -2
  44. data/lib/datadog/ci/ext/environment.rb +16 -4
  45. data/lib/datadog/core/configuration/agent_settings_resolver.rb +0 -3
  46. data/lib/datadog/core/configuration/components.rb +28 -16
  47. data/lib/datadog/core/configuration/settings.rb +127 -8
  48. data/lib/datadog/core/configuration.rb +1 -1
  49. data/lib/datadog/core/diagnostics/environment_logger.rb +5 -1
  50. data/lib/datadog/core/header_collection.rb +41 -0
  51. data/lib/datadog/core/telemetry/collector.rb +0 -2
  52. data/lib/datadog/core/utils/compression.rb +5 -1
  53. data/lib/datadog/core/workers/async.rb +0 -2
  54. data/lib/datadog/core.rb +0 -54
  55. data/lib/datadog/opentracer/tracer.rb +4 -6
  56. data/lib/datadog/profiling/collectors/cpu_and_wall_time.rb +12 -2
  57. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +5 -3
  58. data/lib/datadog/profiling/collectors/old_stack.rb +1 -1
  59. data/lib/datadog/profiling/exporter.rb +2 -4
  60. data/lib/datadog/profiling/http_transport.rb +1 -1
  61. data/lib/datadog/profiling.rb +1 -1
  62. data/lib/datadog/tracing/client_ip.rb +164 -0
  63. data/lib/datadog/tracing/configuration/ext.rb +14 -0
  64. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +2 -0
  65. data/lib/datadog/tracing/contrib/aws/services.rb +0 -2
  66. data/lib/datadog/tracing/contrib/dalli/ext.rb +1 -0
  67. data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +4 -0
  68. data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +2 -0
  69. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +3 -0
  70. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +2 -2
  71. data/lib/datadog/tracing/contrib/ethon/multi_patch.rb +2 -0
  72. data/lib/datadog/tracing/contrib/excon/middleware.rb +2 -0
  73. data/lib/datadog/tracing/contrib/ext.rb +25 -0
  74. data/lib/datadog/tracing/contrib/faraday/middleware.rb +3 -2
  75. data/lib/datadog/tracing/contrib/grape/endpoint.rb +0 -2
  76. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +1 -1
  77. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +5 -0
  78. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +7 -1
  79. data/lib/datadog/tracing/contrib/grpc/ext.rb +2 -0
  80. data/lib/datadog/tracing/contrib/hanami/action_tracer.rb +47 -0
  81. data/lib/datadog/tracing/contrib/hanami/configuration/settings.rb +22 -0
  82. data/lib/datadog/tracing/contrib/hanami/ext.rb +24 -0
  83. data/lib/datadog/tracing/contrib/hanami/integration.rb +44 -0
  84. data/lib/datadog/tracing/contrib/hanami/patcher.rb +33 -0
  85. data/lib/datadog/tracing/contrib/hanami/plugin.rb +23 -0
  86. data/lib/datadog/tracing/contrib/hanami/renderer_policy_tracing.rb +41 -0
  87. data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +44 -0
  88. data/lib/datadog/tracing/contrib/http/instrumentation.rb +2 -0
  89. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +2 -0
  90. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +2 -0
  91. data/lib/datadog/tracing/contrib/mongodb/ext.rb +7 -0
  92. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +4 -0
  93. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +12 -0
  94. data/lib/datadog/tracing/contrib/mysql2/ext.rb +1 -0
  95. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +16 -0
  96. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +12 -0
  97. data/lib/datadog/tracing/contrib/pg/ext.rb +2 -1
  98. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +38 -21
  99. data/lib/datadog/tracing/contrib/propagation/sql_comment/comment.rb +43 -0
  100. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +32 -0
  101. data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +28 -0
  102. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +49 -0
  103. data/lib/datadog/tracing/contrib/rack/header_collection.rb +35 -0
  104. data/lib/datadog/tracing/contrib/rack/middlewares.rb +105 -43
  105. data/lib/datadog/tracing/contrib/redis/ext.rb +2 -0
  106. data/lib/datadog/tracing/contrib/redis/instrumentation.rb +4 -2
  107. data/lib/datadog/tracing/contrib/redis/integration.rb +2 -1
  108. data/lib/datadog/tracing/contrib/redis/patcher.rb +40 -0
  109. data/lib/datadog/tracing/contrib/redis/tags.rb +5 -0
  110. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +2 -0
  111. data/lib/datadog/tracing/contrib/sinatra/env.rb +12 -23
  112. data/lib/datadog/tracing/contrib/sinatra/ext.rb +7 -3
  113. data/lib/datadog/tracing/contrib/sinatra/patcher.rb +2 -2
  114. data/lib/datadog/tracing/contrib/sinatra/tracer.rb +8 -80
  115. data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +14 -9
  116. data/lib/datadog/tracing/contrib/utils/quantization/http.rb +92 -10
  117. data/lib/datadog/tracing/contrib.rb +1 -0
  118. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +84 -0
  119. data/lib/datadog/tracing/distributed/headers/datadog.rb +122 -30
  120. data/lib/datadog/tracing/distributed/headers/ext.rb +2 -0
  121. data/lib/datadog/tracing/flush.rb +57 -35
  122. data/lib/datadog/tracing/metadata/ext.rb +11 -9
  123. data/lib/datadog/tracing/metadata/tagging.rb +9 -0
  124. data/lib/datadog/tracing/propagation/http.rb +9 -1
  125. data/lib/datadog/tracing/sampling/ext.rb +31 -0
  126. data/lib/datadog/tracing/sampling/priority_sampler.rb +46 -4
  127. data/lib/datadog/tracing/sampling/rate_by_key_sampler.rb +8 -9
  128. data/lib/datadog/tracing/sampling/rate_by_service_sampler.rb +29 -5
  129. data/lib/datadog/tracing/sampling/rate_limiter.rb +3 -0
  130. data/lib/datadog/tracing/sampling/rate_sampler.rb +20 -3
  131. data/lib/datadog/tracing/sampling/rule_sampler.rb +4 -3
  132. data/lib/datadog/tracing/sampling/span/ext.rb +25 -0
  133. data/lib/datadog/tracing/sampling/span/matcher.rb +9 -0
  134. data/lib/datadog/tracing/sampling/span/rule.rb +82 -0
  135. data/lib/datadog/tracing/sampling/span/rule_parser.rb +104 -0
  136. data/lib/datadog/tracing/sampling/span/sampler.rb +75 -0
  137. data/lib/datadog/tracing/span_operation.rb +0 -2
  138. data/lib/datadog/tracing/trace_digest.rb +3 -0
  139. data/lib/datadog/tracing/trace_operation.rb +32 -3
  140. data/lib/datadog/tracing/trace_segment.rb +7 -2
  141. data/lib/datadog/tracing/tracer.rb +34 -6
  142. data/lib/datadog/tracing/writer.rb +7 -0
  143. data/lib/ddtrace/transport/trace_formatter.rb +7 -0
  144. data/lib/ddtrace/transport/traces.rb +3 -1
  145. data/lib/ddtrace/version.rb +1 -1
  146. metadata +36 -18
  147. data/lib/datadog/profiling/old_ext.rb +0 -42
  148. data/lib/datadog/profiling/transport/http/api/endpoint.rb +0 -85
  149. data/lib/datadog/profiling/transport/http/api/instance.rb +0 -38
  150. data/lib/datadog/profiling/transport/http/api/spec.rb +0 -42
  151. data/lib/datadog/profiling/transport/http/api.rb +0 -45
  152. data/lib/datadog/profiling/transport/http/builder.rb +0 -30
  153. data/lib/datadog/profiling/transport/http/client.rb +0 -37
  154. data/lib/datadog/profiling/transport/http/response.rb +0 -21
  155. 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 only completed traces (where all spans have finished)
7
- class Finished
8
- # Consumes and returns completed traces (where all spans have finished)
9
- # from the provided \trace_op, if any.
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
- # Any traces consumed are removed from +trace_op+ as a side effect.
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 full_flush?(trace_op)
25
+ return unless flush?(trace_op)
16
26
 
17
27
  get_trace(trace_op)
18
28
  end
19
29
 
20
- def full_flush?(trace_op)
21
- trace_op && trace_op.sampled? && trace_op.finished?
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
- # Performs partial trace flushing to avoid large traces residing in memory for too long
32
- class Partial
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
- # Consumes and returns completed or partially completed
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! popolates the env with span ID, trace ID and sampling priority
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] ||= RateSampler.new(rate)
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, options = {})
15
- super(DEFAULT_KEY, default_rate, &method(:key_for))
16
- @env = options[:env]
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
- def initialize(sample_rate = 1.0)
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
- trace.sample_rate = @sample_rate if sampled
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[:env] })
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