class AwesomeExplain::Subscribers::ActiveRecordSubscriber

Public Instance Methods

controller_data() click to toggle source
# File lib/awesome_explain/subscribers/active_record_subscriber.rb, line 81
def controller_data
  Thread.current['ae_controller_data']
end
db_explain_enabled?(sql) click to toggle source
# File lib/awesome_explain/subscribers/active_record_subscriber.rb, line 145
def db_explain_enabled?(sql)
  # return true if Thread.current['ae_analyze']
  # return false if Rails.const_defined?('Console')
  return false if ddm_query?(sql)
  true
end
db_logging_enbled?() click to toggle source
# File lib/awesome_explain/subscribers/active_record_subscriber.rb, line 152
def db_logging_enbled?
  # return true if Thread.current['ae_analyze']
  return false if Rails.const_defined?('Console')
  true
end
ddm_query?(sql) click to toggle source
# File lib/awesome_explain/subscribers/active_record_subscriber.rb, line 72
def ddm_query?(sql)
  matched = sql.match(/INSERT|DELETE|UPDATE/)
  matched.present? && matched[0].present?
end
extract_delayed_job_jid(args) click to toggle source
# File lib/awesome_explain/subscribers/active_record_subscriber.rb, line 89
def extract_delayed_job_jid(args)
  Thread.current[:delayed_job]&.id
end
extract_sidekiq_jid(args) click to toggle source
# File lib/awesome_explain/subscribers/active_record_subscriber.rb, line 85
def extract_sidekiq_jid(args)
  Thread.current[:sidekiq_job].dig('jid')
end
extract_sql_operation(sql) click to toggle source
# File lib/awesome_explain/subscribers/active_record_subscriber.rb, line 158
def extract_sql_operation(sql)
  sql.match(/SELECT|INSERT|DELETE|UPDATE/)[0]
