datadog 2.28.0 → 2.30.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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +87 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +82 -12
  4. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +32 -11
  5. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -1
  6. data/ext/datadog_profiling_native_extension/extconf.rb +9 -24
  7. data/ext/datadog_profiling_native_extension/heap_recorder.c +186 -55
  8. data/ext/datadog_profiling_native_extension/heap_recorder.h +12 -1
  9. data/ext/datadog_profiling_native_extension/http_transport.c +51 -64
  10. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -13
  11. data/ext/datadog_profiling_native_extension/profiling.c +3 -1
  12. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +24 -8
  13. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +1 -3
  14. data/ext/datadog_profiling_native_extension/stack_recorder.c +63 -48
  15. data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -1
  16. data/ext/libdatadog_api/crashtracker.c +5 -0
  17. data/ext/libdatadog_api/crashtracker_report_exception.c +126 -0
  18. data/ext/libdatadog_api/extconf.rb +4 -21
  19. data/ext/libdatadog_extconf_helpers.rb +49 -11
  20. data/lib/datadog/ai_guard/configuration/settings.rb +3 -0
  21. data/lib/datadog/appsec/assets/blocked.html +2 -1
  22. data/lib/datadog/appsec/configuration/settings.rb +14 -0
  23. data/lib/datadog/appsec/context.rb +44 -9
  24. data/lib/datadog/appsec/contrib/active_record/patcher.rb +3 -0
  25. data/lib/datadog/appsec/contrib/devise/integration.rb +1 -1
  26. data/lib/datadog/appsec/contrib/excon/patcher.rb +2 -0
  27. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +55 -6
  28. data/lib/datadog/appsec/contrib/faraday/integration.rb +1 -1
  29. data/lib/datadog/appsec/contrib/faraday/patcher.rb +1 -1
  30. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +60 -7
  31. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +11 -6
  32. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +1 -1
  33. data/lib/datadog/appsec/contrib/graphql/integration.rb +1 -1
  34. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +6 -10
  35. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +4 -4
  36. data/lib/datadog/appsec/contrib/rack/integration.rb +1 -1
  37. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +26 -5
  38. data/lib/datadog/appsec/contrib/rack/response_body.rb +36 -0
  39. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +2 -2
  40. data/lib/datadog/appsec/contrib/rails/integration.rb +1 -1
  41. data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +2 -0
  42. data/lib/datadog/appsec/contrib/rest_client/patcher.rb +2 -0
  43. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +72 -7
  44. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +5 -3
  45. data/lib/datadog/appsec/counter_sampler.rb +25 -0
  46. data/lib/datadog/appsec/event.rb +1 -17
  47. data/lib/datadog/appsec/instrumentation/gateway/middleware.rb +2 -3
  48. data/lib/datadog/appsec/instrumentation/gateway.rb +2 -2
  49. data/lib/datadog/appsec/metrics/telemetry_exporter.rb +18 -0
  50. data/lib/datadog/appsec/monitor/gateway/watcher.rb +2 -2
  51. data/lib/datadog/appsec/security_engine/engine.rb +23 -2
  52. data/lib/datadog/appsec/utils/http/body.rb +38 -0
  53. data/lib/datadog/appsec/utils/http/media_range.rb +2 -1
  54. data/lib/datadog/appsec/utils/http/media_type.rb +28 -35
  55. data/lib/datadog/appsec/utils/http/url_encoded.rb +52 -0
  56. data/lib/datadog/core/configuration/components.rb +29 -4
  57. data/lib/datadog/core/configuration/option.rb +2 -1
  58. data/lib/datadog/core/configuration/options.rb +1 -1
  59. data/lib/datadog/core/configuration/settings.rb +27 -3
  60. data/lib/datadog/core/configuration/supported_configurations.rb +19 -0
  61. data/lib/datadog/core/configuration.rb +2 -2
  62. data/lib/datadog/core/crashtracking/component.rb +71 -19
  63. data/lib/datadog/core/environment/agent_info.rb +65 -1
  64. data/lib/datadog/core/logger.rb +1 -1
  65. data/lib/datadog/core/metrics/logging.rb +1 -1
  66. data/lib/datadog/core/process_discovery.rb +20 -19
  67. data/lib/datadog/core/rate_limiter.rb +2 -0
  68. data/lib/datadog/core/remote/component.rb +16 -5
  69. data/lib/datadog/core/remote/transport/config.rb +5 -11
  70. data/lib/datadog/core/runtime/metrics.rb +1 -2
  71. data/lib/datadog/core/telemetry/component.rb +0 -13
  72. data/lib/datadog/core/telemetry/transport/telemetry.rb +5 -6
  73. data/lib/datadog/core/transport/ext.rb +1 -0
  74. data/lib/datadog/core/transport/http/response.rb +4 -0
  75. data/lib/datadog/core/transport/parcel.rb +61 -9
  76. data/lib/datadog/core/utils/base64.rb +1 -1
  77. data/lib/datadog/core/utils/fnv.rb +26 -0
  78. data/lib/datadog/core/workers/interval_loop.rb +13 -6
  79. data/lib/datadog/core/workers/queue.rb +0 -4
  80. data/lib/datadog/core/workers/runtime_metrics.rb +9 -1
  81. data/lib/datadog/core.rb +6 -1
  82. data/lib/datadog/data_streams/processor.rb +35 -33
  83. data/lib/datadog/data_streams/transport/http/stats.rb +6 -0
  84. data/lib/datadog/data_streams/transport/http.rb +0 -4
  85. data/lib/datadog/data_streams/transport/stats.rb +5 -12
  86. data/lib/datadog/di/boot.rb +1 -0
  87. data/lib/datadog/di/component.rb +17 -5
  88. data/lib/datadog/di/configuration/settings.rb +9 -0
  89. data/lib/datadog/di/context.rb +6 -0
  90. data/lib/datadog/di/instrumenter.rb +183 -134
  91. data/lib/datadog/di/probe.rb +10 -1
  92. data/lib/datadog/di/probe_file_loader.rb +2 -2
  93. data/lib/datadog/di/probe_manager.rb +86 -64
  94. data/lib/datadog/di/probe_notification_builder.rb +46 -18
  95. data/lib/datadog/di/probe_notifier_worker.rb +65 -9
  96. data/lib/datadog/di/probe_repository.rb +198 -0
  97. data/lib/datadog/di/proc_responder.rb +4 -0
  98. data/lib/datadog/di/remote.rb +6 -7
  99. data/lib/datadog/di/serializer.rb +127 -9
  100. data/lib/datadog/di/transport/diagnostics.rb +5 -7
  101. data/lib/datadog/di/transport/http/diagnostics.rb +3 -1
  102. data/lib/datadog/di/transport/http/input.rb +1 -1
  103. data/lib/datadog/di/transport/http.rb +12 -3
  104. data/lib/datadog/di/transport/input.rb +51 -14
  105. data/lib/datadog/kit/tracing/method_tracer.rb +132 -0
  106. data/lib/datadog/open_feature/configuration.rb +2 -0
  107. data/lib/datadog/open_feature/transport.rb +8 -11
  108. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +13 -0
  109. data/lib/datadog/profiling/component.rb +20 -6
  110. data/lib/datadog/profiling/http_transport.rb +5 -6
  111. data/lib/datadog/profiling/profiler.rb +15 -8
  112. data/lib/datadog/tracing/contrib/dalli/integration.rb +4 -1
  113. data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +5 -1
  114. data/lib/datadog/tracing/contrib/ethon/ext.rb +1 -0
  115. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +5 -2
  116. data/lib/datadog/tracing/contrib/excon/ext.rb +1 -0
  117. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +5 -2
  118. data/lib/datadog/tracing/contrib/faraday/ext.rb +1 -0
  119. data/lib/datadog/tracing/contrib/grape/endpoint.rb +2 -2
  120. data/lib/datadog/tracing/contrib/grape/instrumentation.rb +13 -8
  121. data/lib/datadog/tracing/contrib/grape/patcher.rb +6 -1
  122. data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +5 -2
  123. data/lib/datadog/tracing/contrib/grpc/ext.rb +1 -0
  124. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +5 -2
  125. data/lib/datadog/tracing/contrib/http/ext.rb +1 -0
  126. data/lib/datadog/tracing/contrib/http/integration.rb +0 -2
  127. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +5 -2
  128. data/lib/datadog/tracing/contrib/httpclient/ext.rb +1 -0
  129. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +5 -2
  130. data/lib/datadog/tracing/contrib/httprb/ext.rb +1 -0
  131. data/lib/datadog/tracing/contrib/karafka/configuration/settings.rb +5 -1
  132. data/lib/datadog/tracing/contrib/karafka/ext.rb +1 -0
  133. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +6 -0
  134. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +2 -1
  135. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +6 -0
  136. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +2 -1
  137. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +10 -0
  138. data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +5 -1
  139. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +24 -0
  140. data/lib/datadog/tracing/contrib/que/configuration/settings.rb +5 -2
  141. data/lib/datadog/tracing/contrib/que/ext.rb +1 -0
  142. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +5 -1
  143. data/lib/datadog/tracing/contrib/rack/ext.rb +1 -0
  144. data/lib/datadog/tracing/contrib/rack/route_inference.rb +18 -6
  145. data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +5 -2
  146. data/lib/datadog/tracing/contrib/rails/ext.rb +1 -0
  147. data/lib/datadog/tracing/contrib/registerable.rb +11 -0
  148. data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +5 -2
  149. data/lib/datadog/tracing/contrib/rest_client/ext.rb +1 -0
  150. data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +5 -1
  151. data/lib/datadog/tracing/contrib/sidekiq/ext.rb +1 -0
  152. data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +5 -1
  153. data/lib/datadog/tracing/contrib/sinatra/ext.rb +1 -0
  154. data/lib/datadog/tracing/contrib/sneakers/integration.rb +15 -4
  155. data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +6 -0
  156. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +3 -1
  157. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +5 -1
  158. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +1 -0
  159. data/lib/datadog/tracing/metadata/ext.rb +4 -0
  160. data/lib/datadog/tracing/sync_writer.rb +0 -1
  161. data/lib/datadog/tracing/transport/io/client.rb +5 -8
  162. data/lib/datadog/tracing/transport/io/traces.rb +28 -34
  163. data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
  164. data/lib/datadog/tracing/transport/traces.rb +4 -10
  165. data/lib/datadog/tracing/writer.rb +0 -1
  166. data/lib/datadog/version.rb +1 -1
  167. metadata +14 -8
  168. data/lib/datadog/appsec/contrib/rails/ext.rb +0 -13
  169. data/lib/datadog/tracing/workers/trace_writer.rb +0 -204
