ddtrace 1.5.2 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -1
  3. data/ext/ddtrace_profiling_loader/ddtrace_profiling_loader.c +9 -2
  4. data/ext/ddtrace_profiling_loader/extconf.rb +17 -0
  5. data/ext/ddtrace_profiling_native_extension/NativeExtensionDesign.md +38 -2
  6. data/ext/ddtrace_profiling_native_extension/clock_id.h +1 -0
  7. data/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c +1 -0
  8. data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time.c +517 -42
  9. data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time.h +3 -0
  10. data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +208 -30
  11. data/ext/ddtrace_profiling_native_extension/collectors_stack.c +156 -46
  12. data/ext/ddtrace_profiling_native_extension/collectors_stack.h +11 -2
  13. data/ext/ddtrace_profiling_native_extension/extconf.rb +11 -1
  14. data/ext/ddtrace_profiling_native_extension/http_transport.c +83 -64
  15. data/ext/ddtrace_profiling_native_extension/libdatadog_helpers.h +4 -4
  16. data/ext/ddtrace_profiling_native_extension/native_extension_helpers.rb +3 -2
  17. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +59 -0
  18. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.h +3 -0
  19. data/ext/ddtrace_profiling_native_extension/profiling.c +10 -0
  20. data/ext/ddtrace_profiling_native_extension/ruby_helpers.c +0 -1
  21. data/ext/ddtrace_profiling_native_extension/ruby_helpers.h +4 -2
  22. data/ext/ddtrace_profiling_native_extension/stack_recorder.c +45 -29
  23. data/ext/ddtrace_profiling_native_extension/stack_recorder.h +7 -7
  24. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +4 -0
  25. data/lib/datadog/appsec/event.rb +6 -0
  26. data/lib/datadog/core/configuration/components.rb +20 -14
  27. data/lib/datadog/core/configuration/settings.rb +42 -4
  28. data/lib/datadog/core/diagnostics/environment_logger.rb +5 -1
  29. data/lib/datadog/core/utils/compression.rb +5 -1
  30. data/lib/datadog/core.rb +0 -54
  31. data/lib/datadog/profiling/collectors/cpu_and_wall_time.rb +12 -2
  32. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +5 -3
  33. data/lib/datadog/profiling/exporter.rb +2 -4
  34. data/lib/datadog/profiling/http_transport.rb +1 -1
  35. data/lib/datadog/tracing/configuration/ext.rb +1 -0
  36. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +2 -0
  37. data/lib/datadog/tracing/contrib/dalli/ext.rb +1 -0
  38. data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +4 -0
  39. data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +2 -0
  40. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +3 -0
  41. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +2 -0
  42. data/lib/datadog/tracing/contrib/ethon/multi_patch.rb +2 -0
  43. data/lib/datadog/tracing/contrib/excon/middleware.rb +2 -0
  44. data/lib/datadog/tracing/contrib/ext.rb +6 -0
  45. data/lib/datadog/tracing/contrib/faraday/middleware.rb +2 -0
  46. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +5 -0
  47. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +7 -1
  48. data/lib/datadog/tracing/contrib/grpc/ext.rb +2 -0
  49. data/lib/datadog/tracing/contrib/hanami/action_tracer.rb +47 -0
  50. data/lib/datadog/tracing/contrib/hanami/configuration/settings.rb +22 -0
  51. data/lib/datadog/tracing/contrib/hanami/ext.rb +24 -0
  52. data/lib/datadog/tracing/contrib/hanami/integration.rb +44 -0
  53. data/lib/datadog/tracing/contrib/hanami/patcher.rb +33 -0
  54. data/lib/datadog/tracing/contrib/hanami/plugin.rb +23 -0
  55. data/lib/datadog/tracing/contrib/hanami/renderer_policy_tracing.rb +41 -0
  56. data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +44 -0
  57. data/lib/datadog/tracing/contrib/http/instrumentation.rb +2 -0
  58. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +2 -0
  59. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +2 -0
  60. data/lib/datadog/tracing/contrib/mongodb/ext.rb +7 -0
  61. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +4 -0
  62. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +12 -0
  63. data/lib/datadog/tracing/contrib/mysql2/ext.rb +1 -0
  64. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +16 -0
  65. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +12 -0
  66. data/lib/datadog/tracing/contrib/pg/ext.rb +2 -1
  67. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +34 -18
  68. data/lib/datadog/tracing/contrib/propagation/sql_comment/comment.rb +43 -0
  69. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +32 -0
  70. data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +28 -0
  71. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +49 -0
  72. data/lib/datadog/tracing/contrib/rack/middlewares.rb +11 -5
  73. data/lib/datadog/tracing/contrib/redis/ext.rb +2 -0
  74. data/lib/datadog/tracing/contrib/redis/instrumentation.rb +4 -2
  75. data/lib/datadog/tracing/contrib/redis/patcher.rb +41 -0
  76. data/lib/datadog/tracing/contrib/redis/tags.rb +5 -0
  77. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +2 -0
  78. data/lib/datadog/tracing/contrib/sinatra/env.rb +12 -23
  79. data/lib/datadog/tracing/contrib/sinatra/ext.rb +7 -3
  80. data/lib/datadog/tracing/contrib/sinatra/patcher.rb +2 -2
  81. data/lib/datadog/tracing/contrib/sinatra/tracer.rb +8 -80
  82. data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +14 -9
  83. data/lib/datadog/tracing/contrib.rb +1 -0
  84. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +84 -0
  85. data/lib/datadog/tracing/distributed/headers/datadog.rb +122 -30
  86. data/lib/datadog/tracing/distributed/headers/ext.rb +2 -0
  87. data/lib/datadog/tracing/flush.rb +1 -1
  88. data/lib/datadog/tracing/metadata/ext.rb +8 -0
  89. data/lib/datadog/tracing/propagation/http.rb +9 -1
  90. data/lib/datadog/tracing/sampling/ext.rb +31 -0
  91. data/lib/datadog/tracing/sampling/priority_sampler.rb +46 -4
  92. data/lib/datadog/tracing/sampling/rate_by_key_sampler.rb +8 -9
  93. data/lib/datadog/tracing/sampling/rate_by_service_sampler.rb +29 -5
  94. data/lib/datadog/tracing/sampling/rate_sampler.rb +10 -3
  95. data/lib/datadog/tracing/sampling/rule_sampler.rb +4 -3
  96. data/lib/datadog/tracing/sampling/span/ext.rb +0 -4
  97. data/lib/datadog/tracing/sampling/span/rule.rb +1 -1
  98. data/lib/datadog/tracing/sampling/span/sampler.rb +14 -3
  99. data/lib/datadog/tracing/trace_digest.rb +3 -0
  100. data/lib/datadog/tracing/trace_operation.rb +10 -0
  101. data/lib/datadog/tracing/trace_segment.rb +6 -0
  102. data/lib/datadog/tracing/tracer.rb +3 -1
  103. data/lib/datadog/tracing/writer.rb +7 -0
  104. data/lib/ddtrace/transport/trace_formatter.rb +7 -0
  105. data/lib/ddtrace/transport/traces.rb +1 -1
  106. data/lib/ddtrace/version.rb +2 -2
  107. metadata +18 -14
  108. data/lib/datadog/profiling/old_ext.rb +0 -42
  109. data/lib/datadog/profiling/transport/http/api/endpoint.rb +0 -85
  110. data/lib/datadog/profiling/transport/http/api/instance.rb +0 -38
  111. data/lib/datadog/profiling/transport/http/api/spec.rb +0 -42
  112. data/lib/datadog/profiling/transport/http/api.rb +0 -45
  113. data/lib/datadog/profiling/transport/http/builder.rb +0 -30
  114. data/lib/datadog/profiling/transport/http/client.rb +0 -37
  115. data/lib/datadog/profiling/transport/http/response.rb +0 -21
  116. data/lib/datadog/profiling/transport/http.rb +0 -118
