Class: Datadog::DI::ProbeManager Private
- Inherits:
-
Object
- Object
- Datadog::DI::ProbeManager
- Defined in:
- lib/datadog/di/probe_manager.rb
Overview
This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.
Orchestrates probe lifecycle: installation, removal, and execution callbacks. Delegates probe storage to ProbeRepository.
Stores probes received from remote config (that we can parse, in other words, whose type/attributes we support), requests needed instrumentation for the probes via Instrumenter, and stores pending probes (those which haven’t yet been instrumented successfully due to their targets not existing) and failed probes (where we are certain the target will not ever be loaded, or otherwise become valid).
Instance Attribute Summary collapse
-
#definition_trace_point ⇒ Object
readonly
private
Class/module definition trace point (:end type).
- #instrumenter ⇒ Object readonly private
- #logger ⇒ Object readonly private
- #probe_notification_builder ⇒ Object readonly private
- #probe_notifier_worker ⇒ Object readonly private
- #probe_repository ⇒ Object readonly private
- #settings ⇒ Object readonly private
- #telemetry ⇒ Object readonly private
Instance Method Summary collapse
-
#add_probe(probe) ⇒ Boolean
private
Requests to install the specified probe.
-
#clear_hooks ⇒ void
private
Removes all installed probe instrumentation and clears the probe repository.
-
#close ⇒ void
private
Shuts down the probe manager and releases all resources.
-
#initialize(settings, instrumenter, probe_notification_builder, probe_notifier_worker, logger, probe_repository, telemetry: nil) ⇒ ProbeManager
constructor
private
A new instance of ProbeManager.
-
#install_pending_line_probes(path) ⇒ Object
private
Installs pending line probes, if any, for the file of the specified absolute path.
-
#probe_condition_evaluation_failed_callback(context, expr, exc) ⇒ Object
private
Callback invoked when a probe’s condition expression fails to evaluate.
-
#probe_disabled_callback(probe, duration) ⇒ Object
private
Callback invoked when a probe is disabled, for example due to exceeding the CPU consumption limit in DI processing.
-
#probe_executed_callback(context) ⇒ Object
private
Entry point invoked from the instrumentation when the specfied probe is invoked (that is, either its target method is invoked, or execution reached its target file/line).
-
#remove_probe(probe_id) ⇒ Object
private
Removes probe with specified id.
Constructor Details
#initialize(settings, instrumenter, probe_notification_builder, probe_notifier_worker, logger, probe_repository, telemetry: nil) ⇒ ProbeManager
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns a new instance of ProbeManager.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/datadog/di/probe_manager.rb', line 19 def initialize(settings, instrumenter, probe_notification_builder, probe_notifier_worker, logger, probe_repository, telemetry: nil) @settings = settings @instrumenter = instrumenter @probe_notification_builder = probe_notification_builder @probe_notifier_worker = probe_notifier_worker @logger = logger @telemetry = telemetry @probe_repository = probe_repository @definition_trace_point = TracePoint.trace(:end) do |tp| install_pending_method_probes(tp.self) rescue => exc raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions logger.debug { "di: unhandled exception in definition trace point: #{exc.class}: #{exc}" } telemetry&.report(exc, description: "Unhandled exception in definition trace point") # TODO test this path end end |
Instance Attribute Details
#definition_trace_point ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Class/module definition trace point (:end type). Used to install hooks when the target classes/modules aren’t yet defined when the hook request is received.
284 285 286 |
# File 'lib/datadog/di/probe_manager.rb', line 284 def definition_trace_point @definition_trace_point end |
#instrumenter ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
71 72 73 |
# File 'lib/datadog/di/probe_manager.rb', line 71 def instrumenter @instrumenter end |
#logger ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
39 40 41 |
# File 'lib/datadog/di/probe_manager.rb', line 39 def logger @logger end |
#probe_notification_builder ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
72 73 74 |
# File 'lib/datadog/di/probe_manager.rb', line 72 def probe_notification_builder @probe_notification_builder end |
#probe_notifier_worker ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
73 74 75 |
# File 'lib/datadog/di/probe_manager.rb', line 73 def probe_notifier_worker @probe_notifier_worker end |
#probe_repository ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
41 42 43 |
# File 'lib/datadog/di/probe_manager.rb', line 41 def probe_repository @probe_repository end |
#settings ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
70 71 72 |
# File 'lib/datadog/di/probe_manager.rb', line 70 def settings @settings end |
#telemetry ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
40 41 42 |
# File 'lib/datadog/di/probe_manager.rb', line 40 def telemetry @telemetry end |
Instance Method Details
#add_probe(probe) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Requests to install the specified probe.
If the target of the probe does not exist, assume the relevant code is not loaded yet (rather than that it will never be loaded), and store the probe in a pending probe list. When classes are defined, or files loaded, the probe will be checked against the newly defined classes/loaded files, and will be installed if it matches.
On successful installation, sends INSTALLED status to the backend. On failure, sends ERROR status to the backend before re-raising.
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/datadog/di/probe_manager.rb', line 92 def add_probe(probe) probe_repository.synchronize do if probe_repository.find_installed(probe.id) # Either this probe was already installed, or another probe was # installed with the same id (previous version perhaps?). # Since our state tracking is keyed by probe id, we cannot # install this probe since we won't have a way of removing the # instrumentation for the probe with the same id which is already # installed. # # The exception raised here will be caught below and logged and # reported to telemetry. raise Error::AlreadyInstrumented, "Probe with id #{probe.id} is already in installed probes" end # Probe failed to install previously, do not try to install it again. if msg = probe_repository.find_failed(probe.id) # TODO test this path raise Error::ProbePreviouslyFailed, msg end begin instrumenter.hook(probe, self) probe_repository.add_installed(probe) payload = probe_notification_builder.build_installed(probe) probe_notifier_worker.add_status(payload, probe: probe) # The probe would only be in the pending probes list if it was # previously attempted to be installed and the target was not loaded. # Always remove from pending list here because it makes the # API smaller and shouldn't cause any actual problems. probe_repository.remove_pending(probe.id) logger.trace { "di: installed #{probe.type} probe at #{probe.location} (#{probe.id})" } true rescue Error::DITargetNotDefined probe_repository.add_pending(probe) logger.trace { "di: could not install #{probe.type} probe at #{probe.location} (#{probe.id}) because its target is not defined, adding it to pending list" } false end end rescue => exc # In "propagate all exceptions" mode we will try to instrument again. raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions logger.debug { "di: error processing probe configuration: #{exc.class}: #{exc}" } telemetry&.report(exc, description: "Error processing probe configuration") payload = probe_notification_builder.build_errored(probe, exc) probe_notifier_worker.add_status(payload, probe: probe) probe_repository.add_failed(probe.id, "#{exc.class}: #{exc}") raise end |
#clear_hooks ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Removes all installed probe instrumentation and clears the probe repository.
Iterates through all installed probes, unhooks their instrumentation, and clears all probe collections (installed, pending, failed). Called during component shutdown to clean up resources.
64 65 66 67 68 |
# File 'lib/datadog/di/probe_manager.rb', line 64 def clear_hooks probe_repository.clear_all do |probe| instrumenter.unhook(probe) end end |
#close ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Shuts down the probe manager and releases all resources.
Disables the class definition trace point and removes all installed probe instrumentation. Called during component teardown.
TODO test that close is called during component teardown and the trace point is cleared
52 53 54 55 |
# File 'lib/datadog/di/probe_manager.rb', line 52 def close definition_trace_point.disable clear_hooks end |
#install_pending_line_probes(path) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Installs pending line probes, if any, for the file of the specified absolute path.
This method is meant to be called from the script_compiled trace point, which is invoked for each required or loaded file (and also for eval’d code, but those invocations are filtered out).
216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/datadog/di/probe_manager.rb', line 216 def install_pending_line_probes(path) if path.nil? raise ArgumentError, "path must not be nil" end probe_repository.synchronize do probe_repository.pending_probes.values.each do |probe| if probe.line? if probe.file_matches?(path) add_probe(probe) end end end end end |
#probe_condition_evaluation_failed_callback(context, expr, exc) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Callback invoked when a probe’s condition expression fails to evaluate.
This can happen when the expression references undefined variables, has type mismatches, or encounters runtime errors during evaluation. Rate-limited to 1 notification per second per probe to avoid flooding the backend when conditions fail repeatedly.
261 262 263 264 265 266 267 |
# File 'lib/datadog/di/probe_manager.rb', line 261 def probe_condition_evaluation_failed_callback(context, expr, exc) probe = context.probe if probe.condition_evaluation_failed_rate_limiter&.allow? payload = probe_notification_builder.build_condition_evaluation_failed(context, expr, exc) probe_notifier_worker.add_snapshot(payload) end end |
#probe_disabled_callback(probe, duration) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Callback invoked when a probe is disabled, for example due to exceeding the CPU consumption limit in DI processing.
Sends ERROR status notification to the backend.
276 277 278 279 |
# File 'lib/datadog/di/probe_manager.rb', line 276 def probe_disabled_callback(probe, duration) payload = probe_notification_builder.build_disabled(probe, duration) probe_notifier_worker.add_status(payload, probe: probe) end |
#probe_executed_callback(context) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Entry point invoked from the instrumentation when the specfied probe is invoked (that is, either its target method is invoked, or execution reached its target file/line).
This method is responsible for queueing probe status to be sent to the backend (once per the probe’s lifetime) and a snapshot corresponding to the current invocation.
238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/datadog/di/probe_manager.rb', line 238 def probe_executed_callback(context) probe = context.probe logger.trace { "di: executed #{probe.type} probe at #{probe.location} (#{probe.id})" } unless probe.emitting_notified? payload = probe_notification_builder.build_emitting(probe) probe_notifier_worker.add_status(payload, probe: probe) probe.emitting_notified = true end payload = probe_notification_builder.build_executed(context) probe_notifier_worker.add_snapshot(payload) end |
#remove_probe(probe_id) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Removes probe with specified id. The probe could be pending or installed. Does nothing if there is no probe with the specified id.
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/datadog/di/probe_manager.rb', line 149 def remove_probe(probe_id) probe_repository.remove_pending(probe_id) # Do not delete the probe from the registry here in case # deinstrumentation fails - though I don't know why deinstrumentation # would fail and how we could recover if it does. # I plan on tracking the number of outstanding (instrumented) probes # in the future, and if deinstrumentation fails I would want to # keep that probe as "installed" for the count, so that we can # investigate the situation. if probe = probe_repository.find_installed(probe_id) begin instrumenter.unhook(probe) probe_repository.remove_installed(probe_id) rescue => exc raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions # Silence all exceptions? # TODO should we propagate here and rescue upstream? logger.debug { "di: error removing #{probe.type} probe at #{probe.location} (#{probe.id}): #{exc.class}: #{exc}" } telemetry&.report(exc, description: "Error removing probe") end end end |