ddtrace 1.7.0 → 1.8.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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -1
  3. data/README.md +2 -2
  4. data/ext/ddtrace_profiling_loader/extconf.rb +4 -1
  5. data/ext/ddtrace_profiling_native_extension/NativeExtensionDesign.md +1 -1
  6. data/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c +3 -2
  7. data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time.c +15 -41
  8. data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time.h +1 -1
  9. data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +284 -74
  10. data/ext/ddtrace_profiling_native_extension/collectors_dynamic_sampling_rate.c +142 -0
  11. data/ext/ddtrace_profiling_native_extension/collectors_dynamic_sampling_rate.h +14 -0
  12. data/ext/ddtrace_profiling_native_extension/collectors_idle_sampling_helper.c +241 -0
  13. data/ext/ddtrace_profiling_native_extension/collectors_idle_sampling_helper.h +3 -0
  14. data/ext/ddtrace_profiling_native_extension/extconf.rb +21 -7
  15. data/ext/ddtrace_profiling_native_extension/helpers.h +5 -0
  16. data/ext/ddtrace_profiling_native_extension/native_extension_helpers.rb +8 -0
  17. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +108 -24
  18. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.h +9 -0
  19. data/ext/ddtrace_profiling_native_extension/profiling.c +205 -0
  20. data/ext/ddtrace_profiling_native_extension/ruby_helpers.c +86 -0
  21. data/ext/ddtrace_profiling_native_extension/ruby_helpers.h +28 -6
  22. data/ext/ddtrace_profiling_native_extension/setup_signal_handler.c +23 -4
  23. data/ext/ddtrace_profiling_native_extension/setup_signal_handler.h +4 -0
  24. data/ext/ddtrace_profiling_native_extension/stack_recorder.c +15 -18
  25. data/ext/ddtrace_profiling_native_extension/time_helpers.c +17 -0
  26. data/ext/ddtrace_profiling_native_extension/time_helpers.h +10 -0
  27. data/lib/datadog/core/configuration/components.rb +27 -6
  28. data/lib/datadog/core/configuration/ext.rb +18 -0
  29. data/lib/datadog/core/configuration/settings.rb +14 -341
  30. data/lib/datadog/core/diagnostics/health.rb +4 -22
  31. data/lib/datadog/core/environment/variable_helpers.rb +58 -10
  32. data/lib/datadog/core/utils.rb +0 -21
  33. data/lib/datadog/core.rb +21 -1
  34. data/lib/datadog/opentracer/distributed_headers.rb +2 -2
  35. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +16 -5
  36. data/lib/datadog/profiling/collectors/dynamic_sampling_rate.rb +14 -0
  37. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +68 -0
  38. data/lib/datadog/profiling/stack_recorder.rb +14 -0
  39. data/lib/datadog/profiling.rb +2 -0
  40. data/lib/datadog/tracing/configuration/ext.rb +33 -3
  41. data/lib/datadog/tracing/configuration/settings.rb +433 -0
  42. data/lib/datadog/tracing/contrib/aws/configuration/settings.rb +4 -1
  43. data/lib/datadog/tracing/contrib/aws/ext.rb +1 -0
  44. data/lib/datadog/tracing/contrib/dalli/configuration/settings.rb +4 -1
  45. data/lib/datadog/tracing/contrib/dalli/ext.rb +1 -0
  46. data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +5 -1
  47. data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +1 -0
  48. data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +6 -1
  49. data/lib/datadog/tracing/contrib/ethon/ext.rb +1 -0
  50. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +5 -1
  51. data/lib/datadog/tracing/contrib/excon/ext.rb +1 -0
  52. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +5 -1
  53. data/lib/datadog/tracing/contrib/faraday/ext.rb +1 -0
  54. data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +6 -1
  55. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +9 -4
  56. data/lib/datadog/tracing/contrib/grpc/ext.rb +1 -0
  57. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +6 -1
  58. data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +9 -4
  59. data/lib/datadog/tracing/contrib/http/ext.rb +1 -0
  60. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +6 -1
  61. data/lib/datadog/tracing/contrib/httpclient/ext.rb +1 -0
  62. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +6 -1
  63. data/lib/datadog/tracing/contrib/httprb/ext.rb +1 -0
  64. data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +5 -1
  65. data/lib/datadog/tracing/contrib/mongodb/ext.rb +1 -0
  66. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +4 -1
  67. data/lib/datadog/tracing/contrib/mysql2/ext.rb +1 -0
  68. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +2 -2
  69. data/lib/datadog/tracing/contrib/patcher.rb +3 -2
  70. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +4 -1
  71. data/lib/datadog/tracing/contrib/pg/ext.rb +1 -0
  72. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +12 -2
  73. data/lib/datadog/tracing/contrib/presto/configuration/settings.rb +4 -1
  74. data/lib/datadog/tracing/contrib/presto/ext.rb +1 -0
  75. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +1 -0
  76. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +10 -12
  77. data/lib/datadog/tracing/contrib/redis/configuration/settings.rb +4 -1
  78. data/lib/datadog/tracing/contrib/redis/ext.rb +1 -0
  79. data/lib/datadog/tracing/contrib/redis/instrumentation.rb +30 -23
  80. data/lib/datadog/tracing/contrib/redis/integration.rb +34 -2
  81. data/lib/datadog/tracing/contrib/redis/patcher.rb +18 -14
  82. data/lib/datadog/tracing/contrib/redis/quantize.rb +12 -9
  83. data/lib/datadog/tracing/contrib/redis/tags.rb +4 -6
  84. data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +72 -0
  85. data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +6 -1
  86. data/lib/datadog/tracing/contrib/rest_client/ext.rb +1 -0
  87. data/lib/datadog/{core → tracing}/diagnostics/ext.rb +1 -6
  88. data/lib/datadog/tracing/diagnostics/health.rb +40 -0
  89. data/lib/datadog/tracing/distributed/{b3.rb → b3_multi.rb} +2 -2
  90. data/lib/datadog/tracing/distributed/helpers.rb +2 -1
  91. data/lib/datadog/tracing/distributed/none.rb +19 -0
  92. data/lib/datadog/tracing/distributed/trace_context.rb +369 -0
  93. data/lib/datadog/tracing/metadata/ext.rb +1 -1
  94. data/lib/datadog/tracing/sampling/priority_sampler.rb +11 -0
  95. data/lib/datadog/tracing/sampling/rate_sampler.rb +3 -3
  96. data/lib/datadog/tracing/span.rb +3 -19
  97. data/lib/datadog/tracing/span_operation.rb +5 -4
  98. data/lib/datadog/tracing/trace_digest.rb +75 -2
  99. data/lib/datadog/tracing/trace_operation.rb +5 -4
  100. data/lib/datadog/tracing/utils.rb +50 -0
  101. data/lib/ddtrace/version.rb +1 -1
  102. metadata +20 -5
