bin/check-disk-usage.rb
#! /usr/bin/env ruby
# frozen_string_literal: false
#
# check-disk
#
# DESCRIPTION:
# Uses the sys-filesystem gem to get filesystem mount points and metrics
#
# OUTPUT:
# plain text
#
# PLATFORMS:
# Linux, BSD, Windows
#
# DEPENDENCIES:
# gem: sensu-plugin
# gem: sys-filesystem
#
# USAGE:
#
# NOTES:
#
# LICENSE:
# Copyright 2015 Yieldbot Inc <Sensu-Plugins>
# Released under the same terms as Sensu (the MIT license); see LICENSE
# for details.
#
require 'sensu-plugin/check/cli'
require 'sys/filesystem'
#
# Check Disk
#
class CheckDisk < Sensu::Plugin::Check::CLI
include Sys
option :fstype,
short: '-t TYPE[,TYPE]',
description: 'Only check fs type(s)',
proc: proc { |a| a.split(',') }
option :ignoretype,
short: '-x TYPE[,TYPE]',
description: 'Ignore fs type(s)',
proc: proc { |a| a.split(',') }
option :ignoremnt,
short: '-i MNT[,MNT]',
description: 'Ignore mount point(s)',
proc: proc { |a| a.split(',') }
option :includemnt,
description: 'Include only mount point(s)',
short: '-I MNT[,MNT]',
proc: proc { |a| a.split(',') }
option :ignorepathre,
short: '-p PATHRE',
description: 'Ignore mount point(s) matching regular expression',
proc: proc { |a| Regexp.new(a) }
option :ignoreopt,
short: '-o TYPE[.TYPE]',
description: 'Ignore option(s)',
proc: proc { |a| a.split('.') }
option :ignorereadonly,
long: '--ignore-readonly',
description: 'Ignore read-only filesystems',
boolean: true,
default: false
option :ignore_reserved,
description: 'Ignore bytes reserved for privileged processes',
short: '-r',
long: '--ignore-reserved',
boolean: true,
default: false
option :bwarn,
short: '-w PERCENT',
description: 'Warn if PERCENT or more of disk full',
proc: proc(&:to_i),
default: 85
option :bcrit,
short: '-c PERCENT',
description: 'Critical if PERCENT or more of disk full',
proc: proc(&:to_i),
default: 95
option :iwarn,
short: '-W PERCENT',
description: 'Warn if PERCENT or more of inodes used',
proc: proc(&:to_i),
default: 85
option :icrit,
short: '-K PERCENT',
description: 'Critical if PERCENT or more of inodes used',
proc: proc(&:to_i),
default: 95
option :magic,
short: '-m MAGIC',
description: 'Magic factor to adjust warn/crit thresholds. Example: .9',
proc: proc(&:to_f),
default: 1.0
option :normal,
short: '-n NORMAL',
description: 'Levels are not adapted for filesystems of exactly this '\
'size, where levels are reduced for smaller filesystems and raised '\
'for larger filesystems.',
proc: proc(&:to_f),
default: 20
option :minimum,
short: '-l MINIMUM',
description: 'Minimum size to adjust (in GB)',
proc: proc(&:to_f),
default: 100
# Setup variables
#
def initialize
super
@crit_fs = []
@warn_fs = []
end
# Get mount data
#
def fs_mounts
begin
mounts = Filesystem.mounts
rescue StandardError
unknown 'An error occured getting the mount info'
end
mounts.each do |line|
begin
next if config[:fstype] && !config[:fstype].include?(line.mount_type)
next if config[:ignoretype]&.include?(line.mount_type)
next if config[:ignoremnt]&.include?(line.mount_point)
next if config[:ignorepathre]&.match(line.mount_point)
next if config[:ignoreopt]&.include?(line.options)
next if config[:ignorereadonly] && line.options.split(',').include?('ro')
next if config[:includemnt] && !config[:includemnt].include?(line.mount_point)
rescue StandardError
unknown 'An error occured getting the mount info'
end
check_mount(line)
end
end
# Adjust the percentages based on volume size
#
def adj_percent(size, percent)
hsize = (size / (1024.0 * 1024.0)) / config[:normal].to_f
felt = hsize**config[:magic]
scale = felt / hsize
100 - ((100 - percent) * scale)
end
def check_mount(line)
begin
fs_info = Filesystem.stat(line.mount_point)
rescue StandardError
@warn_fs << "#{line.mount_point} Unable to read."
return
end
if line.mount_type == 'devfs'
return
end
if fs_info.respond_to?(:inodes) && !fs_info.inodes.nil? # needed for windows
percent_i = percent_inodes(fs_info)
if percent_i >= config[:icrit]
@crit_fs << "#{line.mount_point} #{percent_i}% inode usage"
elsif percent_i >= config[:iwarn]
@warn_fs << "#{line.mount_point} #{percent_i}% inode usage"
end
end
percent_b = percent_bytes(fs_info)
if fs_info.bytes_total < (config[:minimum] * 1_000_000_000)
bcrit = config[:bcrit]
bwarn = config[:bwarn]
else
bcrit = adj_percent(fs_info.bytes_total, config[:bcrit])
bwarn = adj_percent(fs_info.bytes_total, config[:bwarn])
end
used = to_human(fs_info.bytes_used)
total = to_human(fs_info.bytes_total)
if percent_b >= bcrit
@crit_fs << "#{line.mount_point} #{percent_b}% bytes usage (#{used}/#{total})"
elsif percent_b >= bwarn
@warn_fs << "#{line.mount_point} #{percent_b}% bytes usage (#{used}/#{total})"
end
end
def to_human(size)
unit = [
[1_099_511_627_776, 'TiB'],
[1_073_741_824, 'GiB'],
[1_048_576, 'MiB'],
[1024, 'KiB'],
[0, 'B']
].detect { |u| size >= u[0] }
format(
"%.2f #{unit[1]}",
(size >= 1024 ? size.to_f / unit[0] : size)
)
end
# Determine the percent inode usage
#
def percent_inodes(fs_info)
(100.0 - (100.0 * fs_info.inodes_free / fs_info.inodes)).round(2)
end
# Determine the percent byte usage
#
def percent_bytes(fs_info)
if config[:ignore_reserved]
u100 = fs_info.bytes_used * 100.0
nonroot_total = fs_info.bytes_used + fs_info.bytes_available
if nonroot_total.zero?
0
else
(u100 / nonroot_total + (u100 % nonroot_total != 0 ? 1 : 0)).round(2)
end
else
(100.0 - (100.0 * fs_info.bytes_free / fs_info.bytes_total)).round(2)
end
end
# Generate output
#
def usage_summary
(@crit_fs + @warn_fs).join(', ')
end
# Main function
#
def run
fs_mounts
critical usage_summary unless @crit_fs.empty?
warning usage_summary unless @warn_fs.empty?
ok "All disk usage under #{config[:bwarn]}% and inode usage under #{config[:iwarn]}%"
end
end