Class: NewRelic::Agent::Instrumentation::ActiveRecordSubscriber

Inherits:
NotificationsSubscriber show all
Defined in:
lib/new_relic/agent/instrumentation/active_record_subscriber.rb

Constant Summary collapse

CACHED_QUERY_NAME =
'CACHE'.freeze

Instance Method Summary collapse

Methods inherited from NotificationsSubscriber

#add_segment_params, #define_exception_method, find_all_subscribers, #finish_segment, #log_notification_error, #metric_name, #pop_segment, #push_segment, #segment_stack, #state, subscribe, subscribed?

Constructor Details

#initializeActiveRecordSubscriber

Returns a new instance of ActiveRecordSubscriber.



17
18
19
20
21
22
23
# File 'lib/new_relic/agent/instrumentation/active_record_subscriber.rb', line 17

def initialize
  define_cachedp_method
  # We cache this in an instance variable to avoid re-calling method
  # on each query.
  @explainer = method(:get_explain_plan)
  super
end

Instance Method Details

#active_record_config(payload) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/new_relic/agent/instrumentation/active_record_subscriber.rb', line 87

def active_record_config(payload)
  # handle if the notification payload provides the AR connection
  # available in Rails 6+ & our ActiveRecordNotifications#log extension
  if payload[:connection]
    connection_config = payload[:connection].instance_variable_get(:@config)
    return connection_config if connection_config
  end

  return unless connection_id = payload[:connection_id]

  ::ActiveRecord::Base.connection_handler.connection_pool_list.each do |handler|
    connection = handler.connections.detect { |conn| conn.object_id == connection_id }
    return connection.instance_variable_get(:@config) if connection

    # when using makara, handler.connections will be empty, so use the
    # spec config instead.
    # https://github.com/newrelic/newrelic-ruby-agent/issues/507
    # thank you @lucasklaassen
    return handler.spec.config if use_spec_config?(handler)
  end

  nil
end

#define_cachedp_methodObject

The cached? method is dynamically defined based on ActiveRecord version. This file can and often is required before ActiveRecord is loaded. For that reason we define the cache? method in initialize. The behavior difference is that AR 5.1 includes a key in the payload to check, where older versions set the :name to CACHE.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/new_relic/agent/instrumentation/active_record_subscriber.rb', line 31

def define_cachedp_method
  # we don't expect this to be called more than once, but we're being
  # defensive.
  return if defined?(cached?)

  if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::STRING >= '5.1.0'
    def cached?(payload)
      payload.fetch(:cached, false)
    end
  else
    def cached?(payload)
      payload[:name] == CACHED_QUERY_NAME
    end
  end
end

#finish(name, id, payload) ⇒ Object

THREAD_LOCAL_ACCESS



58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/new_relic/agent/instrumentation/active_record_subscriber.rb', line 58

def finish(name, id, payload) # THREAD_LOCAL_ACCESS
  return if cached?(payload)
  return unless state.is_execution_traced?

  if segment = pop_segment(id)
    if exception = exception_object(payload)
      segment.notice_error(exception)
    end
    segment.finish
  end
rescue => e
  log_notification_error(e, name, 'finish')
end

#get_explain_plan(statement) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/new_relic/agent/instrumentation/active_record_subscriber.rb', line 72

def get_explain_plan(statement)
  connection = NewRelic::Agent::Database.get_connection(statement.config) do
    ::ActiveRecord::Base.send("#{statement.config[:adapter]}_connection",
      statement.config)
  end
  # the following line needs else branch coverage
  if connection && connection.respond_to?(:exec_query) # rubocop:disable Style/SafeNavigation
    return connection.exec_query("EXPLAIN #{statement.sql}",
      "Explain #{statement.name}",
      statement.binds)
  end
rescue => e
  NewRelic::Agent.logger.debug("Couldn't fetch the explain plan for #{statement} due to #{e}")
end

#start(name, id, payload) ⇒ Object

THREAD_LOCAL_ACCESS



47
48
49
50
51
52
53
54
55
56
# File 'lib/new_relic/agent/instrumentation/active_record_subscriber.rb', line 47

def start(name, id, payload) # THREAD_LOCAL_ACCESS
  return if cached?(payload)
  return unless NewRelic::Agent.tl_is_execution_traced?

  config = active_record_config(payload)
  segment = start_segment(config, payload)
  push_segment(id, segment)
rescue => e
  log_notification_error(e, name, 'start')
end

#start_segment(config, payload) ⇒ Object



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/new_relic/agent/instrumentation/active_record_subscriber.rb', line 118

def start_segment(config, payload)
  sql = Helper.correctly_encoded(payload[:sql])
  product, operation, collection = ActiveRecordHelper.product_operation_collection_for(
    payload[:name],
    sql,
    config && config[:adapter]
  )

  host = nil
  port_path_or_id = nil
  database = nil

  if ActiveRecordHelper::InstanceIdentification.supported_adapter?(config)
    host = ActiveRecordHelper::InstanceIdentification.host(config)
    port_path_or_id = ActiveRecordHelper::InstanceIdentification.port_path_or_id(config)
    database = config && config[:database]
  end

  segment = Tracer.start_datastore_segment(product: product,
    operation: operation,
    collection: collection,
    host: host,
    port_path_or_id: port_path_or_id,
    database_name: database)

  segment._notice_sql(sql, config, @explainer, payload[:binds], payload[:name])
  segment
end

#use_spec_config?(handler) ⇒ Boolean

Returns:

  • (Boolean)


111
112
113
114
115
116
# File 'lib/new_relic/agent/instrumentation/active_record_subscriber.rb', line 111

def use_spec_config?(handler)
  handler.respond_to?(:spec) &&
    handler.spec &&
    handler.spec.config &&
    handler.spec.config[:adapter].end_with?('makara')
end