@@ -0,0 +1,72 @@
1
+ # typed: false
2
+
3
+ require_relative '../patcher'
4
+ require_relative 'ext'
5
+ require_relative 'quantize'
6
+ require_relative 'tags'
7
+
8
+ module Datadog
9
+ module Tracing
10
+ module Contrib
11
+ module Redis
12
+ # Instrumentation for Redis 5+
13
+ module TraceMiddleware
14
+ def call(commands, redis_config)
15
+ Tracing.trace(Contrib::Redis::Ext::SPAN_COMMAND) do |span|
16
+ datadog_configuration = resolve(redis_config)
17
+ resource = get_command(commands, datadog_configuration[:command_args])
18
+
19
+ span.service = datadog_configuration[:service_name]
20
+ span.span_type = Contrib::Redis::Ext::TYPE
21
+ span.resource = resource
22
+
23
+ Contrib::Redis::Tags.set_common_tags(redis_config, span, datadog_configuration[:command_args])
24
+
25
+ super
26
+ end
27
+ end
28
+
29
+ def call_pipelined(commands, redis_config)
30
+ Tracing.trace(Contrib::Redis::Ext::SPAN_COMMAND) do |span|
31
+ datadog_configuration = resolve(redis_config)
32
+ pipelined_commands = get_pipeline_commands(commands, datadog_configuration[:command_args])
33
+
34
+ span.service = datadog_configuration[:service_name]
35
+ span.span_type = Contrib::Redis::Ext::TYPE
36
+ span.resource = pipelined_commands.join("\n")
37
+ span.set_metric Contrib::Redis::Ext::METRIC_PIPELINE_LEN, pipelined_commands.length
38
+
39
+ Contrib::Redis::Tags.set_common_tags(redis_config, span, datadog_configuration[:command_args])
40
+
41
+ super
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def get_command(commands, boolean)
48
+ if boolean
49
+ Contrib::Redis::Quantize.format_command_args(commands)
50
+ else
51
+ Contrib::Redis::Quantize.get_verb(commands)
52
+ end
53
+ end
54
+
55
+ def get_pipeline_commands(commands, boolean)
56
+ if boolean
57
+ commands.map { |c| Contrib::Redis::Quantize.format_command_args(c) }
58
+ else
59
+ commands.map { |c| Contrib::Redis::Quantize.get_verb(c) }
60
+ end
61
+ end
62
+
63
+ def resolve(redis_config)
64
+ custom = redis_config.custom[:datadog] || {}
65
+
66
+ Datadog.configuration.tracing[:redis, redis_config.server_url].to_h.merge(custom)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -27,7 +27,12 @@ module Datadog
27
27
  end
