datadog 2.1.0 → 2.2.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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +53 -2
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +19 -1
  4. data/ext/datadog_profiling_native_extension/collectors_stack.c +41 -0
  5. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +1 -1
  6. data/ext/datadog_profiling_native_extension/crashtracker.c +1 -1
  7. data/ext/datadog_profiling_native_extension/extconf.rb +6 -4
  8. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +47 -1
  9. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +1 -1
  10. data/ext/datadog_profiling_native_extension/stack_recorder.c +13 -6
  11. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
  12. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +1 -1
  13. data/lib/datadog/appsec/extensions.rb +1 -0
  14. data/lib/datadog/core/configuration/components.rb +6 -3
  15. data/lib/datadog/core/configuration/settings.rb +39 -0
  16. data/lib/datadog/core/configuration.rb +3 -17
  17. data/lib/datadog/core/deprecations.rb +58 -0
  18. data/lib/datadog/core/environment/yjit.rb +5 -0
  19. data/lib/datadog/core/runtime/ext.rb +1 -0
  20. data/lib/datadog/core/runtime/metrics.rb +6 -0
  21. data/lib/datadog/core/telemetry/component.rb +107 -0
  22. data/lib/datadog/core/telemetry/event.rb +100 -25
  23. data/lib/datadog/core/telemetry/ext.rb +2 -0
  24. data/lib/datadog/core/telemetry/http/adapters/net.rb +1 -1
  25. data/lib/datadog/core/telemetry/metric.rb +167 -0
  26. data/lib/datadog/core/telemetry/metrics_collection.rb +81 -0
  27. data/lib/datadog/core/telemetry/metrics_manager.rb +81 -0
  28. data/lib/datadog/core/telemetry/request.rb +1 -1
  29. data/lib/datadog/core/telemetry/worker.rb +173 -0
  30. data/lib/datadog/core/utils/only_once_successful.rb +76 -0
  31. data/lib/datadog/core.rb +2 -19
  32. data/lib/datadog/opentelemetry/sdk/propagator.rb +5 -10
  33. data/lib/datadog/opentelemetry/sdk/span_processor.rb +5 -2
  34. data/lib/datadog/profiling/collectors/code_provenance.rb +18 -5
  35. data/lib/datadog/profiling/component.rb +18 -1
  36. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +410 -0
  37. data/lib/datadog/profiling.rb +1 -0
  38. data/lib/datadog/tracing/contrib/action_cable/event.rb +1 -1
  39. data/lib/datadog/tracing/contrib/action_cable/events/broadcast.rb +1 -1
  40. data/lib/datadog/tracing/contrib/action_cable/events/perform_action.rb +1 -1
  41. data/lib/datadog/tracing/contrib/action_cable/events/transmit.rb +1 -1
  42. data/lib/datadog/tracing/contrib/action_mailer/event.rb +4 -6
  43. data/lib/datadog/tracing/contrib/action_mailer/events/deliver.rb +9 -4
  44. data/lib/datadog/tracing/contrib/action_mailer/events/process.rb +3 -2
  45. data/lib/datadog/tracing/contrib/action_view/events/render_partial.rb +1 -5
  46. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +1 -1
  47. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +1 -1
  48. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +1 -1
  49. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +1 -1
  50. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +1 -1
  51. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +1 -1
  52. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +1 -1
  53. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +1 -1
  54. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +1 -1
  55. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +1 -1
  56. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -1
  57. data/lib/datadog/tracing/contrib/active_support/cache/event.rb +32 -0
  58. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +156 -0
  59. data/lib/datadog/tracing/contrib/active_support/cache/events.rb +34 -0
  60. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +45 -41
  61. data/lib/datadog/tracing/contrib/active_support/cache/patcher.rb +17 -40
  62. data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +4 -1
  63. data/lib/datadog/tracing/contrib/active_support/notifications/event.rb +29 -6
  64. data/lib/datadog/tracing/contrib/active_support/notifications/subscriber.rb +16 -4
  65. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +33 -29
  66. data/lib/datadog/tracing/contrib/analytics.rb +5 -0
  67. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +5 -0
  68. data/lib/datadog/tracing/contrib/graphql/patcher.rb +8 -2
  69. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +166 -0
  70. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +25 -0
  71. data/lib/datadog/tracing/contrib/kafka/consumer_event.rb +1 -1
  72. data/lib/datadog/tracing/contrib/kafka/consumer_group_event.rb +1 -1
  73. data/lib/datadog/tracing/contrib/kafka/event.rb +1 -1
  74. data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +3 -3
  75. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +3 -3
  76. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +3 -3
  77. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/heartbeat.rb +3 -3
  78. data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +3 -3
  79. data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +3 -3
  80. data/lib/datadog/tracing/contrib/racecar/event.rb +2 -2
  81. data/lib/datadog/tracing/contrib/rails/ext.rb +9 -0
  82. data/lib/datadog/tracing/contrib/rails/patcher.rb +7 -0
  83. data/lib/datadog/tracing/contrib/rails/runner.rb +95 -0
  84. data/lib/datadog/tracing/distributed/b3_multi.rb +1 -1
  85. data/lib/datadog/tracing/distributed/b3_single.rb +3 -1
  86. data/lib/datadog/tracing/distributed/datadog.rb +2 -2
  87. data/lib/datadog/tracing/distributed/propagation.rb +9 -2
  88. data/lib/datadog/tracing/distributed/trace_context.rb +3 -2
  89. data/lib/datadog/tracing/span_operation.rb +3 -2
  90. data/lib/datadog/tracing/trace_operation.rb +7 -3
  91. data/lib/datadog/tracing/trace_segment.rb +4 -1
  92. data/lib/datadog/tracing/tracer.rb +9 -2
  93. data/lib/datadog/tracing.rb +5 -1
  94. data/lib/datadog/version.rb +2 -2
  95. metadata +22 -9
  96. data/lib/datadog/core/telemetry/client.rb +0 -95
  97. data/lib/datadog/core/telemetry/heartbeat.rb +0 -33
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'event'
4
+
5
+ require_relative '../utils/only_once_successful'
6
+ require_relative '../workers/polling'
7
+ require_relative '../workers/queue'
8
+
9
+ module Datadog
10
+ module Core
11
+ module Telemetry
12
+ # Accumulates events and sends them to the API at a regular interval, including heartbeat event.
13
+ class Worker
14
+ include Core::Workers::Queue
15
+ include Core::Workers::Polling
16
+
17
+ DEFAULT_BUFFER_MAX_SIZE = 1000
18
+ APP_STARTED_EVENT_RETRIES = 10
19
+
20
+ TELEMETRY_STARTED_ONCE = Utils::OnlyOnceSuccessful.new(APP_STARTED_EVENT_RETRIES)
21
+
22
+ def initialize(
23
+ heartbeat_interval_seconds:,
24
+ metrics_aggregation_interval_seconds:,
25
+ emitter:,
26
+ metrics_manager:,
27
+ dependency_collection:,
28
+ enabled: true,
29
+ shutdown_timeout: Workers::Polling::DEFAULT_SHUTDOWN_TIMEOUT,
30
+ buffer_size: DEFAULT_BUFFER_MAX_SIZE
31
+ )
32
+ @emitter = emitter
33
+ @metrics_manager = metrics_manager
34
+ @dependency_collection = dependency_collection
35
+
36
+ @ticks_per_heartbeat = (heartbeat_interval_seconds / metrics_aggregation_interval_seconds).to_i
37
+ @current_ticks = 0
38
+
39
+ # Workers::Polling settings
40
+ self.enabled = enabled
41
+ # Workers::IntervalLoop settings
42
+ self.loop_base_interval = metrics_aggregation_interval_seconds
43
+ self.fork_policy = Core::Workers::Async::Thread::FORK_POLICY_STOP
44
+
45
+ @shutdown_timeout = shutdown_timeout
46
+ @buffer_size = buffer_size
47
+
48
+ self.buffer = buffer_klass.new(@buffer_size)
49
+ end
50
+
51
+ def start
52
+ return if !enabled? || forked?
53
+
54
+ # starts async worker
55
+ perform
56
+ end
57
+
58
+ def stop(force_stop = false, timeout = @shutdown_timeout)
59
+ buffer.close if running?
60
+
61
+ super
62
+ end
63
+
64
+ def enqueue(event)
65
+ return if !enabled? || forked?
66
+
67
+ buffer.push(event)
68
+ end
69
+
70
+ def sent_started_event?
71
+ TELEMETRY_STARTED_ONCE.success?
72
+ end
73
+
74
+ def failed_to_start?
75
+ TELEMETRY_STARTED_ONCE.failed?
76
+ end
77
+
78
+ private
79
+
80
+ def perform(*events)
81
+ return if !enabled? || forked?
82
+
83
+ started! unless sent_started_event?
84
+
85
+ metric_events = @metrics_manager.flush!
86
+ events = [] if events.nil?
87
+ flush_events(events + metric_events)
88
+
89
+ @current_ticks += 1
90
+ return if @current_ticks < @ticks_per_heartbeat
91
+
92
+ @current_ticks = 0
93
+ heartbeat!
94
+ end
95
+
96
+ def flush_events(events)
97
+ return if events.empty?
98
+ return if !enabled? || !sent_started_event?
99
+
100
+ Datadog.logger.debug { "Sending #{events&.count} telemetry events" }
101
+ send_event(Event::MessageBatch.new(events))
102
+ end
103
+
104
+ def heartbeat!
105
+ return if !enabled? || !sent_started_event?
106
+
107
+ send_event(Event::AppHeartbeat.new)
108
+ end
109
+
110
+ def started!
111
+ return unless enabled?
112
+
113
+ if failed_to_start?
114
+ Datadog.logger.debug('Telemetry app-started event exhausted retries, disabling telemetry worker')
115
+ disable!
116
+ return
117
+ end
118
+
119
+ TELEMETRY_STARTED_ONCE.run do
120
+ res = send_event(Event::AppStarted.new)
121
+
122
+ if res.ok?
123
+ Datadog.logger.debug('Telemetry app-started event is successfully sent')
124
+
125
+ send_event(Event::AppDependenciesLoaded.new) if @dependency_collection
126
+
127
+ true
128
+ else
129
+ Datadog.logger.debug('Error sending telemetry app-started event, retry after heartbeat interval...')
130
+ false
131
+ end
132
+ end
133
+ end
134
+
135
+ def send_event(event)
136
+ res = @emitter.request(event)
137
+
138
+ disable_on_not_found!(res)
139
+
140
+ res
141
+ end
142
+
143
+ def dequeue
144
+ buffer.pop
145
+ end
146
+
147
+ def work_pending?
148
+ run_loop? || !buffer.empty?
149
+ end
150
+
151
+ def buffer_klass
152
+ if Core::Environment::Ext::RUBY_ENGINE == 'ruby'
153
+ Core::Buffer::CRuby
154
+ else
155
+ Core::Buffer::ThreadSafe
156
+ end
157
+ end
158
+
159
+ def disable!
160
+ self.enabled = false
161
+ @metrics_manager.disable!
162
+ end
163
+
164
+ def disable_on_not_found!(response)
165
+ return unless response.not_found?
166
+
167
+ Datadog.logger.debug('Agent does not support telemetry; disabling future telemetry events.')
168
+ disable!
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'only_once'
4
+
5
+ module Datadog
6
+ module Core
7
+ module Utils
8
+ # Helper class to execute something with only one success.
9
+ #
10
+ # This is useful for cases where we want to ensure that a block of code is only executed once, and only if it
11
+ # succeeds. One such example is sending app-started telemetry event.
12
+ #
13
+ # Successful execution is determined by the return value of the block: any truthy value is considered success.
14
+ #
15
+ # Thread-safe when used correctly (e.g. be careful of races when lazily initializing instances of this class).
16
+ #
17
+ # Note: In its current state, this class is not Ractor-safe.
18
+ # In https://github.com/DataDog/dd-trace-rb/pull/1398#issuecomment-797378810 we have a discussion of alternatives,
19
+ # including an alternative implementation that is Ractor-safe once spent.
20
+ class OnlyOnceSuccessful < OnlyOnce
21
+ def initialize(limit = 0)
22
+ super()
23
+
24
+ @limit = limit
25
+ @failed = false
26
+ @retries = 0
27
+ end
28
+
29
+ def run
30
+ @mutex.synchronize do
31
+ return if @ran_once
32
+
33
+ result = yield
34
+ @ran_once = !!result
35
+
36
+ if !@ran_once && limited?
37
+ @retries += 1
38
+ check_limit!
39
+ end
40
+
41
+ result
42
+ end
43
+ end
44
+
45
+ def success?
46
+ @mutex.synchronize { @ran_once && !@failed }
47
+ end
48
+
49
+ def failed?
50
+ @mutex.synchronize { @ran_once && @failed }
51
+ end
52
+
53
+ private
54
+
55
+ def check_limit!
56
+ if @retries >= @limit
57
+ @failed = true
58
+ @ran_once = true
59
+ end
60
+ end
61
+
62
+ def limited?
63
+ !@limit.nil? && @limit.positive?
64
+ end
65
+
66
+ def reset_ran_once_state_for_tests
67
+ @mutex.synchronize do
68
+ @ran_once = false
69
+ @failed = false
70
+ @retries = 0
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
data/lib/datadog/core.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'core/deprecations'
3
4
  require_relative 'core/extensions'
