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



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/new_relic/agent/instrumentation/active_record_subscriber.rb', line 76

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
# File 'lib/new_relic/agent/instrumentation/active_record_subscriber.rb', line 72

def get_explain_plan(statement)
  NewRelic::Agent::Database.explain_this(statement)
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



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
# File 'lib/new_relic/agent/instrumentation/active_record_subscriber.rb', line 107

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)


100
101
102
103
104
105
# File 'lib/new_relic/agent/instrumentation/active_record_subscriber.rb', line 100

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