@@ -112,51 +112,59 @@ module Datadog
112
112
  end
113
113
  rate_limiter = probe.rate_limiter
114
114
  settings = self.settings
115
+ instrumenter = self
115
116
 
116
117
  mod = Module.new do
117
118
  define_method(method_name) do |*args, **kwargs, &target_block| # steep:ignore NoMethod
118
119
  # Steep: Unsure why it cannot detect kwargs in this block. Workaround:
119
120
  # @type var kwargs: ::Hash[::Symbol, untyped]
120
- continue = true
121
- if condition = probe.condition
122
- begin
123
- # This context will be recreated later, unlike for line probes.
124
- context = Context.new(
125
- locals: serializer.combine_args(args, kwargs, self),
126
- target_self: self,
127
- probe: probe, settings: settings, serializer: serializer,
128
- caller_locations: caller_locations,
129
- )
130
- continue = condition.satisfied?(context)
131
- rescue => exc
132
- # Evaluation error exception can be raised for "expected"
133
- # errors, we probably need another setting to control whether
134
- # these exceptions are propagated.
135
- raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions &&
136
- !exc.is_a?(DI::Error::ExpressionEvaluationError)
137
-
138
- if context
139
- # We want to report evaluation errors for conditions
140
- # as probe snapshots. However, if we failed to create
141
- # the context, we won't be able to report anything as
142
- # the probe notifier builder requires a context.
143
- begin
144
- responder.probe_condition_evaluation_failed_callback(context, exc)
145
- rescue
121
+ di_start_time = Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID)
122
+
123
+ if continue = probe.enabled?
124
+ if condition = probe.condition
125
+ begin
126
+ # This context will be recreated later, unlike for line probes.
127
+ #
128
+ # We do not need the stack for condition evaluation, therefore
129
+ # stack is not passed to Context here.
130
+ context = Context.new(
131
+ locals: serializer.combine_args(args, kwargs, self),
132
+ target_self: self,
133
+ probe: probe, settings: settings, serializer: serializer,
134
+ )
135
+ continue = condition.satisfied?(context)
136
+ rescue => exc
137
+ # Evaluation error exception can be raised for "expected"
138
+ # errors, we probably need another setting to control whether
139
+ # these exceptions are propagated.
140
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions &&
141
+ !exc.is_a?(DI::Error::ExpressionEvaluationError)
142
+
143
+ if context
144
+ # We want to report evaluation errors for conditions
145
+ # as probe snapshots. However, if we failed to create
146
+ # the context, we won't be able to report anything as
147
+ # the probe notifier builder requires a context.
148
+ begin
149
+ responder.probe_condition_evaluation_failed_callback(context, exc)
150
+ rescue => nested_exc
151
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
152
+
153
+ instrumenter.logger.debug { "di: error in probe condition evaluation failed callback: #{nested_exc.class}: #{nested_exc}" }
154
+ instrumenter.telemetry&.report(nested_exc, description: "Error in probe condition evaluation failed callback")
155
+ end
156
+ else
157
+ _ = 42 # stop standard from wrecking this code
158
+
146
159
  raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