4
5
 
5
6
  # We must load core extensions to make certain global APIs
@@ -9,25 +10,7 @@ module Datadog
9
10
  # products. It is a dependency of each product. Contrast with Datadog::Kit
10
11
  # for higher-level features.
11
12
  module Core
12
- class << self
13
- # Records the occurrence of a deprecated operation in this library.
14
- #
15
- # Currently, these operations are logged to `Datadog.logger` at `warn` level.
16
- #
17
- # `disallowed_next_major` adds a message informing that the deprecated operation
18
- # won't be allowed in the next major release.
19
- #
20
- # @yieldreturn [String] a String with the lazily evaluated deprecation message.
21
- # @param [Boolean] disallowed_next_major whether this deprecation will be enforced in the next major release.
22
- def log_deprecation(disallowed_next_major: true)
23
- Datadog.logger.warn do
24
- message = yield
25
- message += ' This will be enforced in the next major release.' if disallowed_next_major
26
- message
27
- end
28
- nil
29
- end
30
- end
13
+ extend Core::Deprecations
31
14
  end
32
15
 
33
16
  extend Core::Extensions
@@ -41,8 +41,11 @@ module Datadog
41
41
  digest = @datadog_propagator.extract(carrier)
42
42
  return context unless digest
43
43
 
44
- trace_id = to_otel_id(digest.trace_id)
45
- span_id = to_otel_id(digest.span_id)
44
+ # Converts the {Numeric} Datadog id object to OpenTelemetry's byte array format.
45
+ # 128-bit unsigned, big-endian integer
46
+ trace_id = [digest.trace_id >> 64, digest.trace_id & 0xFFFFFFFFFFFFFFFF].pack('Q>Q>')
47
+ # 64-bit unsigned, big-endian integer
48
+ span_id = [digest.span_id].pack('Q>')
46
49
 