28
28
 
29
29
  option :distributed_tracing, default: true
30
- option :service_name, default: Ext::DEFAULT_PEER_SERVICE_NAME
30
+
31
+ option :service_name do |o|
32
+ o.default { ENV.fetch(Ext::ENV_SERVICE_NAME, Ext::DEFAULT_PEER_SERVICE_NAME) }
33
+ o.lazy
34
+ end
35
+
31
36
  option :split_by_domain, default: false
32
37
  end
33
38
  end
@@ -8,6 +8,7 @@ module Datadog
8
8
  # @public_api Changing resource names, tag names, or environment variables creates breaking changes.
9
9
  module Ext
10
10
  ENV_ENABLED = 'DD_TRACE_REST_CLIENT_ENABLED'.freeze
11
+ ENV_SERVICE_NAME = 'DD_TRACE_REST_CLIENT_SERVICE_NAME'.freeze
11
12
  ENV_ANALYTICS_ENABLED = 'DD_TRACE_REST_CLIENT_ANALYTICS_ENABLED'.freeze
12
13
  ENV_ANALYTICS_SAMPLE_RATE = 'DD_TRACE_REST_CLIENT_ANALYTICS_SAMPLE_RATE'.freeze
13
14
  DEFAULT_PEER_SERVICE_NAME = 'rest_client'.freeze
@@ -1,19 +1,14 @@
1
1
  # typed: true
2
2
 
3
3
  module Datadog
4
- module Core
4
+ module Tracing
5
5
  module Diagnostics
6
6
  # @public_api
7
7
  module Ext
8
- DD_TRACE_STARTUP_LOGS = 'DD_TRACE_STARTUP_LOGS'.freeze
9
- DD_TRACE_DEBUG = 'DD_TRACE_DEBUG'.freeze
10
- DD_TRACE_ENABLED = 'DD_TRACE_ENABLED'.freeze
11
8
  # Health
12
9
  module Health
13
10
  # Metrics
14
11
  module Metrics
15
- ENV_ENABLED = 'DD_HEALTH_METRICS_ENABLED'.freeze
16
-
17
12
  METRIC_API_ERRORS = 'datadog.tracer.api.errors'.freeze
18
13
  METRIC_API_REQUESTS = 'datadog.tracer.api.requests'.freeze
19
14
  METRIC_API_RESPONSES = 'datadog.tracer.api.responses'.freeze
@@ -0,0 +1,40 @@
1
+ # typed: ignore
2
+
3
+ require_relative 'ext'
4
+
5
+ module Datadog
6
+ module Tracing
7
+ module Diagnostics
8
+ # Health-related diagnostics
9
+ module Health
10
+ # Health metrics for diagnostics
11
+ module Metrics
12
+ def self.extended(base)
13
+ base.class_eval do
14
+ count :api_errors, Ext::Health::Metrics::METRIC_API_ERRORS
15
+ count :api_requests, Ext::Health::Metrics::METRIC_API_REQUESTS
16
+ count :api_responses, Ext::Health::Metrics::METRIC_API_RESPONSES
17
+ count :error_context_overflow, Ext::Health::Metrics::METRIC_ERROR_CONTEXT_OVERFLOW
18
+ count :error_instrumentation_patch, Ext::Health::Metrics::METRIC_ERROR_INSTRUMENTATION_PATCH
19
+ count :error_span_finish, Ext::Health::Metrics::METRIC_ERROR_SPAN_FINISH
20
+ count :error_unfinished_spans, Ext::Health::Metrics::METRIC_ERROR_UNFINISHED_SPANS
21
+ count :instrumentation_patched, Ext::Health::Metrics::METRIC_INSTRUMENTATION_PATCHED
22
+ count :queue_accepted, Ext::Health::Metrics::METRIC_QUEUE_ACCEPTED
23
+ count :queue_accepted_lengths, Ext::Health::Metrics::METRIC_QUEUE_ACCEPTED_LENGTHS
24
+ count :queue_dropped, Ext::Health::Metrics::METRIC_QUEUE_DROPPED
25
+ count :traces_filtered, Ext::Health::Metrics::METRIC_TRACES_FILTERED
26
+ count :transport_trace_too_large, Ext::Health::Metrics::METRIC_TRANSPORT_TRACE_TOO_LARGE
27
+ count :transport_chunked, Ext::Health::Metrics::METRIC_TRANSPORT_CHUNKED
28
+ count :writer_cpu_time, Ext::Health::Metrics::METRIC_WRITER_CPU_TIME
29
+
30
+ gauge :queue_length, Ext::Health::Metrics::METRIC_QUEUE_LENGTH
31
+ gauge :queue_max_length, Ext::Health::Metrics::METRIC_QUEUE_MAX_LENGTH
32
+ gauge :queue_spans, Ext::Health::Metrics::METRIC_QUEUE_SPANS
33
+ gauge :sampling_service_cache_length, Ext::Health::Metrics::METRIC_SAMPLING_SERVICE_CACHE_LENGTH
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -7,9 +7,9 @@ require_relative '../trace_digest'
7
7
  module Datadog
