#!/usr/bin/env python3

"""
Check amount of memory and swap is used

/proc/meminfo :
MemTotal:        2039432 kB <- MemTotal
MemFree:          274036 kB <- MemFree
MemAvailable:    1372156 kB
Buffers:          171008 kB <- Buffers
Cached:          1006260 kB <- Cached
SwapCached:         1260 kB
Active:          1067436 kB
Inactive:         499392 kB
Active(anon):     207748 kB
Inactive(anon):   199896 kB
Active(file):     859688 kB
Inactive(file):   299496 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:       1951740 kB <- SwapTotal
SwapFree:        1923224 kB <- SwapFree
Dirty:              2624 kB
Writeback:             0 kB
AnonPages:        388500 kB
Mapped:            64828 kB
Shmem:             18044 kB
Slab:             152260 kB
SReclaimable:     126940 kB
SUnreclaim:        25320 kB
KernelStack:        4736 kB
PageTables:        12180 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:     2971456 kB
Committed_AS:    1047040 kB
VmallocTotal:   34359738367 kB
VmallocUsed:           0 kB
VmallocChunk:          0 kB
HardwareCorrupted:     0 kB
AnonHugePages:    245760 kB
CmaTotal:              0 kB
CmaFree:               0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
DirectMap4k:      102272 kB
DirectMap2M:     1994752 kB
DirectMap1G:           0 kB

MemUsed = MemTotal - MemFree - Buffers - Cached
SwapUsed = SwapTotal - SwapFree
"""

import re
import sys
import argparse

def get_options():
    options = {
        "warn_mem": None,
        "warn_swap": None,
        "crit_mem": None,
        "crit_swap": None,
    }
    parser = argparse.ArgumentParser(description="Check amount of memory and swap is used")
    parser.add_argument('-w', '--warning', metavar="'mem,swap'", required=True)
    parser.add_argument('-c', '--critical', metavar="'mem,swap'", required=True)
    args = parser.parse_args()
    if args.warning is not None:
        m = re.match(r'(\d+),(\d+)', args.warning)
        if m:
            options["warn_mem"] = int(m.group(1))
            options["warn_swap"] = int(m.group(2))
    if args.critical is not None:
        m = re.match(r'(\d+),(\d+)', args.critical)
        if m:
            options["crit_mem"] = int(m.group(1))
            options["crit_swap"] = int(m.group(2))
    if (options["warn_mem"] is None or
        options["warn_swap"] is None or
        options["crit_mem"] is None or
        options["crit_swap"] is None) :
        print(parser.format_usage())
        sys.exit(3)
    return options

def read_proc_meminfo():
    look_for = ["MemTotal", "MemFree", "Buffers", "Cached", "SwapTotal", "SwapFree"]
    infos = {}
    with open('/proc/meminfo', 'r') as f:
        for l in f.readlines():
            m = re.match(r'(\w+):\s+(\d+)\s', l)
            if m and m.group(1) in look_for:
                infos[m.group(1)] = int(m.group(2))
                look_for.remove(m.group(1))
    infos["MemUsed"] = infos["MemTotal"] - infos["MemFree"] - infos["Buffers"] - infos["Cached"]
    infos["SwapUsed"] = infos["SwapTotal"] - infos["SwapFree"]
    return infos

def check_levels(meminfo, options):
    status = "ok"
    mem_percent = meminfo["MemUsed"] * 100 / meminfo["MemTotal"]
    if meminfo["SwapTotal"] != 0:
        swap_percent = meminfo["SwapUsed"] * 100 / meminfo["SwapTotal"]
    else:
        swap_percent = 100.0
    if (mem_percent >= options["warn_mem"] or
        swap_percent >= options["warn_swap"]):
        status = "warning"
    if (mem_percent >= options["crit_mem"] or
        swap_percent >= options["crit_swap"]):
        status = "critical"

    print(
        "{status}: mem is {mem_percent:.1f}% used, swap is {swap_percent:.1f}% used | "
        "mem_kb={mem_kb}KB "
        "swap_kb={swap_kb}KB "
        "mem_percent={mem_percent:.1f}%;{warn_mem_percent};{crit_mem_percent} "
        "swap_percent={swap_percent:.1f}%;{warn_swap_percent};{crit_swap_percent} "
        "".format(
            status=status,
            mem_kb=meminfo["MemUsed"],
            mem_percent=mem_percent,
            warn_mem_percent=options["warn_mem"],
            crit_mem_percent=options["crit_mem"],
            swap_kb=meminfo["SwapUsed"],
            swap_percent=swap_percent,
            warn_swap_percent=options["warn_swap"],
            crit_swap_percent=options["crit_swap"],
        )
    )
    return status

if __name__ == "__main__":
    options = get_options()
    meminfo = read_proc_meminfo()
    status = check_levels(meminfo, options)
    if status == "ok":
        sys.exit(0)
    elif status == "warning":
        sys.exit(1)
    elif status == "critical":
        sys.exit(2)
    else:
        sys.exit(3)