@@ -25,10 +25,12 @@ module Datadog
25
25
  def call(env)
26
26
  # Set the trace context (e.g. distributed tracing)
27
27
  if configuration[:distributed_tracing] && Tracing.active_trace.nil?
28
- original_trace = Propagation::HTTP.extract(env)
28
+ original_trace = Tracing::Propagation::HTTP.extract(env)
29
29
  Tracing.continue_trace!(original_trace)
30
30
  end
31
31
 
32
+ return @app.call(env) if Sinatra::Env.datadog_span(env)
33
+
32
34
  Tracing.trace(
33
35
  Ext::SPAN_REQUEST,
34
36
  service: configuration[:service_name],
@@ -39,7 +41,7 @@ module Datadog
39
41
  # the nil signals that there's no good one yet and is also seen by profiler, when sampling the resource
40
42
  span.resource = nil
41
43
 
42
- Sinatra::Env.set_datadog_span(env, @app_instance, span)
44
+ Sinatra::Env.set_datadog_span(env, span)
43
45
 
44
46
  response = @app.call(env)
45
47
  ensure
@@ -51,14 +53,18 @@ module Datadog
51
53
  span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_REQUEST)
52
54
 
53
55
  request = ::Sinatra::Request.new(env)
56
+
54
57
  span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, request.path)
