#!/usr/bin/env python3
# ***************************************************************************
#  IIIIII NNN  NNN  Copyright (C) 2022 Innovative Networks, Inc.
#    II    NNN  N   All Rights Reserved. Any redistribution or reproduction
#    II    N NN N   of part or all of the content of this program in any form
#    II    N  NNN   without expressed written consent of the copyright holder
#  IIIIII NNN  NNN  is strictly prohibited.  Please contact admins@in-kc.com
#   Be Innovative.  for additional information.
# ***************************************************************************
#  ScriptName.py - Title of the Script
#  Author - Ian Perry <iperry@indigex.com>
#
#  Purpose:  Tell what the script does
#
#  Version History:
#       2022.11.04 - Initial write
#       2022.11.07 - Modified to use lowercase u instead of U
# ***************************************************************************

#######
# Script utilizes /proc/meminfo to pull memory information as per
# https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773# Where MemAvailable cannot be pulled from /proc/meminfo, it is calculated from free+cached as per the above link.
#######

try:
    from inmon_utils import *
except:
    import sys
    print("Failed to import inmon_utils")
    sys.exit(3)

import argparse
import paramiko
import time # Necessary for SSH communication
import re
import os

parser = argparse.ArgumentParser(description="Checks CPU on ")

parser.add_argument(
    "-i", "--keyfile",
    dest="keyfile",
    help="Specify SSH keyfile",
    required=True
)
parser.add_argument(
    "-H", "--hostname",
    dest="hostname",
    help="Specify device hostname",
    required=True
)
parser.add_argument(
    "-u", "--username",
    dest="username",
    help="Specify username to log into device with",
    required=True
)
parser.add_argument(
    "-c", "--critical",
    dest="critical",
    help="Specify critical threshold",
    required=True
)
parser.add_argument(
    "-w", "--warning",
    dest="warning",
    help="Specify warning threshold",
    required=True
)

args = parser.parse_args()

keyfile = os.path.expanduser(args.keyfile)

regex_percent = re.compile('^[0-9]+%?$')
regex_absolute = re.compile('^[0-9]+[BKMGbkmg]$')

if regex_percent.match(args.warning) and regex_percent.match(args.critical):
    mode="PCT"
elif regex_absolute.match(args.warning) and regex_absolute.match(args.critical):
    mode="ABS"
    warning_denomination = args.warning[-1]
    critical_denomination = args.critical[-1]

    warning_amount = float(args.warning[0:-1])
    critical_amount = float(args.critical[0:-1])

    if re.match(r'b|B', warning_denomination):
        warning_level = warning_amount*(1024**0)
    elif re.match(r'k|K', warning_denomination):
        warning_level = warning_amount*(1024**1)
    elif re.match(r'm|M', warning_denomination):
        warning_level = warning_amount*(1024**2)
    elif re.match(r'g|G', warning_denomination):
        warning_level = warning_amount*(1024**3)
    else:
        print(f"UNKNOWN - Bad unit of measurement used for absolute memory warn level. Warn level {args.warning}, crit level {args.critial}")
        sys.exit(3)


    if re.match(r'b|B', critical_denomination):
        critical_level = critical_amount*(1024**0)
    elif re.match(r'k|K', critical_denomination):
        critical_level = critical_amount*(1024**1)
    elif re.match(r'm|M', critical_denomination):
        critical_level = critical_amount*(1024**2)
    elif re.match(r'g|G', critical_denomination):
        critical_level = critical_amount*(1024**3)
    else:
        print(f"UNKNOWN - Bad unit of measurement used for absolute memory crit level. Warn level {args.warning}, crit level {args.critial}")
        sys.exit(3)
else:
    print("UNKNOWN -- Arguments must be same type, either both percent or both absolute.")
    sys.exit(3)

if args.warning > args.critical:
    print("UNKNOWN - Warning level must be less than critical level")

client = paramiko.client.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(args.hostname, username=args.username, key_filename=keyfile, disabled_algorithms={'keys': ['rsa-sha2-256', 'rsa-sha2-512'], 'pubkeys': ['rsa-sha2-256', 'rsa-sha2-512']})
stdin, stdout, stderr = client.exec_command('cat /proc/meminfo')
time.sleep(1)
client.close()
output = stdout.read().splitlines()
output = [i.decode() for i in output]
out_dict = {i.split()[0].replace(':', '') : i.split()[1] for i in output}

if "MemAvailable" in out_dict:
    mem_available = int(out_dict["MemAvailable"])
else:
    mem_available = int(out_dict["MemFree"]) + int(out_dict["Cached"])

mem_total = int(out_dict["MemTotal"]) * 1024
mem_free = int(out_dict["MemFree"]) * 1024
buffers = int(out_dict["Buffers"]) * 1024
cached = int(out_dict["Cached"]) * 1024
mem_available = mem_available * 1024

mem_used = mem_total - mem_available
mem_simple = mem_used
percent_used = round((mem_used*100)/mem_total, 2)

denom_list = ['B', 'K', 'M', 'G', 'T']

if mode == "ABS":
    power = 0
    while mem_simple >= 1024:
        mem_simple = mem_simple/1024
        power += 1
    mem_simple = round(mem_simple, 2)

    denom = denom_list[power]

    if mem_used <= warning_level:
        out_text = "[OK]"
        return_code = 0
    elif mem_used >= warning_level and mem_used < critical_level:
        out_text = "[WARNING]"
        return_code = 1
    elif mem_used >= critical_level:
        out_text = "[CRITICAL]"
        return_code = 2
    else:
        out_text = f"[UNKNOWN]"
        return_code = 3
    out_text +=  f" - RAM usage is {mem_simple}{denom}."
    percent_warn = (warning_level/mem_total)*100
    percent_crit = (critical_level/mem_total)*100
elif mode == "PCT":
    if percent_used < float(args.warning):
        out_text = "[OK]"
        return_code = 0
    elif percent_used >= float(args.warning) and percent_used < float(args.critical):
        out_text = "[WARNING]"
        return_code = 1
    elif percent_used >= float(args.critical):
        out_text = "[CRIT]"
        return_code = 2
    else:
        out_text = "[UNKNOWN]"
        return_code = 3
    out_text += f" - RAM usage is {percent_used}%."
    percent_warn = float(args.warning)
    percent_crit = float(args.critical)

out_text += f" | PercentUsed={percent_used}%;{percent_warn};{percent_crit}; MemTotal={mem_total}B;; MemFree={mem_free}B;; MemAvailable={mem_available}B;; Buffers={buffers}B;; Cached={cached}B"

print(out_text)
sys.exit(return_code)
