#!/usr/bin/python3

import os
from time import sleep
from ctypes import CDLL
from argparse import ArgumentParser


###############################################################################


def cgroup2_root():
    """
    """
    with open(mounts) as f:
        for line in f:
            if cgroup2_separator in line:
                return line.partition(cgroup2_separator)[0].partition(' ')[2]


def mlockall():
    """
    """

    MCL_CURRENT = 1
    MCL_FUTURE = 2
    MCL_ONFAULT = 4

    libc = CDLL('libc.so.6', use_errno=True)

    result = libc.mlockall(
        MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT
    )
    if result != 0:
        result = libc.mlockall(
            MCL_CURRENT | MCL_FUTURE
        )
        if result != 0:
            pass
        else:
            pass
    else:
        pass


def psi_file_mem_to_metrics(psi_path):
    """
    """

    with open(psi_path) as f:
        psi_list = f.readlines()
    # print(psi_list)
    some_list, full_list = psi_list[0].split(' '), psi_list[1].split(' ')
    # print(some_list, full_list)
    some_avg10 = some_list[1].split('=')[1]
    some_avg60 = some_list[2].split('=')[1]
    some_avg300 = some_list[3].split('=')[1]

    full_avg10 = full_list[1].split('=')[1]
    full_avg60 = full_list[2].split('=')[1]
    full_avg300 = full_list[3].split('=')[1]

    return (some_avg10, some_avg60, some_avg300,
            full_avg10, full_avg60, full_avg300)


def psi_file_cpu_to_metrics(psi_path):
    """
    """

    with open(psi_path) as f:
        psi_list = f.readlines()
    # print(psi_list)
    some_list = psi_list[0].split(' ')
    # print(some_list, full_list)
    some_avg10 = some_list[1].split('=')[1]
    some_avg60 = some_list[2].split('=')[1]
    some_avg300 = some_list[3].split('=')[1]

    return (some_avg10, some_avg60, some_avg300)


def log(*msg):
    """
    """

    print(*msg)

    if separate_log:
        logging.info(*msg)

###############################################################################


parser = ArgumentParser()

parser.add_argument(
    '-t',
    '--target',
    help="""target (cgroup2 or SYTSTEM_WIDE)""",
    default='SYSTEM_WIDE',
    type=str
)


parser.add_argument(
    '-p',
    '--period',
    help="""period in sec""",
    default=2,
    type=float
)


parser.add_argument(
    '-l',
    '--log',
    help="""path to log file""",
    default=None,
    type=str
)


args = parser.parse_args()

target = args.target

period = args.period

log_file = args.log


if log_file is None:
    separate_log = False
else:
    separate_log = True
    import logging


def sleeeep():
    try:
        sleep(period)
    except KeyboardInterrupt:
        log('Exit')
        exit()


###############################################################################

if separate_log:
    logging.basicConfig(
        filename=log_file,
        level=logging.INFO,
        format="%(asctime)s: %(message)s")

###############################################################################


log('target:    {}'.format(target))
log('period:    {}'.format(period))
if log_file is not None:
    log('log file:  {}'.format(log_file))


if not os.path.exists('/proc/pressure'):
    log('ERROR: /proc/pressure does not exist, PSI is not supported, exit')
    exit()


if target == 'SYSTEM_WIDE':

    cpu_file = "/proc/pressure/cpu"
    memory_file = "/proc/pressure/memory"
    io_file = "/proc/pressure/io"

else:

    mounts = '/proc/mounts'
    cgroup2_separator = ' cgroup2 rw,'
    cgroup2_mountpoint = cgroup2_root()

    if cgroup2_mountpoint is None:

        log('ERROR: unified cgroup hierarchy is not mounted, exit')
        exit()

    else:

        log('cgroup2 mountpoint: {}'.format(cgroup2_mountpoint))

    cpu_file = cgroup2_mountpoint + target + "/cpu.pressure"
    memory_file = cgroup2_mountpoint + target + "/memory.pressure"
    io_file = cgroup2_mountpoint + target + "/io.pressure"


mlockall()


log('Starting psi-monitor, target: {}, period: {}'.format(target, period))
log('----------------------------------------------------------------------'
    '--------------------------------------------')
log(' some cpu pressure   || some memory pressure | full memory pressure ||'
    '  some io pressure    |  full io pressure')
log('---------------------||----------------------|----------------------||'
    '----------------------|---------------------')
log(' avg10  avg60 avg300 ||  avg10  avg60 avg300 |  avg10  avg60 avg300 ||'
    '  avg10  avg60 avg300 |  avg10  avg60 avg300')
log('------ ------ ------ || ------ ------ ------ | ------ ------ ------ ||'
    ' ------ ------ ------ | ------ ------ ------')


while True:

    if not os.path.exists(cpu_file):
        log('ERROR: cpu pressure file does not exist:    {}'.format(cpu_file))
        sleeeep()
        continue

    if not os.path.exists(memory_file):
        log('ERROR: memory pressure file does not exist: {}'.format(
            memory_file))
        sleeeep()
        continue

    if not os.path.exists(io_file):
        log('ERROR: io pressure file does not exist:     {}'.format(cpu_file))
        sleeeep()
        continue

    (c_some_avg10, c_some_avg60, c_some_avg300
     ) = psi_file_cpu_to_metrics(cpu_file)

    (m_some_avg10, m_some_avg60, m_some_avg300,
     m_full_avg10, m_full_avg60, m_full_avg300
     ) = psi_file_mem_to_metrics(memory_file)

    (i_some_avg10, i_some_avg60, i_some_avg300,
     i_full_avg10, i_full_avg60, i_full_avg300
     ) = psi_file_mem_to_metrics(io_file)

    log('{} {} {} || {} {} {} | {} {} {} || {} {} {} | {} {} {}'.format(

        c_some_avg10.rjust(6),
        c_some_avg60.rjust(6),
        c_some_avg300.rjust(6),

        m_some_avg10.rjust(6),
        m_some_avg60.rjust(6),
        m_some_avg300.rjust(6),
        m_full_avg10.rjust(6),
        m_full_avg60.rjust(6),
        m_full_avg300.rjust(6),

        i_some_avg10.rjust(6),
        i_some_avg60.rjust(6),
        i_some_avg300.rjust(6),
        i_full_avg10.rjust(6),
        i_full_avg60.rjust(6),
        i_full_avg300.rjust(6)

    ))

    sleeeep()