147
160
 
148
- # TODO log / report via telemetry?
161
+ instrumenter.logger.debug { "di: error evaluating condition without context (tracer bug?): #{exc.class}: #{exc}" }
162
+ instrumenter.telemetry&.report(exc, description: "Error evaluating condition without context")
163
+ # If execution gets here, there is probably a bug in the tracer.
149
164
  end
150
- else
151
- _ = 42 # stop standard from wrecking this code
152
165
 
153
- raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
154
-
155
- # TODO log / report via telemetry?
156
- # If execution gets here, there is probably a bug in the tracer.
166
+ continue = false
157
167
  end
158
-
159
- continue = false
160
168
  end
161
169
  end
162
170
 
@@ -173,6 +181,8 @@ module Datadog
173
181
  # customer, and DI is not allowed to invoke customer code.
174
182
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
175
183
 
184
+ di_duration = Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID) - di_start_time
185
+
176
186
  rv = nil
177
187
  begin
178
188
  # Under Ruby 2.6 we cannot just call super(*args, **kwargs)
@@ -195,7 +205,15 @@ module Datadog
195
205
  # the instrumentation callback runs.
196
206
  end
197
207
 
198
- duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
208
+ end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
209
+ duration = end_time - start_time
210
+
211
+ # Restart DI timer.
212
+ # The DI execution duration covers time spent in DI code before
213
+ # the customer method is invoked and time spent in DI code
214
+ # after the customer method finishes.
215
+ di_start_time = Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID)
216
+
199
217
  # The method itself is not part of the stack trace because
