datadog 2.23.0 → 2.24.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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -2
  3. data/ext/datadog_profiling_native_extension/collectors_stack.c +17 -5
  4. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +239 -0
  5. data/ext/datadog_profiling_native_extension/extconf.rb +4 -1
  6. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +12 -0
  7. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +4 -0
  8. data/ext/datadog_profiling_native_extension/profiling.c +2 -0
  9. data/lib/datadog/appsec/context.rb +2 -1
  10. data/lib/datadog/appsec/remote.rb +1 -9
  11. data/lib/datadog/appsec/security_engine/result.rb +2 -1
  12. data/lib/datadog/core/configuration/config_helper.rb +1 -1
  13. data/lib/datadog/core/configuration/deprecations.rb +2 -2
  14. data/lib/datadog/core/configuration/option_definition.rb +4 -2
  15. data/lib/datadog/core/configuration/options.rb +8 -5
  16. data/lib/datadog/core/configuration/settings.rb +14 -3
  17. data/lib/datadog/core/configuration/supported_configurations.rb +2 -1
  18. data/lib/datadog/core/environment/cgroup.rb +52 -25
  19. data/lib/datadog/core/environment/container.rb +140 -46
  20. data/lib/datadog/core/environment/ext.rb +1 -0
  21. data/lib/datadog/core/environment/process.rb +9 -1
  22. data/lib/datadog/core/rate_limiter.rb +9 -1
  23. data/lib/datadog/core/remote/client.rb +14 -6
  24. data/lib/datadog/core/remote/component.rb +6 -4
  25. data/lib/datadog/core/remote/configuration/content.rb +15 -2
  26. data/lib/datadog/core/remote/configuration/digest.rb +14 -7
  27. data/lib/datadog/core/remote/configuration/repository.rb +1 -1
  28. data/lib/datadog/core/remote/configuration/target.rb +13 -6
  29. data/lib/datadog/core/remote/transport/config.rb +3 -16
  30. data/lib/datadog/core/remote/transport/http/config.rb +4 -44
  31. data/lib/datadog/core/remote/transport/http/negotiation.rb +0 -39
  32. data/lib/datadog/core/remote/transport/http.rb +13 -24
  33. data/lib/datadog/core/remote/transport/negotiation.rb +7 -16
  34. data/lib/datadog/core/telemetry/component.rb +52 -13
  35. data/lib/datadog/core/telemetry/event/app_started.rb +34 -0
  36. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
  37. data/lib/datadog/core/telemetry/metrics_manager.rb +9 -0
  38. data/lib/datadog/core/telemetry/request.rb +17 -3
  39. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +2 -32
  40. data/lib/datadog/core/telemetry/transport/http.rb +21 -16
  41. data/lib/datadog/core/telemetry/transport/telemetry.rb +3 -10
  42. data/lib/datadog/core/telemetry/worker.rb +88 -32
  43. data/lib/datadog/core/transport/ext.rb +2 -0
  44. data/lib/datadog/core/transport/http/api/endpoint.rb +9 -4
  45. data/lib/datadog/core/transport/http/api/instance.rb +4 -21
  46. data/lib/datadog/core/transport/http/builder.rb +9 -5
  47. data/lib/datadog/core/transport/http/client.rb +19 -8
  48. data/lib/datadog/core/transport/http.rb +22 -19
  49. data/lib/datadog/core/transport/response.rb +9 -0
  50. data/lib/datadog/core/transport/transport.rb +90 -0
  51. data/lib/datadog/core/utils/only_once_successful.rb +2 -0
  52. data/lib/datadog/core/utils/time.rb +1 -1
  53. data/lib/datadog/core/workers/async.rb +10 -1
  54. data/lib/datadog/core/workers/interval_loop.rb +44 -3
  55. data/lib/datadog/core/workers/polling.rb +2 -0
  56. data/lib/datadog/core/workers/queue.rb +100 -1
  57. data/lib/datadog/data_streams/processor.rb +1 -1
  58. data/lib/datadog/data_streams/transport/http/stats.rb +1 -36
  59. data/lib/datadog/data_streams/transport/http.rb +5 -6
  60. data/lib/datadog/data_streams/transport/stats.rb +3 -17
  61. data/lib/datadog/di/contrib/active_record.rb +31 -5
  62. data/lib/datadog/di/el/compiler.rb +8 -4
  63. data/lib/datadog/di/error.rb +5 -0
  64. data/lib/datadog/di/instrumenter.rb +17 -4
  65. data/lib/datadog/di/probe_builder.rb +2 -1
  66. data/lib/datadog/di/probe_manager.rb +37 -31
  67. data/lib/datadog/di/probe_notification_builder.rb +15 -2
  68. data/lib/datadog/di/remote.rb +89 -84
  69. data/lib/datadog/di/transport/diagnostics.rb +7 -35
  70. data/lib/datadog/di/transport/http/diagnostics.rb +1 -31
  71. data/lib/datadog/di/transport/http/input.rb +1 -31
  72. data/lib/datadog/di/transport/http.rb +28 -17
  73. data/lib/datadog/di/transport/input.rb +7 -34
  74. data/lib/datadog/di.rb +61 -5
  75. data/lib/datadog/open_feature/evaluation_engine.rb +2 -1
  76. data/lib/datadog/open_feature/remote.rb +3 -10
  77. data/lib/datadog/open_feature/transport.rb +9 -11
  78. data/lib/datadog/opentelemetry/api/baggage.rb +1 -1
  79. data/lib/datadog/opentelemetry/configuration/settings.rb +2 -2
  80. data/lib/datadog/opentelemetry/metrics.rb +21 -14
  81. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +5 -8
  82. data/lib/datadog/profiling/collectors/code_provenance.rb +27 -2
  83. data/lib/datadog/profiling/collectors/info.rb +2 -1
  84. data/lib/datadog/profiling/component.rb +12 -11
  85. data/lib/datadog/profiling/http_transport.rb +4 -1
  86. data/lib/datadog/tracing/contrib/extensions.rb +10 -2
  87. data/lib/datadog/tracing/contrib/karafka/patcher.rb +31 -32
  88. data/lib/datadog/tracing/contrib/status_range_matcher.rb +2 -1
  89. data/lib/datadog/tracing/contrib/utils/quantization/hash.rb +3 -1
  90. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +6 -3
  91. data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -1
  92. data/lib/datadog/tracing/remote.rb +1 -9
  93. data/lib/datadog/tracing/span_event.rb +2 -2
  94. data/lib/datadog/tracing/span_operation.rb +9 -4
  95. data/lib/datadog/tracing/trace_operation.rb +44 -6
  96. data/lib/datadog/tracing/tracer.rb +42 -16
  97. data/lib/datadog/tracing/transport/http/traces.rb +2 -50
  98. data/lib/datadog/tracing/transport/http.rb +15 -9
  99. data/lib/datadog/tracing/transport/io/client.rb +1 -1
  100. data/lib/datadog/tracing/transport/traces.rb +6 -66
  101. data/lib/datadog/tracing/workers/trace_writer.rb +5 -0
  102. data/lib/datadog/tracing/writer.rb +1 -0
  103. data/lib/datadog/version.rb +2 -2
  104. metadata +7 -13
  105. data/lib/datadog/core/remote/transport/http/api.rb +0 -53
  106. data/lib/datadog/core/telemetry/transport/http/api.rb +0 -43
  107. data/lib/datadog/core/transport/http/api/spec.rb +0 -36
  108. data/lib/datadog/data_streams/transport/http/api.rb +0 -33
  109. data/lib/datadog/data_streams/transport/http/client.rb +0 -21
  110. data/lib/datadog/di/transport/http/api.rb +0 -42
  111. data/lib/datadog/opentelemetry/api/baggage.rbs +0 -26
  112. data/lib/datadog/tracing/transport/http/api.rb +0 -44