8
8
  module Tracing
9
9
  module Distributed
10
- # B3-style trace propagation.
10
+ # B3 multi header-style trace propagation.
11
11
  # @see https://github.com/openzipkin/b3-propagation#multiple-headers
12
- class B3
12
+ class B3Multi
13
13
  B3_TRACE_ID_KEY = 'x-b3-traceid'
14
14
  B3_SPAN_ID_KEY = 'x-b3-spanid'
15
15
  B3_SAMPLED_KEY = 'x-b3-sampled'
@@ -2,6 +2,7 @@
2
2
  # typed: true
3
3
 
4
4
  require_relative '../sampling/ext'
5
+ require_relative '../utils'
5
6
 
6
7
  module Datadog
7
8
  module Tracing
@@ -47,7 +48,7 @@ module Datadog
47
48
  return if id.nil?
48
49
 
49
50
  # Zero or greater than max allowed value of 2**64
50
- return if id.zero? || id > Span::EXTERNAL_MAX_ID
51
+ return if id.zero? || id > Tracing::Utils::EXTERNAL_MAX_ID
51
52
 
52
53
  id < 0 ? id + (2**64) : id
53
54
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ module Datadog
5
+ module Tracing
6
+ module Distributed
7
+ # Propagator that does not inject nor extract data. It performs no operation.
8
+ # Supported for feature parity with OpenTelemetry.
9
+ # @see https://github.com/open-telemetry/opentelemetry-specification/blob/255a6c52b8914a2ed7e26bb5585abecab276aafc/specification/sdk-environment-variables.md?plain=1#L88
10
+ class None
11
+ # No-op
12
+ def inject!(_digest, _data); end
13
+
14
+ # No-op
15
+ def extract(_data); end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,369 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ module Datadog
5
+ module Tracing
6
+ module Distributed
7
+ # W3C Trace Context propagator implementation, version 00.
8
+ # The trace is propagated through two fields: `traceparent` and `tracestate`.
9
+ # @see https://www.w3.org/TR/trace-context/
10
+ class TraceContext
11
+ TRACEPARENT_KEY = 'traceparent'
12
+ TRACESTATE_KEY = 'tracestate'
13
+ SPEC_VERSION = '00'
14
+
15
+ def initialize(
16
+ fetcher:,
17
+ traceparent_key: TRACEPARENT_KEY,
18
+ tracestate_key: TRACESTATE_KEY
19
+ )
20
+ @fetcher = fetcher
21
+ @traceparent_key = traceparent_key
22
+ @tracestate_key = tracestate_key
23
+ end
24
+
25
+ def inject!(digest, data)
26
+ return if digest.nil?
27
+
28
+ if (traceparent = build_traceparent(digest))
29
+ data[@traceparent_key] = traceparent
30
+
31
+ if (tracestate = build_tracestate(digest))
32
+ data[@tracestate_key] = tracestate
33
+ end
34
+ end
35
+
36
+ data
37
+ end
38
+
39
+ def extract(data)
40
+ fetcher = @fetcher.new(data)
41
+
42
+ trace_id, dd_trace_id, parent_id, sampled, trace_flags = extract_traceparent(fetcher[@traceparent_key])
43
+
44
+ return unless trace_id # Could not parse traceparent
45
+
46
+ tracestate, sampling_priority, origin, tags = extract_tracestate(fetcher[@tracestate_key])
47
+
48
+ sampling_priority = parse_priority_sampling(sampled, sampling_priority)
49
+
50
+ TraceDigest.new(
51
+ span_id: parent_id,
52
+ trace_id: dd_trace_id,
53
+ trace_origin: origin,
54
+ trace_sampling_priority: sampling_priority,
55
+ trace_distributed_tags: tags,
56
+ trace_distributed_id: trace_id,
57
+ trace_state: tracestate,
58
+ trace_flags: trace_flags
59
+ )
60
+ end
61
+
62
+ private
63
+
64
+ # Refinements to ensure newer rubies do not suffer performance impact
65
+ # by needing to use older APIs.
66
+ module Refine
67
+ # Backport `Regexp::match?` because it is measurably the most performant
68
+ # way to check if a string matches a regular expression.
69
+ unless Regexp.method_defined?(:match?)
70
+ refine ::Regexp do
71
+ def match?(*args)
72
+ !match(*args).nil?
73
+ end
74
+ end
75
+ end
76
+
77
+ unless String.method_defined?(:delete_prefix)
78
+ refine ::String do
79
+ def delete_prefix(prefix)
80
+ prefix = prefix.to_s
81
+ if rindex(prefix, 0)
82
+ self[prefix.length..-1]
83
+ else
84
+ dup
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ using Refine
91
+
92
+ # @see https://www.w3.org/TR/trace-context/#traceparent-header
93
+ def build_traceparent(digest)
94
+ build_traceparent_string(
95
+ digest.trace_distributed_id || digest.trace_id,
96
+ digest.span_id,
97
+ build_trace_flags(digest)
98
+ )
99
+ end
100
+
101
+ # For the current version (00), the traceparent has the following format:
102
+ #
103
+ # `"#{version}-#{trace_id}-#{parent_id}-#{trace_flags}"`
104
+ #
105
+ # Where:
106
+ # * `version`: 2 hex-encoded digits, zero padded.
107
+ # * `trace_id`: 32 hex-encoded digits, zero padded.
108
+ # * `parent_id`: 16 hex-encoded digits, zero padded.
109
+ # * `trace_flags`: 2 hex-encoded digits, zero padded.
110
+ #
111
+ # All hex values should be lowercase.
112
+ #
113
+ # @param trace_id [Integer] 128-bit
114
+ # @param parent_id [Integer] 64-bit
115
+ # @param trace_flags [Integer] 8-bit
116
+ def build_traceparent_string(trace_id, parent_id, trace_flags)
117
+ "00-#{format('%032x', trace_id)}-#{format('%016x', parent_id)}-#{format('%02x', trace_flags)}"
118
+ end
119
+
120
+ # Sets the trace flag to an existing `trace_flags`.
121
+ def build_trace_flags(digest)
122
+ trace_flags = digest.trace_flags || DEFAULT_TRACE_FLAGS
123
+
124
+ if digest.trace_sampling_priority
125
+ if Tracing::Sampling::PrioritySampler.sampled?(digest.trace_sampling_priority)
126
+ trace_flags |= TRACE_FLAGS_SAMPLED
127
+ else
128
+ trace_flags &= ~TRACE_FLAGS_SAMPLED
129
+ end
130
+ end
131
+
132
+ trace_flags
133
+ end
134
+
135
+ # @see https://www.w3.org/TR/trace-context/#tracestate-header
136
+ def build_tracestate(digest)
137
+ tracestate = String.new('dd=')
138
+ tracestate << "s:#{digest.trace_sampling_priority};" if digest.trace_sampling_priority
139
+ tracestate << "o:#{serialize_origin(digest.trace_origin)};" if digest.trace_origin
140
+
141
+ if digest.trace_distributed_tags
142
+ digest.trace_distributed_tags.each do |name, value|
143
+ tag = "t.#{serialize_tag_key(name)}:#{serialize_tag_value(value)};"
144
+
145
+ # If tracestate size limit is exceed, drop the remaining data.
146
+ # String#bytesize is used because only ASCII characters are allowed.
147
+ #
148
+ # We add 1 to the limit because of the trailing comma, which will be removed before returning.
149
+ break if tracestate.bytesize + tag.bytesize > (TRACESTATE_VALUE_SIZE_LIMIT + 1)
150
+
151
+ tracestate << tag
152
+ end
153
+ end
154
+
155
+ # Is there any Datadog-specific information to propagate.
156
+ # Check for > 3 size because the empty prefix `dd=` has 3 characters.
157
+ if tracestate.size > 3
158
+ # Propagate upstream tracestate with `dd=...` appended to the list
159
+ tracestate.chop! # Removes trailing `;` from Datadog trace state string.
160
+
161
+ if digest.trace_state
162
+ # Delete existing `dd=` tracestate fields, if present.
163
+ vendors = split_tracestate(digest.trace_state)
164
+ vendors.reject! { |v| v.start_with?('dd=') }
165
+ end
166
+
167
+ if vendors && !vendors.empty?
168
+ # Ensure the list has at most 31 elements, as we need to prepend Datadog's
169
+ # entry and the limit is 32 elements total.
170
+ vendors = vendors[0..30]
171
+ "#{tracestate},#{vendors.join(',')}"
172
+ else
173
+ tracestate.to_s
174
+ end
175
+ else
176
+ digest.trace_state # Propagate upstream tracestate with no Datadog changes
177
+ end
178
+ end
179
+
180
+ # If any characters in <origin_value> are invalid, replace each invalid character with 0x5F (underscore).
181
+ # Invalid characters are: characters outside the ASCII range 0x20 to 0x7E, 0x2C (comma), and 0x3D (equals).
182
+ def serialize_origin(value)
183
+ # DEV: It's unlikely that characters will be out of range, as they mostly
184
+ # DEV: come from Datadog-controlled sources.
185
+ # DEV: Trying to `match?` is measurably faster than a `gsub` that does not match.
186
+ if INVALID_ORIGIN_CHARS.match?(value)
187
+ value.gsub(INVALID_ORIGIN_CHARS, '_')
188
+ else
189
+ value
190
+ end
191
+ end
192
+
193
+ # Serialize `_dd.p.{key}` by first removing the `_dd.p.` prefix.
194
+ # Then replacing invalid characters with `_`.
195
+ def serialize_tag_key(name)
196
+ key = name.delete_prefix(Tracing::Metadata::Ext::Distributed::TAGS_PREFIX)
197
+
198
+ # DEV: It's unlikely that characters will be out of range, as they mostly
199
+ # DEV: come from Datadog-controlled sources.
200
+ # DEV: Trying to `match?` is measurably faster than a `gsub` that does not match.
201
+ if INVALID_TAG_KEY_CHARS.match?(key)
202
+ key.gsub(INVALID_TAG_KEY_CHARS, '_')
203
+ else
204
+ key
205
+ end
206
+ end
207
+
208
+ # Replaces invalid characters with `_`, then replaces `=` with `:`.
209
+ def serialize_tag_value(value)
210
+ # DEV: It's unlikely that characters will be out of range, as they mostly
211
+ # DEV: come from Datadog-controlled sources.
212
+ # DEV: Trying to `match?` is measurably faster than a `gsub` that does not match.
213
+ ret = if INVALID_TAG_VALUE_CHARS.match?(value)
214
+ value.gsub(INVALID_TAG_VALUE_CHARS, '_')
215
+ else
216
+ value
217
+ end
218
+
219
+ # DEV: Checking for an unlikely '=' is faster than a no-op `tr!`.
220
+ ret.tr!('=', ':') if ret.include?('=')
221
+ ret
222
+ end
223
+
224
+ def extract_traceparent(traceparent)
225
+ trace_id, parent_id, trace_flags = parse_traceparent_string(traceparent)
226
+
227
+ # Return unless all traceparent fields are valid.
228
+ return unless trace_id && !trace_id.zero? && parent_id && !parent_id.zero? && trace_flags
229
+
230
+ dd_trace_id = parse_datadog_trace_id(trace_id)
231
+ sampled = parse_sampled_flag(trace_flags)
232
+
233
+ [trace_id, dd_trace_id, parent_id, sampled, trace_flags]
234
+ end
235
+
236
+ def parse_traceparent_string(traceparent)
237
+ return unless traceparent
238
+
239
+ version, trace_id, parent_id, trace_flags, extra = traceparent.strip.split('-')
240
+
241
+ return if version == INVALID_VERSION
242
+
243
+ # Extra fields are not allowed in version 00, but we have to be lenient for future versions.
244
+ return if version == SPEC_VERSION && extra
245
+
246
+ # Invalid field sizes
247
+ return if version.size != 2 || trace_id.size != 32 || parent_id.size != 16 || trace_flags.size != 2
248
+
249
+ [Integer(trace_id, 16), Integer(parent_id, 16), Integer(trace_flags, 16)]
250
+ rescue ArgumentError # Conversion to integer failed
251
+ nil
252
+ end
253
+
254
+ # Datadog only allows 64 bits for the trace id.
255
+ # We extract the lower 64 bits from the original 128-bit id.
256
+ def parse_datadog_trace_id(trace_id)
257
+ trace_id & 0xFFFFFFFFFFFFFFFF
258
+ end
259
+
260
+ def parse_sampled_flag(trace_flags)
261
+ trace_flags & TRACE_FLAGS_SAMPLED
262
+ end
263
+
264
+ # @return [Array<String,Integer,String,Hash>] returns 4 values: tracestate, sampling_priority, origin, tags.
265
+ def extract_tracestate(tracestate)
266
+ return unless tracestate
267
+
268
+ vendors = split_tracestate(tracestate)
269
+
270
+ # Find Datadog's `dd=` tracestate field.
271
+ dd_tracestate = vendors.find { |v| v.start_with?('dd=') }
272
+ return tracestate unless dd_tracestate
273
+
274
+ # Delete `dd=` prefix
275
+ dd_tracestate.slice!(0..2)
276
+
277
+ origin, sampling_priority, tags = extract_datadog_fields(dd_tracestate)
278
+
279
+ [tracestate, sampling_priority, origin, tags]
280
+ end
281
+
282
+ def extract_datadog_fields(dd_tracestate)
283
+ sampling_priority = nil
284
+ origin = nil
285
+ tags = nil
286
+
287
+ # DEV: Since Ruby 2.6 `split` can receive a block, so `each` can be removed then.
288
+ dd_tracestate.split(';').each do |pair|
289
+ key, value = pair.split(':', 2)
290
+ case key
291
+ when 's'
292
+ sampling_priority = Integer(value) rescue nil
293
+ when 'o'
294
+ origin = value
295
+ when /^t\./
296
+ key.slice!(0..1) # Delete `t.` prefix
297
+
298
+ value = deserialize_tag_value(value)
299
+
300
+ tags ||= {}
301
+ tags["#{Tracing::Metadata::Ext::Distributed::TAGS_PREFIX}#{key}"] = value
302
+ end
303
+ end
304
+
305
+ [origin, sampling_priority, tags]
306
+ end
307
+
308
+ # Restore `:` back to `=`.
309
+ def deserialize_tag_value(value)
310
+ value.tr!(':', '=')
311
+ value
312
+ end
313
+
314
+ # If `sampled` and `sampling_priority` disagree, `sampled` overrides the decision.
315
+ # @return [Integer] one of the {Datadog::Tracing::Sampling::Ext::Priority} values
316
+ def parse_priority_sampling(sampled, sampling_priority)
317
+ # If both fields agree
318
+ if sampling_priority &&
319
+ (!Tracing::Sampling::PrioritySampler.sampled?(sampling_priority) && sampled == 0 || # Both drop
320
+ Tracing::Sampling::PrioritySampler.sampled?(sampling_priority) && sampled == 1) # Both keep
321
+
322
+ return sampling_priority # Return the richer `sampling_priority`
323
+ end
324
+
325
+ sampled # Sampled flag trumps `sampling_priority` on conflict
326
+ end
327
+
328
+ def split_tracestate(tracestate)
329
+ tracestate.split(/[ \t]*,[ \t]*/)[0..31]
330
+ end
331
+
332
+ # Version 0xFF is invalid as per spec
333
+ # @see https://www.w3.org/TR/trace-context/#version
334
+ INVALID_VERSION = 'ff'
335
+ private_constant :INVALID_VERSION
336
+
337
+ # Empty 8-bit `trace-flags`.
338
+ # @see https://www.w3.org/TR/trace-context/#trace-flags
339
+ DEFAULT_TRACE_FLAGS = 0b00000000
340
+ private_constant :DEFAULT_TRACE_FLAGS
341
+
342
+ # Bit-mask for `trace-flags` that represents a sampled span (sampled==true).
343
+ # @see https://www.w3.org/TR/trace-context/#trace-flags
344
+ TRACE_FLAGS_SAMPLED = 0b00000001
345
+ private_constant :TRACE_FLAGS_SAMPLED
346
+
347
+ # The limit is inclusive: sizes *greater* than 256 are disallowed.
348
+ # @see https://www.w3.org/TR/trace-context/#value
349
+ TRACESTATE_VALUE_SIZE_LIMIT = 256
350
+ private_constant :TRACESTATE_VALUE_SIZE_LIMIT
351
+
352
+ # Replace all characters with `_`, except ASCII characters 0x20-0x7E.
353
+ # Additionally, `,`, ';', and `=` must also be replaced by `_`.
354
+ INVALID_ORIGIN_CHARS = /[\u0000-\u0019,;=\u007F-\u{10FFFF}]/.freeze
355
+ private_constant :INVALID_ORIGIN_CHARS
356
+
357
+ # Replace all characters with `_`, except ASCII characters 0x21-0x7E.
358
+ # Additionally, `,` and `=` must also be replaced by `_`.
359
+ INVALID_TAG_KEY_CHARS = /[\u0000-\u0020,=\u007F-\u{10FFFF}]/.freeze
360
+ private_constant :INVALID_TAG_KEY_CHARS
361
+
362
+ # Replace all characters with `_`, except ASCII characters 0x20-0x7E.
363
+ # Additionally, `,`, ':' and `;` must also be replaced by `_`.
364
+ INVALID_TAG_VALUE_CHARS = /[\u0000-\u001F,:;\u007F-\u{10FFFF}]/.freeze
365
+ private_constant :INVALID_TAG_VALUE_CHARS
366
+ end
367
+ end
368
+ end
369
+ end
@@ -60,7 +60,7 @@ module Datadog
60
60
  # @public_api