200
218
  # we are getting the stack trace from outside of the method.
201
219
  # Add the method in manually as the top frame.
@@ -222,6 +240,9 @@ module Datadog
222
240
  return_value: rv, duration: duration, exception: exc,)
223
241
 
224
242
  responder.probe_executed_callback(context)
243
+
244
+ instrumenter.send(:check_and_disable_if_exceeded, probe, responder, di_start_time, di_duration)
245
+
225
246
  if exc
226
247
  raise exc
227
248
  else
@@ -296,7 +317,6 @@ module Datadog
296
317
  end
297
318
 
298
319
  line_no = probe.line_no!
299
- rate_limiter = probe.rate_limiter
300
320
 
301
321
  # Memoize the value to ensure this method always uses the same
302
322
  # value for the setting.
@@ -367,103 +387,7 @@ module Datadog
367
387
  [:line]
368
388
  end
369
389
  tp = TracePoint.new(*types) do |tp|
370
- begin
371
- # If trace point is not targeted, we must verify that the invocation
372
- # is the file & line that we want, because untargeted trace points
373
- # are invoked for *each* line of Ruby executed.
374
- # TODO find out exactly when the path in trace point is relative.
375
- # Looks like this is the case when line trace point is not targeted?
376
- continue = iseq || tp.lineno == probe.line_no && (
377
- probe.file == tp.path || probe.file_matches?(tp.path)
378
- )
379
-
380
- # We set the trace point on :return to be able to instrument
381
- # 'end' lines. This also causes the trace point to be invoked on
382
- # non-'end' lines when a line raises an exception, since the
383
- # exception causes the method to stop executing and stack unwends.
384
- # We do not want two invocations of the trace point.
385
- # Therefore, if a trace point is invoked with a :line event,
386
- # mark it as such and ignore subsequent :return events.
387
- continue &&= if probe.executed_on_line?
388
- tp.event == :line
389
- else
390
- if tp.event == :line
391
- probe.executed_on_line!
392
- end
393
- true
394
- end
395
-
396
- if continue
397
- if condition = probe.condition
398
- begin
399
- context = Context.new(
400
- locals: Instrumenter.get_local_variables(tp),
401
- target_self: tp.self,
402
- probe: probe, settings: settings, serializer: serializer,
403
- path: tp.path,
404
- caller_locations: caller_locations,
405
- )
406
- continue = condition.satisfied?(context)
407
- rescue => exc
408
- # Evaluation error exception can be raised for "expected"
409
- # errors, we probably need another setting to control whether
410
- # these exceptions are propagated.
411
- raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions &&
412
- !exc.is_a?(DI::Error::ExpressionEvaluationError)
413
-
414
- continue = false
415
- if context
416
- # We want to report evaluation errors for conditions
417
- # as probe snapshots. However, if we failed to create
418
- # the context, we won't be able to report anything as
419
- # the probe notifier builder requires a context.
420
- begin
421
- responder.probe_condition_evaluation_failed_callback(context, condition, exc)
422
- rescue
423
- raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
424
-
425
- # TODO log / report via telemetry?
426
- end
427
- else
428
- _ = 42 # stop standard from wrecking this code
429
-
430
- raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
431
-
432
- # TODO log / report via telemetry?
433
- # If execution gets here, there is probably a bug in the tracer.
434
- end
435
- end
436
- end
437
- end
438
-
439
- continue &&= rate_limiter.nil? || rate_limiter.allow? # standard:disable Style/AndOr
440
-
441
- if continue
442
- # The context creation is relatively expensive and we don't
443
- # want to run it if the callback won't be executed due to the
444
- # rate limit.
445
- # Thus the copy-paste of the creation call here.
446
- context ||= Context.new(
447
- locals: Instrumenter.get_local_variables(tp),
448
- target_self: tp.self,
449
- probe: probe, settings: settings, serializer: serializer,
450
- path: tp.path,
451
- caller_locations: caller_locations,
452
- )
453
-
454
- responder.probe_executed_callback(context)
455
- end
456
- rescue => exc
457
- raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
458
- logger.debug { "di: unhandled exception in line trace point: #{exc.class}: #{exc}" }
459
- telemetry&.report(exc, description: "Unhandled exception in line trace point")
460
- # TODO test this path
461
- end
462
- rescue => exc
463
- raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
464
- logger.debug { "di: unhandled exception in line trace point: #{exc.class}: #{exc}" }
465
- telemetry&.report(exc, description: "Unhandled exception in line trace point")
466
- # TODO test this path
390
+ line_trace_point_callback(probe, iseq, responder, tp)
467
391
  end