@@ -10,7 +10,7 @@ module Datadog
10
10
  # Current monotonic time
11
11
  #
12
12
  # @param unit [Symbol] unit for the resulting value, same as ::Process#clock_gettime, defaults to :float_second
13
- # @return [Numeric] timestamp in the requested unit, since some unspecified starting point
13
+ # @return [Float|Integer] timestamp in the requested unit, since some unspecified starting point
14
14
  def get_time(unit = :float_second)
15
15
  Process.clock_gettime(Process::CLOCK_MONOTONIC, unit)
16
16
  end
@@ -8,6 +8,11 @@ module Datadog
8
8
  module Async
9
9
  # Adds threading behavior to workers
10
10
  # to run tasks asynchronously.
11
+ #
12
+ # This module is included in Polling module, and has no other
13
+ # direct users.
14
+ #
15
+ # @api private
11
16
  module Thread
12
17
  FORK_POLICY_STOP = :stop
13
18
  FORK_POLICY_RESTART = :restart
@@ -24,7 +29,11 @@ module Datadog
24
29
  # Methods that must be prepended
25
30
  module PrependedMethods
26
31
  def perform(*args)
27
- start_async { self.result = super(*args) } unless started?
32
+ unless started?
33
+ start_async do
34
+ self.result = super(*args)
35
+ end
36
+ end
28
37
  end