47
50
  if digest.trace_state || digest.trace_flags
48
51
  trace_flags = ::OpenTelemetry::Trace::TraceFlags.from_byte(digest.trace_flags)
@@ -78,14 +81,6 @@ module Datadog
78
81
  def fields
79
82
  []
80
83
  end
81
-
82
- private
83
-
84
- # Converts the {Numeric} Datadog id object to OpenTelemetry's byte array format.
85
- # This method currently converts an unsigned 64-bit Integer to a binary String.
86
- def to_otel_id(dd_id)
87
- Array(dd_id).pack('Q')
88
- end
89
84
  end
90
85
  end
91
86
  end
@@ -70,7 +70,8 @@ module Datadog
70
70
  if parent_context.trace
71
71
  Tracing.send(:tracer).send(:call_context).activate!(parent_context.ensure_trace)
72
72
  else
73
- Tracing.continue_trace!(nil)
73
+ otel_trace_id = span.context.hex_trace_id.to_i(16)
74
+ Tracing.continue_trace!(Datadog::Tracing::TraceDigest.new(trace_id: otel_trace_id, span_remote: false))
74
75
  end
75
76
 
76
77
  datadog_span = start_datadog_span(span)
@@ -85,7 +86,6 @@ module Datadog
85
86
  name, kwargs = span_arguments(span, attributes)
