ddtrace 1.5.2 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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