29
38
  end
30
39
 
@@ -5,6 +5,11 @@ module Datadog
5
5
  module Workers
6
6
  # Adds looping behavior to workers, with a sleep
7
7
  # interval between each loop.
8
+ #
9
+ # This module is included in Polling module, and has no other
10
+ # direct users.
11
+ #
12
+ # @api private
8
13
  module IntervalLoop
9
14
  BACK_OFF_RATIO = 1.2
10
15
  BACK_OFF_MAX = 5
@@ -38,15 +43,39 @@ module Datadog
38
43
 
39
44
  def stop_loop
40
45
  mutex.synchronize do
41
- return false unless run_loop?
42
-
46
+ # Do not call run_loop? from this method to see if the loop
47
+ # is running, because @run_loop is normally initialized by
48
+ # the background thread and if the stop is requested right
49
+ # after the worker starts, the background thread may be created
50
+ # (and scheduled) but hasn't run yet, thus skipping the
51
+ # write to @run_loop here would leave the thread running forever.
43
52
  @run_loop = false
53
+
54
+ # It is possible that we don't need to signal shutdown if
55
+ # @run_loop was not initialized (i.e. we changed it from not
56
+ # defined to false above). But let's be safe and signal the
57
+ # shutdown anyway, I don't see what harm it can cause.
44
58
  shutdown.signal
45
59
  end
46
60
 
61
+ # Previously, this method would return false (and do nothing)
62
+ # if the worker was not running the loop. However, this was racy -
63
+ # see https://github.com/DataDog/ruby-guild/issues/279.
64
+ # stop_loop now always sets the state to "stop requested" and,
65
+ # correspondingly, always returns true.
66
+ #
67
+ # There is some test code that returns false when mocking this
68
+ # method - most likely this method should be treated as a void one
69
+ # and the caller should assume that the stop was always requested.
47
70
  true
48
71
  end
49
72
 
73
+ # TODO This overwrites Queue's +work_pending?+ method with an
74
+ # implementation that, to me, is at leat questionable semantically:
75
+ # the Queue's idea of pending work is if the buffer is not empty,
76
+ # but this module says that work is pending if the work processing
77
+ # loop is scheduled to run (in other words, as long as the background
78
+ # thread is running, there is always pending work).
50
79
  def work_pending?
51
80
  run_loop?
52
81
  end
@@ -104,7 +133,19 @@ module Datadog
104
133
 
105
134
  def perform_loop
106
135
  mutex.synchronize do
107
- @run_loop = true
136
+ unless defined?(@run_loop)
137
+ # This write must only happen if @run_loop is not defined
138
+ # (i.e., not initialized). In the case when the worker is
139
+ # asked to stop right after it is created, the thread may not
140
+ # have run yet by the time +stop_loop+ is invoked and
141
+ # we need to preserve the stop-requested state from
142
+ # +stop_loop+ to +perform_loop+.
143
+ #
144
+ # If the workers are refactored to use classes and inheritance
145
+ # and their state, such as @run_loop, is initialized in
146
+ # constructors, the write can be made unconditional.
147
+ @run_loop = true
148
+ end
108
149
 
109
150
  shutdown.wait(mutex, loop_wait_time) if loop_wait_before_first_iteration?
110
151
  end
@@ -7,6 +7,8 @@ module Datadog
7
7
  module Core
