# Copyright 2026 SigmaSRC, Inc. All Rights Reserved.

"""Gather the ram by parsing the output from top (or other system commands)."""

if __name__ == "__main__":
    import os
    import sys
    try:
        import sigma
    except ImportError:
        sys.path.append(os.path.join(os.pardir, os.pardir))
    del sys
    del os

import os
import re
import platform
import logging

logger = logging.getLogger("gather.gather_ram")

_linuxline = re.compile(r"^Mem:\s*(\d+)[kK] \S+,\s*(\d+)[kK] used,\s*(\d+)[kK] free")

_linuxline2 = re.compile(r"^([kKmMgGtTpPeE])iB Mem : \s+([0-9.]+) \S+, \s+([0-9.]+) \S+, \s+([0-9.]+) \S+, .+")

_linuxlines = [_linuxline, _linuxline2, ]

assert _linuxline.match("Mem:   512312k av,  501528k used,   10784k free,       0k shrd,   36036k buff"), "_linuxline"
assert _linuxline.match("Mem:   512312K total,  501528K used,   10784K free,       0K shrd,   36036K buff"), "_linuxline"

assert _linuxline2.match("MiB Mem :  32148.2 total,   5998.2 free,  11037.0 used,  15113.0 buff/cache"), "_linuxline2"
assert _linuxline2.match("KiB Mem :  5946092 total,  1603344 free,  1263772 used,  3078976 buff/cache"), "_linuxline2"

_solarisline = re.compile(r"^Memory:\s*(\d+)M real,\s*(\d+[MK]) free")
assert _solarisline.match("Memory: 128M real, 73M free, 38M swap in use, "
                          "551M swap free"), "_solarisline"

_solarisline1 = re.compile(r"^Memory size: (\d+)\s+Megabytes")
assert _solarisline1.match("Memory size: 1024 Megabytes"), "_solarisline1"

_solarisline2 = re.compile(r"^Memory size: (\d+)\s+Gigabytes")
assert _solarisline2.match("Memory size: 1024 Gigabytes"), "_solarisline2"

_solarisline3 = re.compile(r"^\s*\d+\s+\d+\s+\d+\s+\d+\s+(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+")
assert _solarisline3.match(" 0 0 0 4863680 739544 0   6  0  0  0  0  0  0  0  0  0  361   56  167  0  1 99"), "_solarisline3"

_hpuxline = re.compile(r"\s*memory\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%")
assert _hpuxline.match("memory  1037112  563528  473584   54%"), "_hpuxline"

r"""
_hpuxline = re.compile(
    r"\s+Physical:\s+(\d+)\s+Kbytes,\s+"
    r"lockable:\s+(\d+)\s+Kbytes,\s+"
    r"available:\s+(\d+)\s+Kbytes\s*$")
assert _hpuxline.match(
"    Physical: 1037112 Kbytes, lockable: 541004 Kbytes, available: 679684 Kbytes\n"), "_hpuxline"
"""
_osxline = re.compile(r"^PhysMem:.*, *([\d]+(?:\.\d*)?[KMGT]?) used, *([\d]+(?:\.\d*)?[KMGT]?) free")

def do_aix(table):
    pagesize = 4096 # XXX Or get it from /usr/bin/pagesize ?
    regex = re.compile(r"\s*(\d+)\s+(memory|free)\s+pages")
    pipe = os.popen("/usr/bin/vmstat -v")
    total = used = free = None
    try:
        for line in pipe:
            m = regex.match(line)
            if m:
                pages, type = m.group(1, 2)
                bytes = pagesize * int(pages)
                if type == "memory":
                    total = bytes
                elif type == "free":
                    free = bytes
    finally:
        pipe.close()
    if total and free:
        used = total - free
    table.add([total, used, free])

