Module: Datadog::CI::Contrib::Instrumentation

Defined in:
lib/datadog/ci/contrib/instrumentation.rb

Defined Under Namespace

Classes: InvalidIntegrationError

Class Method Summary collapse

Class Method Details

.auto_configure_datadogObject



156
157
158
159
160
161
162
163
164
# File 'lib/datadog/ci/contrib/instrumentation.rb', line 156

def self.auto_configure_datadog
  configure_once.run do
    Datadog.logger.debug("Applying Datadog configuration in CI mode...")
    Datadog.configure do |c|
      c.ci.enabled = true
      c.tracing.enabled = true
    end
  end
end

.auto_instrumentObject

Auto instrumentation of all integrations.

Registers a :script_compiled tracepoint to watch for new Ruby files being loaded. On every file load it checks if any of the integrations are patchable now. Only the integrations that are available in the environment are checked.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/datadog/ci/contrib/instrumentation.rb', line 31

def self.auto_instrument
  Datadog.logger.debug("Auto instrumenting all integrations...")
  @auto_instrumented = true

  auto_instrumented_integrations = fetch_auto_instrumented_integrations
  if auto_instrumented_integrations.empty?
    Datadog.logger.warn(
      "Auto instrumentation was requested, but no available integrations were found. " \
      "Tests will be run without Datadog instrumentation."
    )
    return
  end

  # note that `Kernel.require` might be called from a different thread, so
  # there is a possibility of concurrent execution of this tracepoint
  mutex = Mutex.new
  script_compiled_tracepoint = TracePoint.new(:script_compiled) do |tp|
    all_patched = true

    mutex.synchronize do
      auto_instrumented_integrations.each do |integration|
        next if integration.patched?

        all_patched = false
        next unless integration.loaded?

        auto_configure_datadog

        Datadog.logger.debug("#{integration.class} is loaded")
        patch_integration(integration)
      end

      if all_patched
        Datadog.logger.debug("All expected integrations are patched, disabling the script_compiled tracepoint")

        tp.disable
      end
    end
  end
  script_compiled_tracepoint.enable
end

.auto_instrumented?Boolean

Returns:

  • (Boolean)


22
23
24
# File 'lib/datadog/ci/contrib/instrumentation.rb', line 22

def self.auto_instrumented?
  @auto_instrumented
end

.configure_onceObject

This is not thread safe, it is synchronized by the caller in the tracepoint



167
168
169
# File 'lib/datadog/ci/contrib/instrumentation.rb', line 167

def self.configure_once
  @configure_once ||= Datadog::Core::Utils::OnlyOnce.new
end

.fetch_auto_instrumented_integrationsObject



143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/datadog/ci/contrib/instrumentation.rb', line 143

def self.fetch_auto_instrumented_integrations
  @registry.filter_map do |name, integration|
    # ignore integrations that are not in the Gemfile or have incompatible versions
    next unless integration.compatible?

    # late instrumented integrations will be patched when the test session starts
    next if integration.late_instrument?

    Datadog.logger.debug("#{name} should be auto instrumented")
    integration
  end
end

.fetch_integration(name) ⇒ Object



106
107
108
109
# File 'lib/datadog/ci/contrib/instrumentation.rb', line 106

def self.fetch_integration(name)
  @registry[name] ||
    raise(InvalidIntegrationError, "'#{name}' is not a valid integration.")
end

.instrument(integration_name, options = {}, &block) ⇒ Object

Manual instrumentation of a specific integration.

This method is called when user has ‘c.ci.instrument :integration_name` in their code.



76
77
78
79
80
81
82
83
84
# File 'lib/datadog/ci/contrib/instrumentation.rb', line 76

def self.instrument(integration_name, options = {}, &block)
  integration = fetch_integration(integration_name)
  # when manually instrumented, it might be configured via code
  integration.configure(options, &block)

  return unless integration.enabled

  patch_integration(integration, with_dependencies: true)
end

.instrument_on_session_startObject

This method instruments all additional test libraries (ex: selenium-webdriver) that need to be instrumented later in the test suite run.

It is intended to be called when test session starts to add additional capabilities to test visibility.

This method does not automatically instrument test frameworks (ex: RSpec, Cucumber, etc), it requires test framework to be already instrumented.



93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/datadog/ci/contrib/instrumentation.rb', line 93

def self.instrument_on_session_start
  Datadog.logger.debug("Instrumenting all late instrumented integrations...")

  @registry.each do |name, integration|
    next unless integration.late_instrument?
    next unless integration.enabled

    Datadog.logger.debug "#{name} is allowed to be late instrumented"

    patch_integration(integration)
  end
end

.integration_name(subclass) ⇒ Object

take the parent module name and downcase it for example for Datadog::CI::Contrib::RSpec::Integration it will be :rspec



113
114
115
116
117
# File 'lib/datadog/ci/contrib/instrumentation.rb', line 113

def self.integration_name(subclass)
  result = subclass.name&.split("::")&.[](-2)&.downcase&.to_sym
  raise "Integration name could not be derived for #{subclass}" if result.nil?
  result
end

.patch_integration(integration, with_dependencies: false) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/datadog/ci/contrib/instrumentation.rb', line 119

def self.patch_integration(integration, with_dependencies: false)
  patch_results = integration.patch

  if patch_results[:ok]
    Datadog.logger.debug("#{integration.class} is patched")

    return unless with_dependencies

    # try to patch dependant integrations (for example knapsack that depends on rspec)
    dependants = integration.dependants
      .map { |name| fetch_integration(name) }
      .filter { |integration| integration.patchable? }

    Datadog.logger.debug("Found dependent integrations for #{integration.class}: #{dependants}")

    dependants.each do |dependent_integration|
      patch_integration(dependent_integration, with_dependencies: true)
    end

  else
    Datadog.logger.debug("Attention: #{integration.class} is not patched (#{patch_results})")
  end
end

.register_integration(integration_class) ⇒ Object



18
19
20
# File 'lib/datadog/ci/contrib/instrumentation.rb', line 18

def self.register_integration(integration_class)
  @registry[integration_name(integration_class)] = integration_class.new
end

.registryObject



14
15
16
# File 'lib/datadog/ci/contrib/instrumentation.rb', line 14

def self.registry
  @registry
end