468
392
 
469
393
  # Internal sanity check - untargeted trace points create a huge
@@ -551,6 +475,131 @@ module Datadog
551
475
 
552
476
  attr_reader :lock
553
477
 
478
+ def line_trace_point_callback(probe, iseq, responder, tp)
479
+ di_start_time = Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID)
480
+
481
+ # Check if probe is enabled before doing any processing
482
+ return unless probe.enabled?
483
+
484
+ # If trace point is not targeted, we must verify that the invocation
485
+ # is the file & line that we want, because untargeted trace points
486
+ # are invoked for *each* line of Ruby executed.
487
+ # TODO find out exactly when the path in trace point is relative.
488
+ # Looks like this is the case when line trace point is not targeted?
489
+ unless iseq
490
+ return unless tp.lineno == probe.line_no && ( # standard:disable Style/UnlessLogicalOperators
491
+ probe.file == tp.path || probe.file_matches?(tp.path)
492
+ )
493
+ end
494
+
495
+ # We set the trace point on :return to be able to instrument
496
+ # 'end' lines. This also causes the trace point to be invoked on
497
+ # non-'end' lines when a line raises an exception, since the
498
+ # exception causes the method to stop executing and stack unwends.
499
+ # We do not want two invocations of the trace point.
500
+ # Therefore, if a trace point is invoked with a :line event,
501
+ # mark it as such and ignore subsequent :return events.
502
+ if probe.executed_on_line?
503
+ return unless tp.event == :line
504
+ else
505
+ _ = 42 # stop standard from changing this code
506
+
507
+ if tp.event == :line
508
+ probe.executed_on_line!
509
+ end
510
+ end
511
+
512
+ if condition = probe.condition
513
+ begin
514
+ context = build_trace_point_context(probe, tp)
515
+ return unless condition.satisfied?(context)
516
+ rescue => exc
517
+ # Evaluation error exception can be raised for "expected"
518
+ # errors, we probably need another setting to control whether
519
+ # these exceptions are propagated.
520
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions &&
521
+ !exc.is_a?(DI::Error::ExpressionEvaluationError)
522
+
523
+ if context
524
+ # We want to report evaluation errors for conditions
525
+ # as probe snapshots. However, if we failed to create
526
+ # the context, we won't be able to report anything as
527
+ # the probe notifier builder requires a context.
528
+ begin
529
+ responder.probe_condition_evaluation_failed_callback(context, condition, exc)
530
+ rescue => nested_exc
531
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
532
+
533
+ logger.debug { "di: error in probe condition evaluation failed callback: #{nested_exc.class}: #{nested_exc}" }
534
+ telemetry&.report(nested_exc, description: "Error in probe condition evaluation failed callback")
535
+ end
536
+
537
+ return
538
+ else
539
+ _ = 42 # stop standard from wrecking this code
540
+
541
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
542
+
543
+ logger.debug { "di: error evaluating condition without context (tracer bug?): #{exc.class}: #{exc}" }
544
+ telemetry&.report(exc, description: "Error evaluating condition without context")
545
+ # If execution gets here, there is probably a bug in the tracer.
546
+ end
547
+ end
548
+ end
549
+
550
+ # In practice we should always have a rate limiter, but be safe
551
+ # and check that it is in fact set.
552
+ return if probe.rate_limiter && !probe.rate_limiter.allow?
553
+
554
+ # The context creation is relatively expensive and we don't
555
+ # want to run it if the callback won't be executed due to the
556
+ # rate limit.
557
+ # Thus the copy-paste of the creation call here.
558
+ context ||= build_trace_point_context(probe, tp)
559
+
560
+ responder.probe_executed_callback(context)
561
+
562
+ check_and_disable_if_exceeded(probe, responder, di_start_time)
563
+ rescue => exc
564
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
565
+ logger.debug { "di: unhandled exception in line trace point: #{exc.class}: #{exc}" }
566
+ telemetry&.report(exc, description: "Unhandled exception in line trace point")
567
+ # TODO test this path
568
+ end
569
+
570
+ def build_trace_point_context(probe, tp)
571
+ stack = caller_locations
572
+ # We have two helper methods being invoked from the trace point
573
+ # handler block, remove them from the stack.
574
+ #
575
+ # According to steep stack may be nil.
576
+ stack&.shift(2)
577
+ Context.new(
578
+ locals: Instrumenter.get_local_variables(tp),
579
+ target_self: tp.self,
580
+ probe: probe,
581
+ settings: settings,
582
+ serializer: serializer,
583
+ path: tp.path,
584
+ caller_locations: stack,
585
+ )
586
+ end
587
+
588
+ # Circuit breaker: disables the probe if total CPU time consumed by
589
+ # DI processing exceeds the configured threshold.
590
+ def check_and_disable_if_exceeded(probe, responder, di_start_time, accumulated_duration = 0.0)
591
+ return unless max_processing_time = settings.dynamic_instrumentation.internal.max_processing_time
592
+
593
+ di_duration = accumulated_duration + Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID) - di_start_time
594
+ if di_duration > max_processing_time
595
+ logger.debug { "di: disabling probe: consumed #{di_duration}: #{probe}" }
596
+ # We disable the probe here rather than remove it to
597
+ # avoid a dependency on ProbeManager from Instrumenter.
598
+ probe.disable!
599
+ responder.probe_disabled_callback(probe, di_duration)
600
+ end
601
+ end
602
+
554
603
  def raise_if_probe_in_loaded_features(probe)
