#!/usr/bin/env python
#
#   Copyright Hari Sekhon 2007
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA


from sys import exit
from optparse import OptionParser


# Standard Exit Codes for Nagios
OK      = 0
WARNING = 1
CRITICAL= 2
UNKNOWN = 3


def check_ram(warning_threshold,critical_threshold,percent,verbosity,nocache):
    """Takes warning and critical thresholds in KB or percentage if third argument is true,
    and returns a result depending on whether the amount free ram is less than the thresholds"""
   
    if verbosity >= 3:
        print "Opening /proc/meminfo"
    try:
        f = open('/proc/meminfo')
    except Exception,e:
        print "RAM CRITICAL: Error opening /proc/meminfo - %s" % str(e)
        return CRITICAL
    
    output = f.readlines()
    
    for x in range(len(output)):
        y = output[x].split()
        if y [0] == "MemTotal:":
            memtotal  = int(y[1])
        elif y[0] == "MemFree:":
            memfree   = int(y[1])
        elif y[0] == "Cached:":
            memcached = int(y[1])
  
    for x in memtotal,memfree,memcached:
        if x == None:
            print "UNKNOWN: failed to get mem stats" 
            return UNKNOWN

    if nocache == True:
        total_free = memfree
    else:
        total_free = memfree + memcached
    
    total_used_megs = float(memtotal-memfree) / 1024
    total_free_megs = float(total_free) / 1024
    memtotal_megs   = float(memtotal) / 1024

    if percent == True:
        percentage_free = int( float(total_free) / float(memtotal) * 100 )
        if percentage_free < critical_threshold:
            print "RAM CRITICAL: %d%% ram free (%d/%d MB used)" % (percentage_free,total_used_megs,memtotal_megs)
            return CRITICAL
        elif percentage_free < warning_threshold:
            print "RAM WARNING: %d%% ram free (%d/%d MB used)" % (percentage_free,total_used_megs,memtotal_megs)
            return WARNING
        else:
            print "RAM OK: %d%% ram free" % percentage_free
            return OK
    else:
        if total_free < critical_threshold:
            print "RAM CRITICAL: %dMB ram free (%d/%d MB used)" % (total_free_megs,total_used_megs,memtotal_megs)
            return CRITICAL
        if total_free < warning_threshold:
            print "RAM WARNING: %dMB ram free (%d/%d MB used)" % (total_free_megs,total_used_megs,memtotal_megs)
            return WARNING
        else:
            print "RAM OK: %dMB ram free" % (total_free_megs)
            return OK