end
extract_table_name_and_schema(sql) click to toggle source
# File lib/awesome_explain/subscribers/active_record_subscriber.rb, line 162
def extract_table_name_and_schema(sql)
  matched = sql.match(/FROM\s+(\"\w+\")\.?(\"\w+\")?/)
  return reduce_table_and_schema(matched) if matched && matched[1].present?

  matched = sql.match(/INSERT INTO\s+(\"\w+\")\.?(\"\w+\")?/)
  return reduce_table_and_schema(matched) if matched && matched[1].present?

  matched = sql.match(/UPDATE\s+(\"\w+\")\.?(\"\w+\")?/)
  return reduce_table_and_schema(matched) if matched && matched[1].present?

  # matched = sql.match(/DELETE FROM\s+(\"\w+\")\.?(\"\w+\")?/)
  # return reduce_table_and_schema(matched) if matched && matched[1].present?
end
reduce_table_and_schema(matched) click to toggle source
# File lib/awesome_explain/subscribers/active_record_subscriber.rb, line 176
def reduce_table_and_schema(matched)
  if matched[1].present?
    table_name = matched[2].nil? ? matched[1] : matched[2]
    table_name = table_name.gsub(/\"/, '')
    schema_name = matched[2].nil? ? 'public' : matched[1]
    schema_name = schema_name.gsub(/\"/, '')

    return [table_name, schema_name]
  end
end
resolve_controller_id() click to toggle source
# File lib/awesome_explain/subscribers/active_record_subscriber.rb, line 106
def resolve_controller_id
  data = controller_data
  return nil unless data.present?
  ::AwesomeExplain::Controller.find_or_create_by({
    name: controller_data[:controller],
    action: controller_data[:action],
    path: controller_data[:path],
    params: controller_data[:params].to_json,
    session_id: Thread.current['ae_session_id']
  }).id
end
resolve_delayed_job_class_id() click to toggle source
# File lib/awesome_explain/subscribers/active_record_subscriber.rb, line 132
def resolve_delayed_job_class_id
  return unless Thread.current[:delayed_worker_class].present?
  delayed_worker_class_str = Thread.current[:delayed_worker_class]
  delayed_job_queue_str = Thread.current[:delayed_job_queue].to_s
  delayed_job_worker = ::AwesomeExplain::DelayedJob.find_or_create_by({
    job: delayed_worker_class_str,
    jid: extract_delayed_job_jid(Thread.current[:delayed_job]),
    params: Thread.current[:delayed_job].present? ? Thread.current[:delayed_job].to_json : {}
  })

  delayed_job_worker.id
end
resolve_sidekiq_class_id() click to toggle source
# File lib/awesome_explain/subscribers/active_record_subscriber.rb, line 118
def resolve_sidekiq_class_id
  return unless Thread.current[:sidekiq_worker_class].present?
  sidekiq_worker_class_str = Thread.current[:sidekiq_worker_class]
  sidekiq_queue_str = Thread.current[:sidekiq_queue].to_s
  sidekiq_worker = ::AwesomeExplain::SidekiqWorker.find_or_create_by({
    worker: sidekiq_worker_class_str,
    queue: sidekiq_queue_str,
    jid: extract_sidekiq_jid(Thread.current[:sidekiq_job]),
    params: Thread.current[:sidekiq_job].present? ? Thread.current[:sidekiq_job].to_json : {}
  })

  sidekiq_worker.id
end
resolve_source_name() click to toggle source
# File lib/awesome_explain/subscribers/active_record_subscriber.rb, line 77
def resolve_source_name
  Thread.current['ae_source'] || DEFAULT_SOURCE_NAME
end
resolve_stracktrace_id(stacktrace) click to toggle source
# File lib/awesome_explain/subscribers/active_record_subscriber.rb, line 93
def resolve_stracktrace_id(stacktrace)
  stacktrace_str = stacktrace
    .select {|c| c =~ /^#{::AwesomeExplain::Config.instance.rails_path + '\/(lib|app|db)\/'}/ }
    .map {|c| c.gsub Rails.root.to_s, ''}
    .to_json

  stacktrace = ::AwesomeExplain::Stacktrace.find_or_create_by({
    stacktrace: stacktrace_str
  })

  stacktrace.id
end
sql(event) click to toggle source
# File lib/awesome_explain/subscribers/active_record_subscriber.rb, line 3
def sql(event)
  if track_sql(event) && db_logging_enbled?
    sql = event.payload[:sql]
    begin
      table_name_and_schema = extract_table_name_and_schema(sql)
      table_name = table_name_and_schema.first
      schema_name = table_name_and_schema.last
      request_id = event.payload[:connection_id]
      binds = event.payload[:binds]
      cached = event.payload[:name] == 'CACHE'
      operation = extract_sql_operation(sql)
      name = event.payload[:name]
      stacktrace = caller

      ActiveRecord::Base.transaction do
        explain = nil
        if db_explain_enabled?(sql)# && !cached
          connection_id = event.payload[:connection_id]
          connection = ::AwesomeExplain::Config.instance.connection

          explain_uuid = SecureRandom.uuid
          explain = connection.raw_connection.exec("EXPLAIN (ANALYZE true, COSTS true, FORMAT json) #{sql}")
          #explain = connection.raw_connection.exec_prepared("ae_#{explain_uuid}", binds).to_a
          explain = explain.map { |h| h.values.first }.join("\n")

          explain = ::AwesomeExplain::SqlExplain.new({
            explain_output: explain,
            stacktrace_id: resolve_stracktrace_id(stacktrace),
            controller_id: resolve_controller_id,
          })
          explain.save
        end

        sql_query = {
          table_name: table_name,
          schema_name: schema_name,
          app_name: ::AwesomeExplain::Config.instance.app_name,
          source_name: resolve_source_name,
          duration: event.duration,
          query: sql,
          binds: binds.to_json,
          cached: cached,
          name: name,
          operation: operation,
          session_id: Thread.current[:ae_session_id],
          stacktrace_id: resolve_stracktrace_id(stacktrace),
          sql_explain_id: explain&.id,
          controller_id: resolve_controller_id,
          sidekiq_worker_id: resolve_sidekiq_class_id,
          delayed_job_id: resolve_delayed_job_class_id,
        }
        ::AwesomeExplain::SqlQuery.create(sql_query)
      end
    rescue => exception
      logger.warn sql
      logger.warn exception.to_s
      logger.warn exception.backtrace[0..5]
    end
  end
end
track_sql(event) click to toggle source
# File lib/awesome_explain/subscribers/active_record_subscriber.rb, line 64
def track_sql(event)
  return false if event.payload[:connection].class.name == 'ActiveRecord::ConnectionAdapters::SQLite3Adapter'
  sql = event.payload[:sql]
  !sql.match(/EXPLAIN|SAVEPOINT|nextval|CREATE|BEGIN|COMMIT|ROLLBACK|begin|commit|rollback|ar_|sql_|pg_|explain|logs|controllers|stacktraces|schema_migrations|delayed_jobs/) &&
  sql.strip == sql &&
  event.payload[:name] != 'SCHEMA'
end