61
61
  module Errors
62
62
  STATUS = 1
63
- TAG_MSG = 'error.msg'
63
+ TAG_MSG = 'error.message'
64
64
  TAG_STACK = 'error.stack'
65
65
  TAG_TYPE = 'error.type'
66
66
  end
@@ -85,6 +85,17 @@ module Datadog
85
85
  @priority_sampler.update(rate_by_service, decision: decision)
86
86
  end
87
87
 
88
+ # Check if the Priority Sampling decision is to keep or drop the trace.
89
+ # Other factors can influence the sampling decision; this method is only
90
+ # responsible for interpreting the Sampling Priority decision.
91
+ #
92
+ # @param priority_sampling [Integer] priority sampling number
93
+ # @return [Boolean] true if trace is "kept" by priority sampling
94
+ # @return [Boolean] false if trace is "dropped" by priority sampling
95
+ def self.sampled?(priority_sampling)
96
+ priority_sampling >= Ext::Priority::AUTO_KEEP
97
+ end
98
+
88
99
  private
89
100
 
90
101
  def pre_sample?(trace)
@@ -3,7 +3,7 @@
3
3
  require_relative '../../core'
4
4
 
5
5
  require_relative 'sampler'
6
- require_relative '../span'
6
+ require_relative '../utils'
7
7
 
