class MysqlHealth::Health

Public Class Methods

new(options = {}) click to toggle source
# File lib/mysql_health/health.rb, line 32
def initialize(options = {})
  @options = options

  @mutex = Mutex.new
  @scheduler = Rufus::Scheduler.start_new
  def @scheduler.handle_exception(job, e)
    MysqlHealth.log.error "job #{job.job_id} caught #{e.class} exception '#{e}' #{e.backtrace.join("\n")}"
  end

  if options[:master]
    master_status = {}
    master_status[:status] = 503
    master_status[:content] = "Health of master not yet determined\n"
    self.master_status=(master_status)
    @scheduler.every options[:interval], :allow_overlapping => options[:allow_overlapping], :first_in => options[:delay] do 
      check_master
    end
  else
    master_status = {}
    master_status[:status] = '501 Not Enabled'
    master_status[:content] = "Health of master not enabled\n"
    self.master_status=(master_status)
  end

  if options[:slave]
    slave_status = {}
    slave_status[:status] = '501 Not Enabled'
    slave_status[:content] = "Health of slave not yet determined\n"
    self.slave_status=(slave_status)
    @scheduler.every options[:interval], :allow_overlapping => options[:allow_overlapping], :first_in => options[:delay] do 
      check_slave
    end
  else
    slave_status = {}
    slave_status[:status] = '501 Not Enabled'
    slave_status[:content] = "Health of slave not enabled\n"
    self.slave_status=(slave_status)
  end
end

Public Instance Methods

check_master() click to toggle source
# File lib/mysql_health/health.rb, line 111
def check_master
  MysqlHealth.log.debug("check_master")
  response = {}
  response[:content_type] = 'text/plain'

  begin
    # connect to the MySQL server
    dbh = DBI.connect(@options[:dsn], @options[:username], @options[:password])

    # Validate that database exists
    unless @options[:database].nil?
      unless database?(dbh, @options[:database])
        raise Exception.new("Database schema named #{@options[:database]} does not exist")
      end
    end

    status = show_status(dbh)
    if status.length > 0
      if read_only?(dbh)
        response[:status] = '503 Service Read Only'
        response[:content] = describe_status(status)
      else
        response[:status] = '200 OK'
        response[:content] = describe_status(status)
      end
    else
      response[:status] = '503 Service Unavailable'
      response[:content] = describe_status(status)
    end
  rescue Exception => e
    response[:status] = '500 Server Error'
    response[:content] = e.message
  end
  self.master_status=(response)
end
check_slave() click to toggle source
# File lib/mysql_health/health.rb, line 187
def check_slave
  MysqlHealth.log.debug("check_slave")
  response = {}
  response[:content_type] = 'text/plain'

  begin
    # connect to the MySQL server
    dbh = DBI.connect(@options[:dsn], @options[:username], @options[:password])

    # Validate that database exists
    unless @options[:database].nil?
      unless database?(dbh, @options[:database])
        raise Exception.new("Database schema named #{@options[:database]} does not exist")
      end
    end

    show_slave_status = show_status(dbh, :slave)
    if show_slave_status.length > 0
      seconds_behind_master = show_slave_status[:seconds_behind_master]

      # We return a "203 Non-Authoritative Information" when replication is shot. We don't want to reduce site performance, but still want to track that something is awry.
      if seconds_behind_master.eql?('NULL')
        response[:status] = '203 Slave Stopped'
        response[:content] = show_slave_status.to_json
        response[:content_type] = 'application/json'
      elsif seconds_behind_master.to_i > 60*30
        response[:status] = '203 Slave Behind'
        response[:content] = show_slave_status.to_json
        response[:content_type] = 'application/json'
      elsif read_only?(dbh)
        response[:status] = '200 OK ' + seconds_behind_master  + ' Seconds Behind Master'
        response[:content] = show_slave_status.to_json
        response[:content_type] = 'application/json'
      else
        response[:status] = '503 Service Unavailable'
        response[:content] = show_slave_status.to_json
        response[:content_type] = 'application/json'
      end
    elsif @options[:allow_master] && master?(dbh)
        response[:status] = '200 OK Master Running'
        response[:content] = describe_status show_status(dbh)
        response[:content_type] = 'application/json'
    else
      response[:status] = '503 Slave Not Configured'
      response[:content] = show_slave_status.to_json
    end
  rescue Exception => e
    response[:status] = '500 Server Error'
    response[:content] = e.message
  end
  self.slave_status=(response)
end
database?(dbh, name) click to toggle source
# File lib/mysql_health/health.rb, line 183
def database?(dbh, name)
  show_create_database(dbh, name).nil? == false
end
describe_status(status) click to toggle source
# File lib/mysql_health/health.rb, line 147
def describe_status(status)
  # Mimick "mysqladmin status"
  "Uptime: %s  Threads: %s  Questions: %s  Slow queries: %s  Opens: %s  Flush tables: %s  Open tables: %s  Queries per second avg: %.3f\n" %
            [ status[:uptime], status[:threads_running], status[:questions], status[:slow_queries], status[:opened_tables], status[:flush_commands], status[:open_tables], status[:queries].to_i/status[:uptime].to_i]
end
master?(dbh) click to toggle source
# File lib/mysql_health/health.rb, line 107
def master?(dbh)
  read_only?(dbh) == false
end
master_status() click to toggle source
# File lib/mysql_health/health.rb, line 79
def master_status 
  master_status = nil
  @mutex.synchronize do
    master_status = @master_status
  end
  return master_status
end
master_status=(response) click to toggle source
# File lib/mysql_health/health.rb, line 72
def master_status=(response)
  @mutex.synchronize do
    MysqlHealth.log.info("master status: #{response[:status]} (#{response[:content].split(/\n/)[0]})")
    @master_status = response
  end
end
read_only?(dbh) click to toggle source
# File lib/mysql_health/health.rb, line 102
def read_only?(dbh)
  variables = dbh.select_all("SHOW VARIABLES WHERE Variable_name = 'read_only' AND Value = 'ON'")
  return (variables.length == 1)
end
show_create_database(dbh, name) click to toggle source
# File lib/mysql_health/health.rb, line 173
def show_create_database(dbh, name)
  schema = nil
  dbh.execute('SHOW CREATE DATABASE %s' % name) do |sth|
    sth.fetch() do |row|
      schema = row[1]
    end
  end
  return schema
end
show_status(dbh, type = nil) click to toggle source

Return a hash of status information

# File lib/mysql_health/health.rb, line 154
def show_status(dbh, type = nil)
  status = {}
  if type.nil?
    # Format of "SHOW STATUS" differs from "SHOW [MASTER|SLAVE] STATUS"
    dbh.select_all('SHOW STATUS') do |row|
      status[row[0].downcase.to_sym] = row[1]
    end
  else
    dbh.execute('SHOW %s STATUS' % type.to_s.upcase) do |sth|
      sth.fetch_hash() do |row|
        row.each_pair do |k,v|
          status[k.downcase.to_sym] = v
        end
      end
    end
  end
  status
end
slave_status() click to toggle source
# File lib/mysql_health/health.rb, line 94
def slave_status
  slave_status = nil
  @mutex.synchronize do
    slave_status = @slave_status
  end
  return slave_status
end
slave_status=(response) click to toggle source
# File lib/mysql_health/health.rb, line 87
def slave_status=(response)
  @mutex.synchronize do 
    MysqlHealth.log.info("slave status: #{response[:status]} (#{response[:content].split(/\n/)[0]})")
    @slave_status = response
  end
end