55
58
  span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_METHOD, request.request_method)
59
+
60
+ datadog_route = Sinatra::Env.route_path(env)
61
+
62
+ span.set_tag(Ext::TAG_ROUTE_PATH, datadog_route) if datadog_route
63
+
56
64
  if request.script_name && !request.script_name.empty?
57
65
  span.set_tag(Ext::TAG_SCRIPT_NAME, request.script_name)
58
66
  end
59
67
 
60
- span.set_tag(Ext::TAG_APP_NAME, @app_instance.settings.name)
61
-
62
68
  # If this app handled the request, then Contrib::Sinatra::Tracer OR Contrib::Sinatra::Base set the
63
69
  # resource; if no resource was set, let's use a fallback
64
70
  span.resource = env['REQUEST_METHOD'] if span.resource.nil?
@@ -79,7 +85,10 @@ module Datadog
79
85
  end
80
86
 
81
87
  if (headers = response[1])
82
- Sinatra::Headers.response_header_tags(headers, configuration[:headers][:response]).each do |name, value|
88
+ Sinatra::Headers.response_header_tags(
89
+ headers,
90
+ configuration[:headers][:response]
91
+ ).each do |name, value|
83
92
  span.set_tag(name, value) if span.get_tag(name).nil?
84
93
  end
85
94
  end
@@ -111,10 +120,6 @@ module Datadog
111
120
  def configuration
112
121
  Datadog.configuration.tracing[:sinatra]
113
122
  end
114
-
115
- def header_to_rack_header(name)
116
- "HTTP_#{name.to_s.upcase.gsub(/[-\s]/, '_')}"
117
- end
118
123
  end
119
124
  end
120
125
  end
@@ -49,6 +49,7 @@ require_relative 'contrib/faraday/integration'
49
49
  require_relative 'contrib/grape/integration'
50
50
  require_relative 'contrib/graphql/integration'
51
51
  require_relative 'contrib/grpc/integration'
52
+ require_relative 'contrib/hanami/integration'
52
53
  require_relative 'contrib/http/integration'
53
54
  require_relative 'contrib/httpclient/integration'
54
55
  require_relative 'contrib/httprb/integration'
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Tracing
5
+ module Distributed
6
+ # Encodes and decodes distributed 'x-datadog-tags' tags for transport
7
+ # to and from external processes.
8
+ module DatadogTagsCodec
9
+ # Backport `Regexp::match?` because it is measurably the most performant
10
+ # way to check if a string matches a regular expression.
11
+ module RefineRegexp
12
+ unless Regexp.method_defined?(:match?)
13
+ refine ::Regexp do
14
+ def match?(*args)
15
+ !match(*args).nil?
16
+ end
17
+ end
18
+ end
19
+ end
20
+ using RefineRegexp
21
+
22
+ # ASCII characters 32-126, except `,`, `=`, and ` `. At least one character.
23
+ VALID_KEY_CHARS = /\A(?:(?![,= ])[\u0020-\u007E])+\Z/.freeze
24
+ # ASCII characters 32-126, except `,`. At least one character.
25
+ VALID_VALUE_CHARS = /\A(?:(?!,)[\u0020-\u007E])+\Z/.freeze
26
+
27
+ # Serializes a {Hash<String,String>} into a `x-datadog-tags`-compatible
28
+ # String.
29
+ #
30
+ # @param tags [Hash<String,String>] trace tag hash
31
+ # @return [String] serialized tags hash
32
+ # @raise [EncodingError] if tags cannot be serialized to the `x-datadog-tags` format
33
+ def self.encode(tags)
34
+ begin
35
+ tags.map do |raw_key, raw_value|
36
+ key = raw_key.to_s
37
+ value = raw_value.to_s
38
+
39
+ raise EncodingError, "Invalid key `#{key}` for value `#{value}`" unless VALID_KEY_CHARS.match?(key)
40
+ raise EncodingError, "Invalid value `#{value}` for key `#{key}`" unless VALID_VALUE_CHARS.match?(value)
41
+
42
+ "#{key}=#{value.strip}"
43
+ end.join(',')
44
+ rescue => e
45
+ raise EncodingError, "Error encoding tags `#{tags}`: `#{e}`"
46
+ end
47
+ end
48
+
49
+ # Deserializes a `x-datadog-tags`-formatted String into a {Hash<String,String>}.
50
+ #
51
+ # @param string [String] tags as serialized by {#encode}
52
+ # @return [Hash<String,String>] decoded input as a hash of strings
53
+ # @raise [DecodingError] if string does not conform to the `x-datadog-tags` format
54
+ def self.decode(string)
55
+ result = Hash[string.split(',').map do |raw_tag|
56
+ raw_tag.split('=', 2).tap do |raw_key, raw_value|
57
+ key = raw_key.to_s
58
+ value = raw_value.to_s
59
+
60
+ raise DecodingError, "Invalid key: #{key}" unless VALID_KEY_CHARS.match?(key)
61
+ raise DecodingError, "Invalid value: #{value}" unless VALID_VALUE_CHARS.match?(value)
62
+
63
+ value.strip!
64
+ end
65
+ end]
66
+
67
+ raise DecodingError, "Invalid empty tags: #{string}" if result.empty? && !string.empty?
68
+
69
+ result
70
+ end
71
+
72
+ # An error occurred during distributed tags encoding.
73
+ # See {#message} for more information.
74
+ class EncodingError < StandardError
75
+ end
76
+
77
+ # An error occurred during distributed tags decoding.
78
+ # See {#message} for more information.
79
+ class DecodingError < StandardError
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
1
2
  # typed: true
