datadog 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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