8
8
  module Workers
9
9
  # Adds polling (async looping) behavior to workers
10
+ #
11
+ # @api private
10
12
  module Polling
11
13
  DEFAULT_SHUTDOWN_TIMEOUT = 1
12
14
 
@@ -5,6 +5,17 @@ module Datadog
5
5
  module Workers
6
6
  # Adds queue behavior to workers, with a buffer
7
7
  # to which items can be queued then dequeued.
8
+ #
9
+ # This module is included in some but not all workers.
10
+ # Notably, Data Streams Processor uses a queue but implements it
11
+ # inline rather than using this module.
12
+ #
13
+ # The workers that do include Queue also include Polling, which
14
+ # in turn includes Async::Thread and IntervalLoop. This means
15
+ # we have e.g. +in_iteration?+ always available in any worker
16
+ # that includes Queue.
17
+ #
18
+ # @api private
8
19
  module Queue
9
20
  def self.included(base)
10
21
  base.prepend(PrependedMethods)
@@ -13,11 +24,16 @@ module Datadog
13
24
  # Methods that must be prepended
14
25
  module PrependedMethods
15
26
  def perform(*args)
16
- super(*dequeue) if work_pending?
27
+ if work_pending?
28
+ work = dequeue
29
+ super(*work)
30
+ end
17
31
  end
18
32
  end
19
33
 
20
34
  def buffer
35
+ # Why is this an unsynchronized Array and not a Core::Buffer
36
+ # instance?
21
37
  @buffer ||= []
22
38
  end
23
39
 
@@ -34,10 +50,93 @@ module Datadog
34
50
  !buffer.empty?
35
51
  end
36
52
 
53
+ # Wait for the worker to finish handling all work that has already
54
+ # been submitted to it.
55
+ #
56
+ # If the worker is not enabled, returns nil.
57
+ # If the worker is enabled, returns whether, at the point of return,
58
+ # there was no pending or in progress work.
59
+ #
60
+ # Flushing can time out because there is a constant stream of work
61
+ # submitted at the same or higher rate than it is processed.
62
+ # Flushing can also fail if the worker thread is not running -
63
+ # this method will not flush from the calling thread.
64
+ def flush(timeout: nil)
65
+ # Default timeout is 5 seconds.
66
+ # Specific workers can override it to be more or less
67
+ timeout ||= 5
68
+
69
+ # Nothing needs to be done if the worker is not enabled.
70
+ return nil unless enabled?
71
+
72
+ unless running?
73
+ unless buffer.empty?
74
+ # If we are asked to flush but the worker is not running,
75
+ # do not flush from the caller thread. If the buffer is not
76
+ # empty, it will not be flushed. Log a warning to this effect.
77
+ #
78
+ # We are not guaranteed to have a logger as an instance method,
79
+ # reference the global for now - all other worker methods
80
+ # also reference the logger globally.
81
+ # TODO inject it into worker instances.
82
+ Datadog.logger.debug { "Asked to flush #{self} when the worker is not running" }
83
+ return false
84
+ end
85
+ end
86
+
87
+ started = Utils::Time.get_time
88
+ loop do
89
+ # The AppStarted event is triggered by the worker itself,
90
+ # from the worker thread. As such the main thread has no way
91
+ # to delay itself until that event is queued and we need some
92
+ # way to wait until that event is sent out to assert on it in
93
+ # the test suite. Check the run once flag which *should*
94
+ # indicate the event has been queued (at which point our queue
95
+ # depth check should wait until it's sent).
96
+ # This is still a hack because the flag can be overridden
97
+ # either way with or without the event being sent out.
98
+ # Note that if the AppStarted sending fails, this check
99
+ # will return false and flushing will be blocked until the
100
+ # 15 second timeout.
101
+ # Note that the first wait interval between telemetry event
102
+ # sending is 10 seconds, the timeout needs to be strictly
103
+ # greater than that.
104
+ return true if idle?
105
+
106
+ return false if Utils::Time.get_time - started > timeout
107
+
108
+ sleep 0.5
109
+ end
110
+ end
111
+
37
112
  protected
38
113
 
39
114
  attr_writer \
40
115
  :buffer
