class StackifyRubyAPM::Normalizers::ActiveRecord::SqlNormalizer

@api private

Public Class Methods

new(*args) click to toggle source
# File lib/stackify_apm/normalizers/active_record.rb, line 15
def initialize(*args)
  super(*args)
end

Public Instance Methods

normalize(_transaction, _name, payload) click to toggle source
# File lib/stackify_apm/normalizers/active_record.rb, line 19
def normalize(_transaction, _name, payload)
  return :skip if %w[SCHEMA CACHE].include?(payload[:name])

  statement = query_variables(payload)
  check_prepared_stmt(statement, payload)
  name = payload[:sql] || payload[:name] || 'Default'
  context = Span::Context.new(statement)

  type = format('db.%s.sql', lookup_adapter(payload) || 'unknown').freeze
  [name, type, context]
end

Private Instance Methods

check_prepared_stmt(statement, payload) click to toggle source
# File lib/stackify_apm/normalizers/active_record.rb, line 110
def check_prepared_stmt(statement, payload)
  if StackifyRubyAPM.agent.config.prefix_enabled
    case get_profiler(lookup_adapter(payload))
    when 'generic', 'mysql', 'sqlite', 'oracle', 'db2'
      check_prepared_stmt_by_placeholder(payload[:sql].include?('?'), statement, payload)
    when 'postgresql'
      check_prepared_stmt_by_placeholder(!!payload[:sql].match(/\$\d/), statement, payload)
    end
  end
end
lookup_adapter(payload) click to toggle source

Ideally the application doesn't connect to the database during boot, but sometimes it does. In case it did, we want to empty out the connection pools so that a non-database-using process (e.g. a master process in a forking server model) doesn't retain a needless connection. If it was needed, the incremental cost of reestablishing this connection is trivial: the rest of the pool would need to be populated anyway.

Reference: github.com/rails/rails/blob/main/activerecord/lib/active_record/railtie.rb#L253

Miko: Considering we are getting the connection method, it is retrieving connection from the connection pool Connection Method: lib/active_record/connection_handling.rb#L264 Retrieve Connection: lib/active_record/connection_handling.rb#L309 Handler Retrieve Connection: lib/active_record/connection_adapters/abstract/connection_pool.rb#L1111

# File lib/stackify_apm/normalizers/active_record.rb, line 59
def lookup_adapter(payload)
  connection = nil
  if (payload.key?(:connection))
    connection = payload[:connection]
  elsif ::ActiveRecord::Base.connection_pool.instance_variable_defined?(:@reserved_connections) and payload.key?(:connection_id)
    connection_id = payload[:connection_id] # Connection ID here is the object_id of the connection object
    connections = ::ActiveRecord::Base.connection_pool.instance_variable_get(:@reserved_connections) # Lets check the reserved connections

    if (
        (connections.class != nil and connections.respond_to?(:class) and
          (connections.class.to_s == 'ThreadSafe::Cache' or connections.class.to_s == 'Hash')
        ) and connections.size()
    )
      connections.each_value do |val|
        if val.object_id == connection_id
          connection = val
          break
        end
      end
    end
  end

  if (connection.nil?)
    return 'generic'
  end

  if (connection.respond_to?(:adapter_name) && connection.adapter_name.nil?)
    return 'generic'
  end

  connection.adapter_name.downcase
rescue StandardError => error
  debug '[SqlNormalizer] lookup_adapter err: ' + error.inspect.to_s
  nil
end
lookup_adapter_config() click to toggle source
# File lib/stackify_apm/normalizers/active_record.rb, line 95
def lookup_adapter_config
  config = nil
  if Gem::Version.new(Rails::VERSION::STRING) >= Gem::Version.new('6.1')
    config = ::ActiveRecord::Base.connection_db_config
  else
    config = ::ActiveRecord::Base.connection_config
  end
  if (config != nil && config.respond_to(:to_h))
    config.to_h
  end
rescue StandardError => error
  debug '[SqlNormalizer] lookup_adapter_config err: ' + error.inspect.to_s
  nil
end
query_variables(payload) click to toggle source
# File lib/stackify_apm/normalizers/active_record.rb, line 33
def query_variables(payload)
  adapter_config = lookup_adapter_config
  props = get_common_db_properties
  props[:PROVIDER] = get_profiler(lookup_adapter(payload))
  props[:SQL] = payload[:sql]
  if adapter_config
    props[:URL] = "#{adapter_config[:host]}:#{adapter_config[:port]}"
  end
  props
end