86
87
 
87
88
  datadog_span = Tracing.trace(name, **kwargs)
88
-
89
89
  datadog_span.set_error([nil, span.status.description]) unless span.status.ok?
90
90
  datadog_span.set_tags(span.attributes)
91
91
 
@@ -143,6 +143,9 @@ module Datadog
143
143
 
144
144
  kwargs[:tags] = attributes.to_h
145
145
 
146
+ # DEV: The datadog span must have the same ID as the OpenTelemetry span
147
+ kwargs[:id] = span.context.hex_span_id.to_i(16)
148
+
146
149
  [name, kwargs]
147
150
  end
148
151
 
@@ -92,25 +92,38 @@ module Datadog
92
92
 
93
93
  seen_files << file_path
94
94
 
95
- _, found_library = libraries_by_path.find { |library_path, _| file_path.start_with?(library_path) }
96
- seen_libraries << found_library if found_library
95
+ # NOTE: Don't use .find, it allocates a lot more memory (see commit that added this note for details)
96
+ libraries_by_path.any? do |library_path, library|
97
+ seen_libraries << library if file_path.start_with?(library_path)
98
+ end
97
99
  end
98
100
  end
99
101
 
100
102
  # Represents metadata we have for a ruby gem
103
+ #
104
+ # Important note: This class gets encoded to JSON with the built-in JSON gem. But, we've found that in some
105
+ # buggy cases, some Ruby gems monkey patch the built-in JSON gem and forget to call #to_json, and instead
106
+ # encode this class instance-field-by-instance-field.
107
+ #
108
+ # Thus, this class was setup to match the JSON output. Take this into consideration if you are adding new
109
+ # fields. (Also, we have a spec for this)
101
110
  class Library