116
+
117
+ # Returns whether this worker has no pending work and is not actively
118
+ # working.
119
+ #
120
+ # The reason why "actively working" is considered is that we use
121
+ # flushing to ensure all work is completed before asserting on the
122
+ # outcome in the tests - if work is happening in a background thread,
123
+ # it's too early to assert on its results.
124
+ def idle?
125
+ # We have a +work_pending?+ method in this class that semantically
126
+ # would be appropriate here instead of calling +buffer.empty?+.
127
+ # Unfortunately IntervalLoop replaces our implementation of
128
+ # +work_pending?+ with one that doesn't make sense at least for the
129
+ # Queue. And we can't change the order of module includes because
130
+ # they all override +perform+ and the correct behavior depends on
131
+ # placing IntervalLoop after Queue.
132
+ #
133
+ # The TraceWriter worker then defines +work_pending?+ to be the
134
+ # same as Queue implementation here... Essentially, it demands
135
+ # the behavior that perhaps should be applied to all workers.
136
+ #
137
+ # Until this mess is untangled, call +buffer.empty?+ here.
138
+ buffer.empty? && !in_iteration?
139
+ end
41
140
  end
42
141
  end
43
142
  end
@@ -242,7 +242,7 @@ module Datadog
242
242
  current_context = get_current_context
243
243
  tags = tags.sort
244
244
 
245
- direction = nil
245
+ direction = nil #: ::String?
246
246
  tags.each do |tag|
247
247
  if tag.start_with?('direction:')
248
248
  direction = tag
@@ -1,11 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../stats'
4
- require_relative 'client'
5
- require_relative '../../../core/transport/http/response'
6
4
  require_relative '../../../core/transport/http/api/endpoint'
7
- require_relative '../../../core/transport/http/api/spec'
8
- require_relative '../../../core/transport/http/api/instance'
5
+ require_relative '../../../core/transport/http/response'
9
6
 
10
7
  module Datadog
11
8
  module DataStreams
@@ -23,38 +20,6 @@ module Datadog
23
20
  end
24
21
 
25
22
  module API
26
- # HTTP API Spec for DSM
27
- class Spec < Core::Transport::HTTP::API::Spec
28
- attr_accessor :stats
29
-
30
- def send_stats(env, &block)
31
- raise Core::Transport::HTTP::API::Spec::EndpointNotDefinedError.new('stats', self) if stats.nil?
32
-
33
- stats.call(env, &block)
34
- end
35
-
36
- def encoder
37
- # DSM handles encoding in the transport layer (MessagePack + gzip)
38
- # so we don't need an encoder at the API level
39
- nil
40
- end
41
- end
42
-
43
- # HTTP API Instance for DSM
44
- class Instance < Core::Transport::HTTP::API::Instance
45
- def send_stats(env)
46
- unless spec.is_a?(Stats::API::Spec)
47
- raise Core::Transport::HTTP::API::Instance::EndpointNotSupportedError.new(
48
- 'stats', self
49
- )
50
- end
51
-
52
- spec.send_stats(env) do |request_env|
53
- call(request_env)
54
- end
55
- end
56
- end
57
-
58
23
  # Endpoint for submitting DSM stats data
59
24
  class Endpoint < Core::Transport::HTTP::API::Endpoint
60
25
  def initialize(path)
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../../core/transport/http'
4
- require_relative 'http/api'
5
- require_relative 'http/client'
6
4
  require_relative 'http/stats'
7
5
  require_relative 'stats'
8
6
 
@@ -11,6 +9,10 @@ module Datadog
11
9
  module Transport
12
10
  # HTTP transport for Data Streams Monitoring
13
11
  module HTTP
12
+ V01 = Stats::API::Endpoint.new(
13
+ '/v0.1/pipeline_stats'
14
+ )
15
+
14
16
  module_function
15
17
 
16
18
  # Builds a new Transport::HTTP::Client with default settings
@@ -19,7 +21,6 @@ module Datadog
19
21
  logger:
20
22
  )
21
23
  Core::Transport::HTTP.build(
22
- api_instance_class: Stats::API::Instance,
23
24
  agent_settings: agent_settings,
24
25
  logger: logger,
25
26
  headers: {
@@ -27,9 +28,7 @@ module Datadog
27
28
  'Content-Encoding' => 'gzip'
28
29
  }
29
30
  ) do |transport|