2
3
 
3
4
  require_relative 'parser'
4
5
  require_relative 'ext'
6
+ require_relative '../../metadata/ext'
5
7
  require_relative '../../trace_digest'
8
+ require_relative '../datadog_tags_codec'
6
9
 
7
10
  module Datadog
8
11
  module Tracing
@@ -10,40 +13,129 @@ module Datadog
10
13
  module Headers
11
14
  # Datadog provides helpers to inject or extract headers for Datadog style headers
12
15
  module Datadog
13
- include Ext
16
+ class << self
17
+ include Ext
14
18
 
15
- def self.inject!(digest, env)
16
- return if digest.nil?
19
+ def inject!(digest, env)
20
+ return if digest.nil?
17
21
 
18
- env[HTTP_HEADER_TRACE_ID] = digest.trace_id.to_s
19
- env[HTTP_HEADER_PARENT_ID] = digest.span_id.to_s
20
- env[HTTP_HEADER_SAMPLING_PRIORITY] = digest.trace_sampling_priority.to_s if digest.trace_sampling_priority
21
- env[HTTP_HEADER_ORIGIN] = digest.trace_origin.to_s unless digest.trace_origin.nil?
22
+ env[HTTP_HEADER_TRACE_ID] = digest.trace_id.to_s
23
+ env[HTTP_HEADER_PARENT_ID] = digest.span_id.to_s
24
+ env[HTTP_HEADER_SAMPLING_PRIORITY] = digest.trace_sampling_priority.to_s if digest.trace_sampling_priority
25
+ env[HTTP_HEADER_ORIGIN] = digest.trace_origin.to_s unless digest.trace_origin.nil?
22
26
 
