sensu-plugins/sensu-plugins-mysql

View on GitHub
bin/check-mysql-msr-replication-status.rb

Summary

Maintainability
B
5 hrs
Test Coverage
#!/usr/bin/env ruby
# frozen_string_literal: false

#
# MySQL Multi-source Replication Status
# ===
#
#
#   EXAMPLE
#     check-mysql-msr-replication-status.rb -h db01 --ini '/etc/sensu/my.cnf'
#     check-mysql-msr-replication-status.rb -h db01 --ini '/etc/sensu/my.cnf' --ini-section customsection
#
#   MY.CNF INI FORMAT
#   [client]
#   user=sensu
#   password="abcd1234"
#
#   [customsection]
#   user=user
#   password="password"
#

require 'sensu-plugin/check/cli'
require 'mysql'
require 'inifile'

class CheckMysqlMSRReplicationStatus < Sensu::Plugin::Check::CLI
  option :host,
         short: '-h',
         long: '--host VALUE',
         description: 'Database host'

  option :port,
         short: '-P',
         long: '--port VALUE',
         description: 'Database port',
         default: 3306,
         proc: proc(&:to_i)

  option :socket,
         short: '-s SOCKET',
         long: '--socket SOCKET',
         description: 'Socket to use'

  option :user,
         short: '-u',
         long: '--username VALUE',
         description: 'Database username'

  option :pass,
         short: '-p',
         long: '--password VALUE',
         description: 'Database password'

  option :ini,
         short: '-i',
         long: '--ini VALUE',
         description: 'My.cnf ini file'

  option :ini_section,
         description: 'Section in my.cnf ini file',
         long: '--ini-section VALUE',
         default: 'client'

  option :warn,
         short: '-w',
         long: '--warning VALUE',
         description: 'Warning threshold for replication lag',
         default: 900,
         proc: proc(&:to_i)

  option :crit,
         short: '-c',
         long: '--critical=VALUE',
         description: 'Critical threshold for replication lag',
         default: 1800,
         proc: proc(&:to_i)

  def set_status(io_thread_status, sql_thread_status, seconds_behind_master)
    if io_thread_status == 'No' || sql_thread_status == 'No' || seconds_behind_master > config[:crit]
      2
    elsif seconds_behind_master > config[:warn] && seconds_behind_master <= config[:crit]
      1
    else
      0
    end
  end

  def run
    if config[:ini]
      ini = IniFile.load(config[:ini])
      section = ini[config[:ini_section]]
      db_user = section['user']
      db_pass = section['password']
    else
      db_user = config[:user]
      db_pass = config[:pass]
    end
    db_host = config[:host]

    if [db_host, db_user, db_pass].any?(&:nil?)
      unknown 'Must specify host, user, password'
    end

    begin
      ok_statuses = []
      warn_statuses = []
      crit_statuses = []
      output = []

      db = Mysql.new(db_host, db_user, db_pass, nil, config[:port], config[:socket])
      channels = db.query('SELECT channel_name FROM performance_schema.replication_connection_status')

      channels.num_rows.times do
        channel = channels.fetch_hash
        results = db.query("SHOW SLAVE STATUS FOR CHANNEL \'#{channel['channel_name']}\'")
        results.each_hash do |row|
          io_thread_status = row['Slave_IO_Running']
          sql_thread_status = row['Slave_SQL_Running']
          seconds_behind_master = row['Seconds_Behind_Master'].to_i
          status = set_status
          message = "#{channel['channel_name']} STATES:"
          message += " Slave_IO_Running=#{io_thread_status}"
          message += ", Slave_SQL_Running=#{sql_thread_status}"
          message += ", Seconds_Behind_Master=#{seconds_behind_master}"

          if status == 0
            ok_statuses << message
          elsif status == 1
            warn_statuses << message
          elsif status == 2
            crit_statuses << message
          else
            puts 'Undefined status.'
          end
        end
      end
      output << crit_statuses unless crit_statuses.empty?
      output << warn_statuses unless warn_statuses.empty?
      output << ok_statuses unless ok_statuses.empty?

      if !crit_statuses.empty?
        critical output
      elsif !warn_statuses.empty?
        warning output
      else
        ok output
      end
    rescue Mysql::Error => e
      errstr = "Error code: #{e.errno} Error message: #{e.error}"
      errstr += "SQLSTATE: #{e.sqlstate}" if e.respond_to?('sqlstate')
      critical errstr
    rescue StandardError => e
      critical "unhandled exception: #{e}"
    ensure
      db&.close
    end
  end
end