8
8
  module Datadog
9
9
  module Tracing
@@ -49,11 +49,11 @@ module Datadog
49
49
 
50
50
  def sample_rate=(sample_rate)
51
51
  @sample_rate = sample_rate
52
- @sampling_id_threshold = sample_rate * Tracing::Span::EXTERNAL_MAX_ID
52
+ @sampling_id_threshold = sample_rate * Tracing::Utils::EXTERNAL_MAX_ID
53
53
  end
54
54
 
55
55
  def sample?(trace)
56
- ((trace.id * KNUTH_FACTOR) % Tracing::Span::EXTERNAL_MAX_ID) <= @sampling_id_threshold
56
+ ((trace.id * KNUTH_FACTOR) % Tracing::Utils::EXTERNAL_MAX_ID) <= @sampling_id_threshold
57
57
  end
58
58
 
59
59
  def sample!(trace)
@@ -2,8 +2,8 @@
2
2
 
3
3
  # typed: true
4
4
 
5
- require_relative '../core/utils'
6
5
  require_relative '../core/utils/safe_dup'
6
+ require_relative 'utils'
7
7
 
8
8
  require_relative 'metadata/ext'
9
9
  require_relative 'metadata'
@@ -19,22 +19,6 @@ module Datadog
19
19
  class Span
