sensu-plugins/sensu-plugins-io-checks

View on GitHub
bin/metrics-ioping.rb

Summary

Maintainability
A
35 mins
Test Coverage
#! /usr/bin/env ruby
# frozen_string_literal: false

#
#   metrics-ioping
#
# DESCRIPTION:
#   Push ioping stats into graphite
#
# OUTPUT:
#   metric data
#
# PLATFORMS:
#   Linux
#
# DEPENDENCIES:
#   gem: sensu-plugin
#   gem: socket
#
# USAGE:
#   Collect IO metrics from /dev/sda
#   metrics-ioping.rb -n sda -d /dev/sda
#
# NOTES:
#   The ioping command must be installed
#
# LICENSE:
#   Copyright 2014 Mitsutoshi Aoe <maoe@foldr.in>
#   Released under the same terms as Sensu (the MIT license); see LICENSE
#   for details.
#

require 'sensu-plugin/metric/cli'
require 'socket'

class IOPingMetrics < Sensu::Plugin::Metric::CLI::Graphite
  option :destination,
         short: '-d DEVICE|FILE|DIRECTORY',
         description: 'Destination device, file or directory',
         required: true

  option :name,
         short: '-n NAME',
         description: 'Name of the series',
         required: true

  option :count,
         short: '-c COUNT',
         description: 'Stop after count requests',
         default: 1,
         proc: proc(&:to_i)

  option :interval,
         short: '-i INTERVAL',
         description: 'Interval between requests in seconds',
         default: 1.0,
         proc: proc(&:to_f)

  option :cached,
         short: '-C',
         description: 'Use cached I/O',
         boolean: true,
         default: false

  option :direct,
         short: '-D',
         description: 'Use direct I/O',
         boolean: true,
         default: false

  option :scheme,
         description: 'Metric naming scheme, text to prepend to metric',
         short: '-s SCHEME',
         long: '--scheme SCHEME',
         default: Socket.gethostname

  def run
    stats = ioping_stats(config[:server])
    critical 'Failed to get/parse ioping output' if stats.nil?
    stats.each do |key, value|
      output([config[:scheme], :ioping, config[:name], key].join('.'), value)
    end
    ok
  end

  def ioping_stats(_servers)
    options = []
    options << '-C' if config[:cached]
    options << '-D' if config[:direct]
    output = `ioping #{options.join(' ')} -c #{config[:count]} -i #{config[:interval]} #{config[:destination]}`
    parse_ioping(output)
  end

  def parse_ioping(str)
    stats = parse_0_8(str)
    stats = parse_0_6(str) if stats.nil?
    stats
  end

  NUMBER = /\d+(?:\.\d+)?/.freeze
  TIME_UNIT = /(?:ns|us|ms|s|min|hour|day)/.freeze
  TIME_UNITS = {
    'ns' => 1e-9,
    'us' => 1e-6,
    'ms' => 1e-3,
    's' => 1,
    'min' => 60,
    'hour' => 60 * 60,
    'day' => 24 * 60 * 60
  }.freeze
  # #YELLOW
  STATS_HEADER = /min\/avg\/max\/mdev/.freeze

  def parse_0_6(str)
    value = /#{NUMBER}/
    sep = /\//
    pattern = /^#{STATS_HEADER} = (#{value})#{sep}(#{value})#{sep}(#{value})#{sep}(#{value}) (#{TIME_UNIT})$/
    str.scan(pattern).each do |scanned|
      min, avg, max, mdev, time_unit = scanned
      time_unit = TIME_UNITS[time_unit]
      if scanned.all? && time_unit
        return {
          min: min.to_f * time_unit,
          avg: avg.to_f * time_unit,
          max: max.to_f * time_unit,
          mdev: mdev.to_f * time_unit
        }
      end
    end
    nil
  end

  def parse_0_8(str)
    value = /(#{NUMBER}) (#{TIME_UNIT})\s/
    sep = /\/\s/
    pattern = /^#{STATS_HEADER} = #{value}#{sep}#{value}#{sep}#{value}#{sep}#{value}$/
    str.scan(pattern).each do |scanned|
      values = []
      units = []
      scanned.each_with_index do |val, idx|
        if idx.even?
          values[idx / 2] = val.to_f
        else
          units[idx / 2] = TIME_UNITS[val]
        end
      end

      if values.all? && units.all?
        return {
          min: values[0] * units[0],
          avg: values[1] * units[1],
          max: values[2] * units[2],
          mdev: values[3] * units[3]
        }
      end
    end
    nil
  end
end