30
- apis = API.defaults
31
-
32
- transport.api API::V01, apis[API::V01], default: true
31
+ transport.api 'v0.1', V01, default: true
33
32
 
34
33
  # Call block to apply any customization, if provided
35
34
  yield(transport) if block_given?
@@ -4,6 +4,7 @@ require 'msgpack'
4
4
  require 'zlib'
5
5
  require_relative '../../core/transport/parcel'
6
6
  require_relative '../../core/transport/request'
7
+ require_relative '../../core/transport/transport'
7
8
 
8
9
  module Datadog
9
10
  module DataStreams
@@ -25,18 +26,7 @@ module Datadog
25
26
  end
26
27
 
27
28
  # Transport for Data Streams Monitoring stats
28
- class Transport
29
- attr_reader :client, :apis, :current_api_id, :logger
30
-
31
- def initialize(apis, default_api, logger:)
32
- @apis = apis
33
- @logger = logger
34
- @default_api = default_api
35
- @current_api_id = default_api
36
-
37
- @client = DataStreams::Transport::HTTP::Client.new(current_api, logger: @logger)
38
- end
39
-
29
+ class Transport < Core::Transport::Transport
40
30
  def send_stats(payload)
41
31
  # MessagePack encode and gzip compress the payload
42
32
  msgpack_data = MessagePack.pack(payload)
@@ -47,11 +37,7 @@ module Datadog
47
37
  request = Request.new(parcel)
48
38
 
49
39
  # Send to agent
50
- client.send_stats_payload(request)
51
- end
52
-
53
- def current_api
54
- apis[@current_api_id]
40
+ client.send_request(:stats, request)
55
41
  end
56
42
  end
57
43
  end
@@ -1,12 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Datadog::DI::Serializer.register(condition: lambda { |value| ActiveRecord::Base === value }) do |serializer, value, name:, depth:| # steep:ignore
4
- # steep thinks all of the arguments are nil here
5
- # steep:ignore:start
3
+ # steep thinks all of the arguments are nil here and does not know what ActiveRecord is.
4
+ # steep:ignore:start
5
+
6
+ Datadog::DI::Serializer.register(
7
+ # This serializer uses a dynamic condition to determine its applicability
8
+ # to a particular value. A simpler case could have been a serializer for
9
+ # a particular class, but in this case any ActiveRecord model is covered
10
+ # and they all have different classes.
11
+ #
12
+ # An alternative could have been to make DI specifically provide lookup
13
+ # logic for "instances of classes derived from X", but a condition Proc
14
+ # is more universal.
15
+ condition: lambda { |value| ActiveRecord::Base === value }
16
+ ) do |serializer, value, name:, depth:|
17
+ # +serializer+ is an instance of DI::Serializer.
18
+ # Use it to perform the serialization to primitive values.
19
+ #
20
+ # +value+ is the value to serialize. It should match the condition
21
+ # provided above, meaning it would be an ActiveRecord::Base instance.
22
+ #
23
+ # +name+ is the name of the (local/instance) variable being serialized.
24
+ # The name is used by DI for redaction (upstream of serialization logic),
25
+ # and could potentially be used for redaction here also.
26
+ #
27
+ # +depth+ is the remaining depth for serializing collections and objects.
28
+ # It should always be an integer.
29
+ # Reduce it by 1 when invoking +serialize_value+ on the contents of +value+.
30
+ # This serializer could also potentially do its own depth limiting.
6
31
  value_to_serialize = {
7
32
  attributes: value.attributes,
8
33
  new_record: value.new_record?,
9
34
  }
10
- serializer.serialize_value(value_to_serialize, depth: depth ? depth - 1 : nil, type: value.class)
11
- # steep:ignore:end
35
+ serializer.serialize_value(value_to_serialize, depth: depth - 1, type: value.class)
12
36
  end
37
+
38
+ # steep:ignore:end
@@ -22,7 +22,8 @@ module Datadog
22
22
 
23
23
  private
24
24
 