def main():
    """main func, parse args, do sanity checks and call check_ram func"""
    
    parser = OptionParser()
    
    parser.add_option("-n","--no-include-cache",action="store_true",dest="nocache",
                        help="Do not include cache as free ram. Linux tends to gobble up free ram "
                        + "as disk cache, but this is freely reusable so this plugin counts it as "
                        + "free space by default since this is nearly always what you want. This "
                        + "switch disables this behaviour so you use only the pure free ram. Not advised.")
    parser.add_option("-c","--critical",dest="critical_threshold",
                        help="Critical threshold. Returns a critical status if the amount of free ram "
                        + "is less than this number. Specify KB,MB or GB after to specify units of "
                        + "KiloBytes, MegaBytes or GigaBytes respectively or % afterwards to indicate"
                        + "a percentage. KiloBytes is used if not specified")
    parser.add_option("-v","--verbose",action="count",dest="verbosity",
                        help="Verbose mode. Good for testing plugin. By default only one result line "
                        + "is printed as per Nagios standards. Use multiple times for increasing "
                        + "verbosity (3 times = debug)")
    parser.add_option("-w","--warning",dest="warning_threshold",
                        help="warning threshold. Returns a warning status if the amount of free ram "
                        + "is less than this number. Specify KB,MB or GB after to specify units of "
                        + "KiloBytes, MegaBytes or GigaBytes respectively or % afterwards to indicate "
                        + "a percentage. KiloBytes is used if not specified")

    options,args = parser.parse_args()

    # This script doesn't take any args, only options so we print
    # usage and exit if any are found
    if args:
        parser.print_help()
        return UNKNOWN

    nocache = False

    warning_threshold  = options.warning_threshold  
    critical_threshold = options.critical_threshold 
    nocache            = options.nocache            
    verbosity          = options.verbosity          

    #====================================================================================#
    #                                Sanity Checks                                       #
    #   This is TOO big really but it allows for nice flexibility on the command line    #
    #====================================================================================#
    if warning_threshold == None:
        print "UNKNOWN: you did not specify a warning threshold\n"
        parser.print_help() 
        return UNKNOWN
    elif critical_threshold == None:
        print "UNKNOWN: you did not specify a critical threshold\n"
        parser.print_help() 
        return UNKNOWN
    else:
        warning_threshold  = str( warning_threshold  )
        critical_threshold = str( critical_threshold )

    megs = [ "MB", "Mb", "mb", "mB" , "M", "m" ]
    gigs = [ "GB", "Gb", "gb", "gB" , "G", "g" ]

    W_percent = False
    C_percent = False

    def get_threshold(input):
        """takes one arg and returns the float threshold value"""

        try:
            threshold = float(input)
        except ValueError:
            print "UNKNOWN: invalid threshold given"
            exit(UNKNOWN)

        return threshold

    # Find out if the supplied argument is a percent or a size
    # and get it's value
    if warning_threshold[-1] == "%":
        warning_threshold = get_threshold(warning_threshold[:-1])
        W_percent = True
    elif warning_threshold[-2:] in megs:
        warning_threshold = get_threshold(warning_threshold[:-2]) * 1024
    elif warning_threshold[-1] in megs:
        warning_threshold = get_threshold(warning_threshold[:-1]) * 1024
    elif warning_threshold[-2:] in gigs:
        warning_threshold = get_threshold(warning_threshold[:-2]) * 1024 * 1024
    elif warning_threshold[-1] in gigs:
        warning_threshold = get_threshold(warning_threshold[:-1]) * 1024 * 1024
    else:
        warning_threshold = get_threshold(warning_threshold)

    if critical_threshold[-1] == "%":
        critical_threshold = get_threshold(critical_threshold[:-1])
        C_percent = True
    elif critical_threshold[-2:] in megs:
        critical_threshold = get_threshold(critical_threshold[:-2]) * 1024
    elif critical_threshold[-1] in megs:
        critical_threshold = get_threshold(critical_threshold[:-1]) * 1024
    elif critical_threshold[-2:] in gigs:
        critical_threshold = get_threshold(critical_threshold[:-2]) * 1024 * 1024
    elif critical_threshold[-1] in gigs:
        critical_threshold = get_threshold(critical_threshold[:-1]) * 1024 * 1024
    else:
        critical_threshold = get_threshold(critical_threshold)

    # Make sure that we use either percentages or units but not both as this makes
    # the code more ugly and complex
    if W_percent == True and C_percent == True:
        percent_true = True
    elif W_percent == False and C_percent == False:
        percent_true = False
    else:
        print "UNKNOWN: please make thresholds either units or percentages, not one of each"
        return UNKNOWN

    # This assumes that the percentage units are numeric, which they must be to have gotten
    # through the get_threhold func above
    if W_percent == True:
        if (warning_threshold < 0) or (warning_threshold > 100):
            exit_invalid_warning("warning percentage must be between 0 and 100")
    if C_percent == True:
        if (critical_threshold < 0) or (critical_threshold > 100):
            exit_invalid_critical("critical percentage must be between 0 and 100")

    if warning_threshold <= critical_threshold:
        print "UNKNOWN: Critical threshold must be less than Warning threshold"
        return UNKNOWN

    # End of Sanity Checks

    result = check_ram(warning_threshold,critical_threshold,percent_true,verbosity,nocache)

    return result

if __name__ == "__main__":
    result = main()
    exit(result)