23
- env
24
- end
27
+ inject_tags(digest, env)
28
+
29
+ env
30
+ end
31
+
32
+ def extract(env)
33
+ # Extract values from headers
34
+ headers = Parser.new(env)
35
+ trace_id = headers.id(HTTP_HEADER_TRACE_ID)
36
+ parent_id = headers.id(HTTP_HEADER_PARENT_ID)
37
+ origin = headers.header(HTTP_HEADER_ORIGIN)
38
+ sampling_priority = headers.number(HTTP_HEADER_SAMPLING_PRIORITY)
39
+
40
+ # Return early if this propagation is not valid
41
+ # DEV: To be valid we need to have a trace id and a parent id
42
+ # or when it is a synthetics trace, just the trace id.
43
+ # DEV: `Parser#id` will not return 0
44
+ return unless (trace_id && parent_id) || (origin && trace_id)
45
+
46
+ trace_distributed_tags = extract_tags(headers)
47
+
48
+ # Return new trace headers
49
+ TraceDigest.new(
50
+ span_id: parent_id,
51
+ trace_id: trace_id,
52
+ trace_origin: origin,
53
+ trace_sampling_priority: sampling_priority,
54
+ trace_distributed_tags: trace_distributed_tags,
55
+ )
56
+ end
57
+
58
+ private
59
+
60
+ # Export trace distributed tags through the `x-datadog-tags` header.
61
+ #
62
+ # DEV: This method accesses global state (the active trace) to record its error state as a trace tag.
63
+ # DEV: This means errors cannot be reported if there's not active span.
64
+ # DEV: Ideally, we'd have a dedicated error reporting stream for all of ddtrace.
65
+ # DEV: The same comment applies to the {.extract_tags}.
66
+ def inject_tags(digest, env)
67
+ return if digest.trace_distributed_tags.nil? || digest.trace_distributed_tags.empty?
68
+
69
+ if ::Datadog.configuration.tracing.x_datadog_tags_max_length <= 0
70
+ active_trace = Tracing.active_trace
71
+ active_trace.set_tag('_dd.propagation_error', 'disabled') if active_trace
72
+ return
73
+ end
74
+
75
+ encoded_tags = DatadogTagsCodec.encode(digest.trace_distributed_tags)
76
+
77
+ if encoded_tags.size > ::Datadog.configuration.tracing.x_datadog_tags_max_length
78
+ active_trace = Tracing.active_trace
79
+ active_trace.set_tag('_dd.propagation_error', 'inject_max_size') if active_trace
80
+
81
+ ::Datadog.logger.warn(
82
+ "Failed to inject x-datadog-tags: tags are too large (size:#{encoded_tags.size} " \
83
+ "limit:#{::Datadog.configuration.tracing.x_datadog_tags_max_length}). This limit can be configured " \
84
+ 'through the DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH environment variable.'
85
+ )
86
+ return
87
+ end
88
+
89
+ env[HTTP_HEADER_TAGS] = encoded_tags
90
+ rescue => e
91
+ active_trace = Tracing.active_trace
92
+ active_trace.set_tag('_dd.propagation_error', 'encoding_error') if active_trace
93
+ ::Datadog.logger.warn(
94
+ "Failed to inject x-datadog-tags: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}"
95
+ )
96
+ end
97
+
98
+ # Import `x-datadog-tags` header tags as trace distributed tags.
99
+ # Only tags that have the `_dd.p.` prefix are processed.
100
+ def extract_tags(headers)
101
+ tags_header = headers.header(HTTP_HEADER_TAGS)
102
+ return unless tags_header
103
+
104
+ if ::Datadog.configuration.tracing.x_datadog_tags_max_length <= 0
105
+ active_trace = Tracing.active_trace
106
+ active_trace.set_tag('_dd.propagation_error', 'disabled') if active_trace
107
+ return
108
+ end
109
+
110
+ if tags_header.size > ::Datadog.configuration.tracing.x_datadog_tags_max_length
111
+ active_trace = Tracing.active_trace
112
+ active_trace.set_tag('_dd.propagation_error', 'extract_max_size') if active_trace
113
+
114
+ ::Datadog.logger.warn(
115
+ "Failed to extract x-datadog-tags: tags are too large (size:#{tags_header.size} " \
116
+ "limit:#{::Datadog.configuration.tracing.x_datadog_tags_max_length}). This limit can be configured " \
117
+ 'through the DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH environment variable.'
118
+ )
119
+ return
120
+ end
121
+
122
+ tags = DatadogTagsCodec.decode(tags_header)
123
+ # Only extract keys with the expected Datadog prefix
124
+ tags.select! do |key, _|
125
+ key.start_with?(Tracing::Metadata::Ext::Distributed::TAGS_PREFIX) && key != EXCLUDED_TAG
126
+ end
127
+ tags
128
+ rescue => e
129
+ active_trace = Tracing.active_trace
130
+ active_trace.set_tag('_dd.propagation_error', 'decoding_error') if active_trace
131
+ ::Datadog.logger.warn(
132
+ "Failed to extract x-datadog-tags: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}"
133
+ )
134
+ end
25
135
 