20
20
  include Metadata
21
21
 
22
- # The max value for a {Datadog::Tracing::Span} identifier.
23
- # Span and trace identifiers should be strictly positive and strictly inferior to this limit.
24
- #
25
- # Limited to +2<<62-1+ positive integers, as Ruby is able to represent such numbers "inline",
26
- # inside a +VALUE+ scalar, thus not requiring memory allocation.
27
- #
28
- # The range of IDs also has to consider portability across different languages and platforms.
29
- RUBY_MAX_ID = (1 << 62) - 1
30
-
31
- # Excludes zero from possible values
32
- RUBY_ID_RANGE = (1..RUBY_MAX_ID).freeze
33
-
34
- # While we only generate 63-bit integers due to limitations in other languages, we support
35
- # parsing 64-bit integers for distributed tracing since an upstream system may generate one
36
- EXTERNAL_MAX_ID = 1 << 64
37
-
38
22
  attr_accessor \
39
23
  :end_time,
40
24
  :id,
@@ -90,9 +74,9 @@ module Datadog
90
74
  @resource = Core::Utils::SafeDup.frozen_or_dup(resource)
91
75
  @type = Core::Utils::SafeDup.frozen_or_dup(type)
92
76
 
93
- @id = id || Core::Utils.next_id
77
+ @id = id || Tracing::Utils.next_id
94
78
  @parent_id = parent_id || 0
95
- @trace_id = trace_id || Core::Utils.next_id
79
+ @trace_id = trace_id || Tracing::Utils.next_id
96
80
 
97
81
  @meta = meta || {}
98
82
  @metrics = metrics || {}