def Gather(collector):
    basicos = platform.uname()[0]
    if basicos in [ "Microsoft", "Microsoft Windows" ]:
        basicos = "Windows"
    if basicos == "SunOS":
        # Should find .../thirdparty/bin/top first in $PATH
        #_command = "top -b -n 1"
        _command = "prtconf | grep Memory"
    elif basicos == "HP-UX":
        #_command = "/usr/sbin/dmesg"
        _command = "swapinfo -M"
    elif basicos == "Linux":
        _command = "/usr/bin/top -b -n 1"
    elif basicos == "Darwin":
        _command = "/usr/bin/top -l 1 -n 0"
    elif basicos == "AIX":
        pass
    elif basicos != "Windows":
        raise Exception("Can't compute ram on this operating system: %r" %
                        basicos)

    t = collector.table("ram", ["total", "used", "free"])

    if basicos == "Windows":
        from win32com.client import GetObject
        freemem = totalmem = usedmem = 0
        try:
            objItems = GetObject("WinMgMts://./root/cimv2").InstancesOf("Win32_OperatingSystem")
            for item in objItems:
                freemem = int(item.Properties_("FreePhysicalMemory").Value)
            # FreePhysicalMemory is in KBytes
            freemem = freemem * 1024
            objItems = GetObject("WinMgMts://./root/cimv2").InstancesOf("Win32_ComputerSystem")
            for item in objItems:
                totalmem = int(item.Properties_("TotalPhysicalMemory").Value)
            t.add([totalmem, totalmem - freemem, freemem])
        except:
            logger.exception("ram gather failed")
    elif basicos == "AIX":
        do_aix(t)
    else:
        file = os.popen(_command + " 2>&1", "r")
        try:
            for line in file:
                if basicos == "SunOS":
                    total, free, used = 0, 0, 0
                    match = _solarisline1.match(line)
                    if match:
                        total = match.group(1)
                        meg = 1024 * 1024
                        total = meg*int(total)
                    match = _solarisline2.match(line)
                    if match:
                        total = match.group(1)
                        gig = 1024 * 1024 * 1024
                        total = gig*int(total)
                    file2 = os.popen("vmstat 1 2" + " 2>&1", "r")
                    try:
                        for aline in file2:
                            match = _solarisline3.match(aline)
                            if match:
                                free = match.group(1)
                                kilo = 1024
                                free = kilo*int(free)
                    finally:
                        file2.close()
                    used = int(total) - int(free)
                    t.add([total, used, free])
                    break
                elif basicos == "HP-UX":
                    match = _hpuxline.match(line)
                    if match:
                        (total, lockable, available) = match.group(1, 2, 3)
                        k = 1024
                        total = int(total) * k
                        free = int(available) * k
                        used = total - free
                        t.add([total, used, free])
                        break
                elif basicos == "Darwin":
                    match = _osxline.match(line)
                    if match:
                        used, free = map(tobytes, match.group(1, 2))
                        total = used + free
                        t.add([total, used, free])
                elif basicos == "Linux":
                    for m in _linuxlines:
                        match = m.match(line)
                        if match:
                            if len(match.groups()) == 3:
                                (total, used, free) = match.group(1, 2, 3)
                                k = 1024
                                t.add([int(total) * k, int(used) * k, int(free) * k])
                            else:
                                (scale, total, free, used) = match.group(1, 2, 3, 4)
                                k = expomap.get(scale)
                                t.add([int(round(float(total) * k)), int(round(float(used) * k)),
                                       int(round(float(free) * k))])
                            break

                else:
                    raise Exception("Can't parse top output for os %r" %
                                    basicos)
        finally:
            file.close()

#IEEE 1541 recommends:
#
# a set of units to refer to quantities used in digital electronics and computing:
#
# bit (symbol 'b'), a binary digit;
# byte (symbol 'B'), a set of adjacent bits (usually, but not necessarily, eight) operated on as a group;
# octet (symbol 'o'), a group of eight bits;
#
#a set of prefixes to indicate binary multiples of the aforesaid units:
#
# kibi (symbol 'Ki'), 2**10 = 1024;
# mebi (symbol 'Mi'), 2**20 = 1048576;
# gibi (symbol 'Gi'), 2**30 = 1073741824;
# tebi (symbol 'Ti'), 2**40 = 1099511627776;
# pebi (symbol 'Pi'), 2**50 = 1125899906842624;
# exbi (symbol 'Ei'), 2**60 = 1152921504606846976;

expomap = {"K": 2**10, "M": 2**20, "G": 2**30, "T": 2**40, "P": 2**50, "E": 2**60}

def tobytes(s):
    head, tail = s[:-1], s[-1]
    return int(float(head) * expomap.get(tail, 1))

def _test():
    from sigma.gather import gathering
    gathering.test(Gather)

if __name__ == "__main__":
    _test()
