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.

[View source]

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

[View source]

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.

[View source]

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

[View source]

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

[View source]

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

[View source]

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

[View source]

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
# 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

  begin
    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
  rescue
    NewRelic::Agent.logger.debug("Failed to retrieve ActiveRecord host, port, and database details for adapter: #{config && config[:adapter]}")
  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)
[View source]

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