555
604
  return unless probe.file
556
605
 
@@ -97,6 +97,7 @@ module Datadog
97
97
  end
98
98
 
99
99
  @emitting_notified = false
100
+ @enabled = true
100
101
  end
101
102
 
102
103
  attr_reader :id
@@ -219,13 +220,21 @@ module Datadog
219
220
  end
220
221
 
221
222
  def executed_on_line?
222
- !!@executed_on_line
223
+ !!(defined?(@executed_on_line) && @executed_on_line)
223
224
  end
224
225
 
225
226
  def executed_on_line!
226
227
  # TODO lock?
227
228
  @executed_on_line = true
228
229
  end
230
+
231
+ def enabled?
232
+ @enabled
233
+ end
234
+
235
+ def disable!
236
+ @enabled = false
237
+ end
229
238
  end
230
239
  end
231
240
  end
@@ -46,7 +46,7 @@ module Datadog
46
46
  component.telemetry&.report(exc, description: "Line probe is targeting a loaded file that is not in code tracker")
47
47
 
48
48
  payload = component.probe_notification_builder.build_errored(probe, exc)
49
- component.probe_notifier_worker.add_status(payload)
49
+ component.probe_notifier_worker.add_status(payload, probe: probe)
50
50
  rescue => exc
51
51
  raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
52
52
 
@@ -55,7 +55,7 @@ module Datadog
55
55
 
56
56
  # TODO test this path
57
57
  payload = component.probe_notification_builder.build_errored(probe, exc)
58
- component.probe_notifier_worker.add_status(payload)
58
+ component.probe_notifier_worker.add_status(payload, probe: probe)
59
59
  end
60
60
  end
61
61
  rescue => exc