Class: Datadog::DI::ProbeManager Private

Inherits:
Object
  • Object
show all
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.

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

Instance Method Summary collapse

Constructor Details

#initialize(settings, instrumenter, probe_notification_builder, probe_notifier_worker, logger, 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.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/datadog/di/probe_manager.rb', line 18

def initialize(settings, instrumenter, probe_notification_builder,
  probe_notifier_worker, logger, telemetry: nil)
  @settings = settings
  @instrumenter = instrumenter
  @probe_notification_builder = probe_notification_builder
  @probe_notifier_worker = probe_notifier_worker
  @logger = logger
  @telemetry = telemetry
  @installed_probes = {}
  @pending_probes = {}
  @failed_probes = {}
  @lock = Monitor.new

  @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.warn("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_pointObject (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.



241
242
243
# File 'lib/datadog/di/probe_manager.rb', line 241

def definition_trace_point
  @definition_trace_point
end

#instrumenterObject (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.



62
63
64
# File 'lib/datadog/di/probe_manager.rb', line 62

def instrumenter
  @instrumenter
end

#loggerObject (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 logger
  @logger
end

#probe_notification_builderObject (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.



63
64
65
# File 'lib/datadog/di/probe_manager.rb', line 63

def probe_notification_builder
  @probe_notification_builder
end

#probe_notifier_workerObject (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.



64
65
66
# File 'lib/datadog/di/probe_manager.rb', line 64

def probe_notifier_worker
  @probe_notifier_worker
end

#settingsObject (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.



61
62
63
# File 'lib/datadog/di/probe_manager.rb', line 61

def settings
  @settings
end

#telemetryObject (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.



42
43
44
# File 'lib/datadog/di/probe_manager.rb', line 42

def telemetry
  @telemetry
end

Instance Method Details

#add_probe(probe) ⇒ 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.

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.



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
# File 'lib/datadog/di/probe_manager.rb', line 95

def add_probe(probe)
  @lock.synchronize do
    # Probe failed to install previously, do not try to install it again.
    if msg = @failed_probes[probe.id]
      # TODO test this path
      raise Error::ProbePreviouslyFailed, msg
    end

    begin
      instrumenter.hook(probe, &method(:probe_executed_callback))

      @installed_probes[probe.id] = probe
      payload = probe_notification_builder.build_installed(probe)
      probe_notifier_worker.add_status(payload)
      # 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.
      @pending_probes.delete(probe.id)
      true
    rescue Error::DITargetNotDefined
      @pending_probes[probe.id] = probe
      false
    end
  rescue => exc
    # In "propagate all exceptions" mode we will try to instrument again.
    raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions

    logger.warn("Error processing probe configuration: #{exc.class}: #{exc}")
    telemetry&.report(exc, description: "Error processing probe configuration")
    # TODO report probe as failed to agent since we won't attempt to
    # install it again.

    # TODO add top stack frame to message
    @failed_probes[probe.id] = "#{exc.class}: #{exc}"

    raise
  end
end

#clear_hooksObject

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.



51
52
53
54
55
56
57
58
59
# File 'lib/datadog/di/probe_manager.rb', line 51

def clear_hooks
  @lock.synchronize do
    @pending_probes.clear
    @installed_probes.each do |probe_id, probe|
      instrumenter.unhook(probe)
    end
    @installed_probes.clear
  end
end

#closeObject

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.

TODO test that close is called during component teardown and the trace point is cleared



46
47
48
49
# File 'lib/datadog/di/probe_manager.rb', line 46

def close
  definition_trace_point.disable
  clear_hooks
end

#failed_probesObject

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.

Probes that failed to instrument for reasons other than the target is not yet loaded are added to this collection, so that we do not try to instrument them every time remote configuration is processed.



81
82
83
84
85
# File 'lib/datadog/di/probe_manager.rb', line 81

def failed_probes
  @lock.synchronize do
    @failed_probes
  end
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).



208
209
210
211
212
213
214
215
216
217
218
# File 'lib/datadog/di/probe_manager.rb', line 208

def install_pending_line_probes(path)
  @lock.synchronize do
    @pending_probes.values.each do |probe|
      if probe.line?
        if probe.file_matches?(path)
          add_probe(probe)
        end
      end
    end
  end
end

#installed_probesObject

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.



66
67
68
69
70
# File 'lib/datadog/di/probe_manager.rb', line 66

def installed_probes
  @lock.synchronize do
    @installed_probes
  end
end

#pending_probesObject

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
75
76
# File 'lib/datadog/di/probe_manager.rb', line 72

def pending_probes
  @lock.synchronize do
    @pending_probes
  end
end

#probe_executed_callback(probe:, **opts) ⇒ 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.



227
228
229
230
231
232
233
234
235
236
# File 'lib/datadog/di/probe_manager.rb', line 227

def probe_executed_callback(probe:, **opts)
  unless probe.emitting_notified?
    payload = probe_notification_builder.build_emitting(probe)
    probe_notifier_worker.add_status(payload)
    probe.emitting_notified = true
  end

  payload = probe_notification_builder.build_executed(probe, **opts)
  probe_notifier_worker.add_snapshot(payload)
end

#remove_other_probes(probe_ids) ⇒ 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 probes with ids other than in the specified list.

This method is meant to be invoked from remote config processor. Remote config contains the list of currently defined probes; any probes not in that list have been removed by user and should be de-instrumented from the application.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/datadog/di/probe_manager.rb', line 141

def remove_other_probes(probe_ids)
  @lock.synchronize do
    @pending_probes.values.each do |probe|
      unless probe_ids.include?(probe.id)
        @pending_probes.delete(probe.id)
      end
    end
    @installed_probes.values.each do |probe|
      unless probe_ids.include?(probe.id)
        begin
          instrumenter.unhook(probe)
          # Only remove the probe from installed list if it was
          # successfully de-instrumented. Active probes do incur overhead
          # for the running application, and if the error is ephemeral
          # we want to try removing the probe again at the next opportunity.
          #
          # TODO give up after some time?
          @installed_probes.delete(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.warn("Error removing probe #{probe.id}: #{exc.class}: #{exc}")
          telemetry&.report(exc, description: "Error removing probe #{probe.id}")
        end
      end
    end
  end
end