25
- OPERATORS = {
25
+ # Steep: https://github.com/soutaro/steep/issues/363
26
+ OPERATORS = { # steep:ignore IncompatibleAssignment
26
27
  'eq' => '==',
27
28
  'ne' => '!=',
28
29
  'ge' => '>=',
@@ -31,16 +32,19 @@ module Datadog
31
32
  'lt' => '<',
32
33
  }.freeze
33
34
 
35
+ # Steep: https://github.com/soutaro/steep/issues/363
34
36
  SINGLE_ARG_METHODS = %w[
35
37
  len isEmpty isUndefined
36
- ].freeze
38
+ ].freeze # steep:ignore IncompatibleAssignment
37
39
 
40
+ # Steep: https://github.com/soutaro/steep/issues/363
38
41
  TWO_ARG_METHODS = %w[
39
42
  startsWith endsWith contains matches
40
43
  getmember index instanceof
41
- ].freeze
44
+ ].freeze # steep:ignore IncompatibleAssignment
42
45
 
43
- MULTI_ARG_METHODS = {
46
+ # Steep: https://github.com/soutaro/steep/issues/363
47
+ MULTI_ARG_METHODS = { # steep:ignore IncompatibleAssignment
44
48
  'and' => '&&',
45
49
  'or' => '||',
46
50
  }.freeze
@@ -42,6 +42,11 @@ module Datadog
42
42
  class ProbePreviouslyFailed < Error
43
43
  end
44
44
 
45
+ # Raised when trying to instrument a probe when there is existing
46
+ # instrumentation for the same probe id.
47
+ class AlreadyInstrumented < Error
48
+ end
49
+
45
50
  # Raised when installing a line probe and multiple files match the
46
51
  # specified path suffix.
47
52
  # A probe must be installed into one file only, since UI only
@@ -166,7 +166,10 @@ module Datadog
166
166
  depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
167
167
  attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
168
168
  end
169
- start_time = Core::Utils::Time.get_time
169
+ # We intentionally do not use Core::Utils::Time.get_time
170
+ # here because the time provider may be overridden by the
171
+ # customer, and DI is not allowed to invoke customer code.
172
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
170
173
 
171
174
  rv = nil
172
175
  begin
@@ -190,7 +193,7 @@ module Datadog
190
193
  # the instrumentation callback runs.
191
194
  end
192
195
 
193
- duration = Core::Utils::Time.get_time - start_time
196
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
194
197
  # The method itself is not part of the stack trace because
195
198
  # we are getting the stack trace from outside of the method.
196
199
  # Add the method in manually as the top frame.
@@ -256,6 +259,8 @@ module Datadog
256
259
 
257
260
  probe.instrumentation_module = mod
258
261
  cls.send(:prepend, mod)
262
+
263
+ DI.instrumented_count_inc(:method)
259
264
  end
260
265
  end
261
266
 
@@ -268,6 +273,8 @@ module Datadog
268
273
  if mod = probe.instrumentation_module
269
274
  mod.send(:remove_method, probe.method_name)
270
275
  probe.instrumentation_module = nil
276
+
277
+ DI.instrumented_count_dec(:method)
271
278
  end
272
279
  end
273
280
  end
@@ -470,12 +477,16 @@ module Datadog
470
477
  # actual_path could be nil if we don't use targeted trace points.
471
478
  probe.instrumented_path = actual_path
472
479
 
473
- if iseq
480
+ # TracePoint#enable returns false when it succeeds.
481
+ rv = if iseq
474
482
  tp.enable(target: iseq, target_line: line_no)
475
483
  else
476
484
  tp.enable
477
485
  end
478
- # TracePoint#enable returns false when it succeeds.
486
+
487
+ DI.instrumented_count_inc(:line)
488
+
489
+ rv
479
490
  end
480
491
  true
481
492
  end
@@ -485,6 +496,8 @@ module Datadog
485
496
  if tp = probe.instrumentation_trace_point
486
497
  tp.disable
487
498
  probe.instrumentation_trace_point = nil
499
+
500
+ DI.instrumented_count_dec(:line)
488
501
  end
489
502
  end
490
503
  end
@@ -20,7 +20,8 @@ module Datadog
20
20
  #
21
21
  # @api private
22
22
  module ProbeBuilder
23
- PROBE_TYPES = {
23
+ # Steep: https://github.com/soutaro/steep/issues/363
24
+ PROBE_TYPES = { # steep:ignore IncompatibleAssignment
24
25
  'LOG_PROBE' => :log,
25
26
  }.freeze
26
27
 
@@ -94,6 +94,19 @@ module Datadog
94
94
  # matches.
95
95
  def add_probe(probe)
96
96
  @lock.synchronize do
97
+ if @installed_probes[probe.id]
98
+ # Either this probe was already installed, or another probe was
99
+ # installed with the same id (previous version perhaps?).
100
+ # Since our state tracking is keyed by probe id, we cannot
101
+ # install this probe since we won't have a way of removing the
102
+ # instrumentation for the probe with the same id which is already
103
+ # installed.
104
+ #
105
+ # The exception raised here will be caught below and logged and
106
+ # reported to telemetry.
107
+ raise Error::AlreadyInstrumented, "Probe with id #{probe.id} is already in installed probes"
108
+ end
109
+
97
110
  # Probe failed to install previously, do not try to install it again.
98
111
  if msg = @failed_probes[probe.id]
99
112
  # TODO test this path
@@ -134,38 +147,30 @@ module Datadog
134
147
  end
135
148
  end
136
149
 
137
- # Removes probes with ids other than in the specified list.
138
- #
139
- # This method is meant to be invoked from remote config processor.
140
- # Remote config contains the list of currently defined probes; any
141
- # probes not in that list have been removed by user and should be
142
- # de-instrumented from the application.
143
- def remove_other_probes(probe_ids)
150
+ # Removes probe with specified id. The probe could be pending or
151
+ # installed. Does nothing if there is no probe with the specified id.
152
+ def remove_probe(probe_id)
144
153
  @lock.synchronize do
145
- @pending_probes.values.each do |probe|
146
- unless probe_ids.include?(probe.id)
147
- @pending_probes.delete(probe.id)
148
- end
149
- end
150
- @installed_probes.values.each do |probe|
151
- unless probe_ids.include?(probe.id)
152
- begin
153
- instrumenter.unhook(probe)
154
- # Only remove the probe from installed list if it was
155
- # successfully de-instrumented. Active probes do incur overhead
156
- # for the running application, and if the error is ephemeral
157
- # we want to try removing the probe again at the next opportunity.
158
- #
159
- # TODO give up after some time?
160
- @installed_probes.delete(probe.id)
161
- rescue => exc
162
- raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
163
- # Silence all exceptions?
164
- # TODO should we propagate here and rescue upstream?
165
- logger.debug { "di: error removing #{probe.type} probe at #{probe.location} (#{probe.id}): #{exc.class}: #{exc}" }
166
- telemetry&.report(exc, description: "Error removing probe")
167
- end
168
- end
154
+ @pending_probes.delete(probe_id)
155
+ end
156
+
157
+ # Do not delete the probe from the registry here in case
158
+ # deinstrumentation fails - though I don't know why deinstrumentation
159
+ # would fail and how we could recover if it does.
160
+ # I plan on tracking the number of outstanding (instrumented) probes
161
+ # in the future, and if deinstrumentation fails I would want to
162
+ # keep that probe as "installed" for the count, so that we can
163
+ # investigate the situation.
164
+ if probe = @installed_probes[probe_id]
165
+ begin
166
+ instrumenter.unhook(probe)
167
+ @installed_probes.delete(probe_id)
168
+ rescue => exc
169
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
170
+ # Silence all exceptions?
171
+ # TODO should we propagate here and rescue upstream?
172
+ logger.debug { "di: error removing #{probe.type} probe at #{probe.location} (#{probe.id}): #{exc.class}: #{exc}" }
173
+ telemetry&.report(exc, description: "Error removing probe")
169
174
  end
170
175
  end
171
176
  end
@@ -185,6 +190,7 @@ module Datadog
185
190
  # TODO is it OK to hook from trace point handler?
186
191
  # TODO the class is now defined, but can hooking still fail?
187
192
  instrumenter.hook(probe, self)
193
+ @installed_probes[probe.id] = probe
188
194
  @pending_probes.delete(probe.id)
189
195
  break
190
196
  rescue Error::DITargetNotDefined