26
- def self.extract(env)
27
- # Extract values from headers
28
- headers = Parser.new(env)
29
- trace_id = headers.id(HTTP_HEADER_TRACE_ID)
30
- parent_id = headers.id(HTTP_HEADER_PARENT_ID)
31
- origin = headers.header(HTTP_HEADER_ORIGIN)
32
- sampling_priority = headers.number(HTTP_HEADER_SAMPLING_PRIORITY)
33
-
34
- # Return early if this propagation is not valid
35
- # DEV: To be valid we need to have a trace id and a parent id
36
- # or when it is a synthetics trace, just the trace id.
37
- # DEV: `Parser#id` will not return 0
38
- return unless (trace_id && parent_id) || (origin && trace_id)
39
-
40
- # Return new trace headers
41
- TraceDigest.new(
42
- span_id: parent_id,
43
- trace_id: trace_id,
44
- trace_origin: origin,
45
- trace_sampling_priority: sampling_priority
46
- )
136
+ # We want to exclude tags that we don't want to propagate downstream.
137
+ EXCLUDED_TAG = '_dd.p.upstream_services'
138
+ private_constant :EXCLUDED_TAG
47
139
  end
48
140
  end
49
141
  end
@@ -12,6 +12,8 @@ module Datadog
12
12
  HTTP_HEADER_PARENT_ID = 'x-datadog-parent-id'.freeze
13
13
  HTTP_HEADER_SAMPLING_PRIORITY = 'x-datadog-sampling-priority'.freeze
14
14
  HTTP_HEADER_ORIGIN = 'x-datadog-origin'.freeze
15
+ # Distributed trace-level tags
16
+ HTTP_HEADER_TAGS = 'x-datadog-tags'.freeze
15
17
 
16
18
  # B3 headers used for distributed tracing
17
19
  B3_HEADER_TRACE_ID = 'x-b3-traceid'.freeze
@@ -48,7 +48,7 @@ module Datadog
48
48
  # Single Span Sampling has chosen to keep this span
49
49
  # regardless of the trace-level sampling decision
50
50
  def single_sampled?(span)
51
- span.get_metric(Sampling::Span::Ext::TAG_MECHANISM) == Sampling::Span::Ext::MECHANISM_SPAN_SAMPLING_RATE
51
+ span.get_metric(Sampling::Span::Ext::TAG_MECHANISM) == Sampling::Ext::Mechanism::SPAN_SAMPLING_RATE
52
52
  end
53
53
  end
54
54
 
@@ -45,8 +45,16 @@ module Datadog
45
45
  # @public_api
46
46
  # Tags related to distributed tracing
47
47
  module Distributed
48
+ # What mechanism was used to make this trace's sampling decision.
49
+ # @see Datadog::Tracing::Sampling::Ext::Mechanism
50
+ TAG_DECISION_MAKER = '_dd.p.dm'
51
+
48
52
  TAG_ORIGIN = '_dd.origin'
49
53
  TAG_SAMPLING_PRIORITY = '_sampling_priority_v1'
54
+
55
+ # Trace tags with this prefix will propagate from a trace through distributed tracing.
56
+ # Distributed headers tags with this prefix will be injected into the active trace.
57
+ TAGS_PREFIX = '_dd.p.'
50
58
  end
51
59
 
52
60
  # @public_api
@@ -23,7 +23,15 @@ module Datadog
23
23
  Configuration::Ext::Distributed::PROPAGATION_STYLE_DATADOG => Distributed::Headers::Datadog
24
24
  }.freeze
25
25
 
26
- # inject! 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
@@ -30,7 +30,7 @@ module Datadog
30
30
  # DEV-2.0: sampler = RateSampler.new
31
31
  # DEV-2.0: sampler.sample_rate = sample_rate
32
32
  # DEV-2.0: ```
33
- def initialize(sample_rate = 1.0)
33
+ def initialize(sample_rate = 1.0, decision: nil)
34
34
  super()
35
35
 
36
36
  unless sample_rate > 0.0 && sample_rate <= 1.0
@@ -39,6 +39,8 @@ module Datadog
39
39
  end
40
40
 
41
41
  self.sample_rate = sample_rate
42
+
43
+ @decision = decision
42
44
  end
43
45
 
44
46
  def sample_rate(*_)
@@ -56,8 +58,13 @@ module Datadog
56
58
 
57
59
  def sample!(trace)
58
60
  sampled = trace.sampled = sample?(trace)
59
- trace.sample_rate = @sample_rate if sampled
60
- sampled
61
+
62
+ return false unless sampled
63
+
64
+ trace.sample_rate = @sample_rate
65
+ trace.set_tag(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER, @decision) if @decision
66
+
67
+ true
61
68
  end
62
69
  end
63
70
  end