#!/usr/bin/env pmpython
#
# Copyright (c) 2023 Oracle and/or its affiliates.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# 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.
#
# pylint: disable=bad-whitespace,too-many-arguments,too-many-lines, bad-continuation
# pylint: disable=redefined-outer-name,unnecessary-lambda
#

import sys
import time
import signal
from pcp import pmapi, pmcc
from cpmapi import PM_CONTEXT_ARCHIVE

METRICS = ["mem.physmem",
            "mem.util.free",
            "mem.util.available",
            "mem.util.bufmem",
            "mem.util.cached",
            "mem.util.swapCached",
            "mem.util.active",
            "mem.util.inactive",
            "mem.util.active_anon",
            "mem.util.inactive_anon",
            "mem.util.active_file",
            "mem.util.inactive_file",
            "mem.util.unevictable",
            "mem.util.mlocked",
            "mem.util.swapTotal",
            "mem.util.swapFree",
            "mem.util.dirty",
            "mem.util.writeback",
            "mem.util.anonpages",
            "mem.util.mapped",
            "mem.util.shared",
            "mem.util.slab",
            "mem.util.slabReclaimable",
            "mem.util.slabUnreclaimable",
            "mem.util.kernelStack",
            "mem.util.pageTables",
            "mem.util.NFS_Unstable",
            "mem.util.bounce",
            "mem.vmstat.nr_writeback_temp",
            "mem.util.commitLimit",
            "mem.util.committed_AS",
            "mem.util.vmallocTotal",
            "mem.util.vmallocUsed",
            "mem.util.vmallocChunk",
            "mem.util.corrupthardware",
            "mem.util.anonhugepages",
            "mem.vmstat.nr_shmem_hugepages",
            "mem.vmstat.nr_shmem_pmdmapped",
            "mem.zoneinfo.nr_free_cma",
            "mem.util.hugepagesTotal",
            "mem.util.hugepagesFree",
            "mem.util.hugepagesRsvd",
            "mem.util.hugepagesSurp",
            "hinv.hugepagesize",
            "mem.util.directMap4k",
            "mem.util.directMap2M",
            "mem.util.directMap1G"]

METRICS_DESC = ["MemTotal",
                "MemFree",
                "MemAvailable",
                "Buffers",
                "Cached",
                "SwapCached",
                "Active",
                "Inactive",
                "Active(anon)",
                "Inactive(anon)",
                "Active(file)",
                "Inactive(file)",
                "Unevictable",
                "Mlocked",
                "SwapTotal",
                "SwapFree",
                "Dirty",
                "Writeback",
                "AnonPages",
                "Mapped",
                "Shmem",
                "Slab",
                "SReclaimable",
                "SUnreclaim",
                "KernelStack",
                "PageTables",
                "NFS_Unstable",
                "Bounce",
                "WritebackTmp",
                "CommitLimit",
                "Committed_AS",
                "VmallocTotal",
                "VmallocUsed",
                "VmallocChunk",
                "HardwareCorrupted",
                "AnonHugePages",
                "ShmemHugePages",
                "ShmemPmdMapped",
                "CmaFree",
                "HugePages_Total_NO_kb",
                "HugePages_Free_NO_kb",
                "HugePages_Rsvd_NO_kb",
                "HugePages_Surp_NO_kb",
                "Hugepagesize",
                "DirectMap4k",
                "DirectMap2M",
                "DirectMap1G"]

SYS_METRICS = ["kernel.uname.sysname",
                "kernel.uname.release",
                "kernel.uname.nodename",
                "kernel.uname.machine",
                "hinv.ncpu"]

ALL_METRICS = METRICS + SYS_METRICS

class MeminfoReport(pmcc.MetricGroupPrinter):
    def __init__(self, opts):
        self.opts = opts
        self.Machine_info_count = 0

    def __get_ncpu(self, group):
        return group['hinv.ncpu'].netValues[0][2]

    def __print_machine_info(self, group, context):
        timestamp = context.pmLocaltime(group.timestamp.tv_sec)
        # Please check strftime(3) for different formatting options.
        # Also check TZ and LC_TIME environment variables for more
        # information on how to override the default formatting of
        # the date display in the header
        time_string = time.strftime("%x", timestamp.struct_time())
        header_string = ''
        header_string += group['kernel.uname.sysname'].netValues[0][2] + '  '
        header_string += group['kernel.uname.release'].netValues[0][2] + '  '
        header_string += '(' + group['kernel.uname.nodename'].netValues[0][2] + ')  '
        header_string += time_string + '  '
        header_string += group['kernel.uname.machine'].netValues[0][2] + '  '

        print("%s  (%s CPU)" % (header_string, self.__get_ncpu(group)))

    def getMetricName(self, idx):
        metric_name = ""
        units = ""
        if METRICS_DESC[idx][-6:] == "_NO_kb":
            metric_name = METRICS_DESC[idx][:-6]
        else:
            metric_name = METRICS_DESC[idx]
            units = "kB"
        return metric_name, units

    def report(self, manager):
        group = manager["sysinfo"]
        try:
            if not self.Machine_info_count:
                self.__print_machine_info(group, manager)
                self.Machine_info_count = 1
        except IndexError:
            # missing some metrics
            return

        group = manager["meminfo"]

        self.opts.pmGetOptionSamples()

        t_s = group.contextCache.pmLocaltime(int(group.timestamp))
        time_string = time.strftime(MeminfoOptions.timefmt, t_s.struct_time())
        print("Timestamp".ljust(18) + ": " + time_string)

        idx = 0
        for metric in METRICS:
            try:
                val = group[metric].netValues[0][2]
            except IndexError:
                metric_name, units = self.getMetricName(idx)
                print("%-17s : NA"%(metric_name))

                idx += 1
                continue

            metric_name, units = self.getMetricName(idx)
            print("%-17s : %s %s"%(metric_name, val, units))

            idx += 1
        print("")

        if MeminfoOptions.context is not PM_CONTEXT_ARCHIVE and self.opts.pmGetOptionSamples() is None:
            sys.exit(0)

class MeminfoOptions(pmapi.pmOptions):
    context = None
    timefmt = "%m/%d/%Y %H:%M:%S"

    def __init__(self):
        pmapi.pmOptions.__init__(self, "a:s:S:T:z:A:t:")
        self.pmSetLongOptionStart()
        self.pmSetLongOptionFinish()
        self.pmSetLongOptionHelp()

if __name__ == '__main__':
    try:
        opts = MeminfoOptions()
        mngr = pmcc.MetricGroupManager.builder(opts,sys.argv)
        MeminfoOptions.context = mngr.type

        missing = mngr.checkMissingMetrics(ALL_METRICS)
        if missing is not None:
            sys.stderr.write('Error: not all required metrics are available\nMissing: %s\n' % (missing))
            sys.exit(1)

        mngr["meminfo"] = METRICS
        mngr["sysinfo"] = SYS_METRICS
        mngr.printer = MeminfoReport(opts)
        sts = mngr.run()
        sys.exit(sts)
    except IOError:
        signal.signal(signal.SIGPIPE,signal.SIG_DFL)
    except pmapi.pmErr as error:
        sys.stderr.write("%s %s\n"%(error.progname(), error.message()))
    except pmapi.pmUsageErr as usage:
        usage.message()
        sys.exit(1)
    except KeyboardInterrupt:
        pass