102
- attr_reader :kind, :name, :version, :path
111
+ attr_reader :kind, :name, :version
103
112
 
104
113
  def initialize(kind:, name:, version:, path:)
105
114
  @kind = kind.freeze
106
115
  @name = name.dup.freeze
107
116
  @version = version.to_s.dup.freeze
108
- @path = path.dup.freeze
117
+ @paths = [path.dup.freeze].freeze
109
118
  freeze
110
119
  end
111
120
 
112
121
  def to_json(arg = nil)
113
- { kind: @kind, name: @name, version: @version, paths: [@path] }.to_json(arg)
122
+ { kind: @kind, name: @name, version: @version, paths: @paths }.to_json(arg)
123
+ end
124
+
125
+ def path
126
+ @paths.first
114
127
  end
115
128
  end
116
129
  end
@@ -75,6 +75,10 @@ module Datadog
75
75
  crashtracker = build_crashtracker(settings, transport)
76
76
  profiler = Profiling::Profiler.new(worker: worker, scheduler: scheduler, optional_crashtracker: crashtracker)
77
77
 
78
+ if dir_interruption_workaround_enabled?(settings, no_signals_workaround_enabled)
79
+ Datadog::Profiling::Ext::DirMonkeyPatches.apply!
80
+ end
81
+
78
82
  [profiler, { profiling_enabled: true }]
79
83
  end
80
84
 
@@ -397,8 +401,12 @@ module Datadog
397
401
 
398
402
  # See https://github.com/datadog/dd-trace-rb/issues/2976 for details.
399
403
  private_class_method def self.incompatible_passenger_version?
404
+ first_compatible_version = Gem::Version.new('6.0.19')
405
+
400
406
  if Gem.loaded_specs['passenger']
401
- Gem.loaded_specs['passenger'].version < Gem::Version.new('6.0.19')
407
+ Gem.loaded_specs['passenger'].version < first_compatible_version
408
+ elsif defined?(PhusionPassenger::VERSION_STRING)
409
+ Gem::Version.new(PhusionPassenger::VERSION_STRING) < first_compatible_version
402
410
  else
403
411
  true
404
412
  end
@@ -445,6 +453,15 @@ module Datadog
445
453
  libmysqlclient_version < Gem::Version.new('5.0.0') &&
446
454
  header_version >= Gem::Version.new('10.0.0'))
447
455
  end
456
+
457
+ private_class_method def self.dir_interruption_workaround_enabled?(settings, no_signals_workaround_enabled)
458
+ return false if no_signals_workaround_enabled
459
+
460
+ # NOTE: In the future this method will evolve to check for Ruby versions affected and not apply the workaround
461
+ # when it's not needed but currently all known Ruby versions are affected.
462
+
463
+ settings.profiling.advanced.dir_interruption_workaround_enabled
464
+ end
448
465
  end
449
466
  end
450
467
  end