#!/usr/bin/env pmpython
#
# Copyright (C) 2018-2022 Red Hat.
# Copyright (C) 2004-2016 Dag Wieers <dag@wieers.com>
#
# 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=missing-docstring,multiple-imports,invalid-name
# pylint: disable=too-many-lines,too-many-arguments,too-many-nested-blocks
# pylint: disable=line-too-long,bad-continuation,broad-except,bare-except
# pylint: disable=global-variable-undefined,global-at-module-level

# Common imports
from collections import OrderedDict
try:
    import configparser as ConfigParser
except ImportError:
    import ConfigParser
import termios, struct, atexit, fcntl, sched, errno, time, re, sys, os

# PCP Python PMAPI
from pcp import pmapi, pmconfig
from cpmapi import PM_CONTEXT_ARCHIVE, PM_CONTEXT_HOST, PM_CONTEXT_LOCAL
from cpmapi import PM_TYPE_32, PM_TYPE_U32, PM_TYPE_64, PM_TYPE_U64
from cpmapi import PM_TYPE_DOUBLE, PM_TYPE_FLOAT, PM_TIME_MIN, PM_TIME_HOUR
from cpmapi import PM_TIME_NSEC, PM_TIME_USEC, PM_TIME_MSEC
from cpmapi import PM_ERR_EOL, PM_IN_NULL, pmUsageMessage

if sys.version >= '3':
    long = int

def py3round(number, ndigits=None):
    if sys.version >= '3':
        if ndigits is not None:
            return round(number, ndigits)
        return round(number)
    ndigits = ndigits if ndigits is not None else 0
    if abs(round(number) - number) == 0.5:
        return 2.0 * round(number / 2.0, ndigits)
    return round(number, ndigits)

TIMEFMT = os.getenv('DSTAT_TIMEFMT') or '%d-%m %H:%M:%S'

NOUNITS = pmapi.pmUnits()

THEME = {'default': ''}

COLOR = {
    'black': '\033[0;30m',
    'darkred': '\033[0;31m',
    'darkgreen': '\033[0;32m',
    'darkyellow': '\033[0;33m',
    'darkblue': '\033[0;34m',
    'darkmagenta': '\033[0;35m',
    'darkcyan': '\033[0;36m',
    'gray': '\033[0;37m',

    'darkgray': '\033[90m',
    'red': '\033[1;31m',
    'green': '\033[1;32m',
    'yellow': '\033[1;33m',
    'blue': '\033[1;34m',
    'magenta': '\033[1;35m',
    'cyan': '\033[1;36m',
    'white': '\033[1;37m',

    'blackbg': '\033[40m',
    'redbg': '\033[41m',
    'greenbg': '\033[42m',
    'yellowbg': '\033[43m',
    'bluebg': '\033[44m',
    'magentabg': '\033[45m',
    'cyanbg': '\033[46m',
    'whitebg': '\033[47m',
}

ANSI = {
    'reset': '\033[0;0m',
    'bold': '\033[1m',
    'reverse': '\033[2m',
    'underline': '\033[4m',

    'clear': '\033[2J',
    'clearline': '\033[2K',
    'save': '\033[s',
    'restore': '\033[u',
    'nolinewrap': '\033[7l',

    'default': '\033[0;0m',
}

CHAR = {
    'pipe': '|',
    'colon': ':',
    'gt': '>',
    'space': ' ',
    'dash': '-',
    'plus': '+',
    'underscore': '_',
    'sep': ',',
}

class DstatTerminal:
    """Manage aspects of querying and manipulating the output terminal"""

    def __init__(self):
        self.termsize = None, 0
        try:
            termios.TIOCGWINSZ
        except:
            try:
                import curses # pylint: disable=import-outside-toplevel
                curses.setupterm()
                curses.tigetnum('lines')
                curses.tigetnum('cols')
            except:
                pass
            else:
                self.termsize = None, 2
        else:
            self.termsize = None, 1

    def get_size(self):
        """Return the dynamic terminal geometry"""
        if not self.termsize[0]:
            try:
                import curses # pylint: disable=import-outside-toplevel
                if self.termsize[1] == 1:
                    s = struct.pack('HHHH', 0, 0, 0, 0)
                    x = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, s)
                    return struct.unpack('HHHH', x)[:2]
                elif self.termsize[1] == 2:
                    curses.setupterm()
                    return curses.tigetnum('lines'), curses.tigetnum('cols')
                else:
                    self.termsize = (int(os.environ['LINES']), int(os.environ['COLUMNS']))
            except:
                self.termsize = 25, 80
        return self.termsize

    def get_color(self):
        """Return whether the system can use colors or not"""
        if sys.stdout.isatty():
            try:
                import curses # pylint: disable=import-outside-toplevel
                curses.setupterm()
                if curses.tigetnum('colors') < 0:
                    return False
            except ImportError:
                sys.stderr.write('Color support is disabled as python-curses is not installed.')
                return False
            except:
                sys.stderr.write('Color support is disabled as curses does not find terminal "%s".' % os.getenv('TERM'))
                return False
            return True
        return False

    @staticmethod
    def set_title(arguments, context):
        """ Write terminal title, if terminal (and shell?) is capable """
        if not sys.stdout.isatty():
            return
        term = os.getenv('TERM')
        if not term:
            return
        shell = os.getenv('SHELL')
        xshell = os.getenv('XTERM_SHELL')
        if not shell:
            shell = xshell
        if not shell or shell != '/bin/bash':
            return
        if term[:5] != 'xterm' and term[:6] != 'screen':
            return
        import getpass # pylint: disable=import-outside-toplevel
        user = getpass.getuser()
        host = context.pmGetContextHostName()
        host = host.split('.')[0]
        path = context.pmProgname()
        args = path + ' ' + ' '.join(arguments)
        sys.stdout.write('\033]0;(%s@%s) %s\007' % (user, host, args))

    @staticmethod
    def set_theme(blackonwhite):
        THEME['title'] = COLOR['darkblue']
        THEME['frame'] = COLOR['darkblue']
        THEME['default'] = ANSI['default']
        THEME['error'] = COLOR['white'] + COLOR['redbg']
        THEME['debug'] = COLOR['darkred']
        THEME['input'] = COLOR['darkgray']
        THEME['roundtrip'] = COLOR['darkblue']
        THEME['text_hi'] = COLOR['darkgray']
        THEME['unit_hi'] = COLOR['darkgray']
        if blackonwhite:
            THEME['subtitle'] = COLOR['darkcyan'] + ANSI['underline']
            THEME['done_lo'] = COLOR['black']
            THEME['done_hi'] = COLOR['darkgray']
            THEME['text_lo'] = COLOR['black']
            THEME['unit_lo'] = COLOR['black']
            THEME['unit_hi'] = COLOR['darkgray']
            THEME['colors_lo'] = (COLOR['darkred'], COLOR['darkmagenta'],
                    COLOR['darkgreen'], COLOR['darkblue'], COLOR['darkcyan'],
                    COLOR['black'], COLOR['red'], COLOR['green'])
            THEME['colors_hi'] = (COLOR['red'], COLOR['magenta'],
                    COLOR['green'], COLOR['blue'], COLOR['cyan'],
                    COLOR['darkgray'], COLOR['darkred'], COLOR['darkgreen'])
        else:
            THEME['subtitle'] = COLOR['blue'] + ANSI['underline']
            THEME['done_lo'] = COLOR['white']
            THEME['done_hi'] = COLOR['gray']
            THEME['text_lo'] = COLOR['gray']
            THEME['unit_lo'] = COLOR['darkgray']
            THEME['colors_lo'] = (COLOR['red'], COLOR['yellow'],
                    COLOR['green'], COLOR['blue'], COLOR['cyan'],
                    COLOR['white'], COLOR['darkred'], COLOR['darkgreen'])
            THEME['colors_hi'] = (COLOR['darkred'], COLOR['darkyellow'],
                    COLOR['darkgreen'], COLOR['darkblue'], COLOR['darkcyan'],
                    COLOR['gray'], COLOR['red'], COLOR['green'])


class DstatPlugin(object):
    """ Performance metrics group, for generating reports on one or
        more performance metrics (term/CSV) using pmConfig services.
    """
    def __init__(self, label):
        #sys.stderr.write("New plugin: %s\n" % label)
        self.name = label   # name of this plugin (config section)
        self.label = label
        self.instances = None
        self.unit = None
        self.type = None
        self.width = 5
        self.precision = None
        self.limit = None
        self.printtype = None
        self.colorstep = None
        self.grouptype = None   # flag for metric/inst group displays
        self.cullinsts = None   # regex pattern for dropped instances
        self.valuesets = {} # dict of sample values within one 'delay'
        self.metrics = []   # list of all metrics (states) for plugin
        self.names = []     # list of all column names for the plugin
        self.mgroup = []    # list of names of metrics in this plugin
        self.igroup = []    # list of names of this plugins instances

    def apply(self, metric):
        """ Apply default pmConfig list values where none exist as yet
            The indices are based on the extended pmConfig.metricspec.
            Note: we keep the plugin name for doing reverse DstatPlugin
            lookups from pmConfig metricspecs at fetch (sampling) time.
        """
        # slot zero is magic - holds metric name (during setup only)
        if metric[1] is None:
            metric[1] = self.label
        if metric[2] == []:
            metric[2] = self.instances
        if metric[3] is None:
            metric[3] = self.unit
        if metric[4] is None:
            metric[4] = self.type
        if metric[5] is None:
            metric[5] = self.width
        # slot six also magic - fetchgroup state
        if metric[7] is None:
            metric[7] = self.precision
        if metric[8] is None:
            metric[8] = self.limit
        if metric[9] is None:
            metric[9] = self.printtype
        if metric[10] is None:
            metric[10] = self.colorstep
        if metric[11] is None:
            metric[11] = self.grouptype
        if metric[12] is None:
            metric[12] = self.cullinsts
        metric[13] = self   # back-pointer to this from metric dict
        metric[14] = self.valuesets  # recent samples for averaging

    def prepare_grouptype(self, instlist, fullinst):
        """Setup a list of instances from the command line"""
        if fullinst:
            self.grouptype = 1
            instlist = []
        elif instlist is None:
            instlist = ['total']
        if 'total' in instlist:
            self.grouptype = 2 if (len(instlist) == 1) else 3
            instlist.remove('total') # remove command line arg
        else:
            self.grouptype = 1
        self.instances = instlist

    def statwidth(self):
        """Return complete width for this plugin"""
        if self.grouptype == 4:
            return self.width
        return len(self.names) * self.width + len(self.names) - 1

    def instlist(self):
        if self.grouptype == 3:
            return self.igroup + ['total']
        elif self.grouptype == 2:
            return ['total']
        return self.igroup

    def title(self):
        if self.grouptype is None or self.grouptype == 4:
            width = self.statwidth()
            label = self.label[0:width].center(width).replace(' ', '-')
            return THEME['title'] + label + THEME['default']
        ret = ''
        ilist = self.instlist()
        for i, name in enumerate(ilist):
            if name is None:
                continue
            name = self.label.replace('%I', name)
            width = self.statwidth()
            label = name[0:width].center(width).replace(' ', '-')
            ret = ret + THEME['title'] + label
            if i + 1 != len(ilist):
                if op.color:
                    ret = ret + THEME['frame'] + CHAR['dash'] + THEME['title']
                else:
                    ret = ret + CHAR['space']
        return ret + THEME['default']

    def subtitle(self):
        ret = ''
        if self.grouptype is None or self.grouptype == 4:
            for i, nick in enumerate(self.names):
                label = nick[0:self.width].center(self.width)
                ret = ret + THEME['subtitle'] + label + THEME['default']
                if self.grouptype == 4:
                    break
                if i + 1 != len(self.names):
                    ret = ret + CHAR['space']
            return ret
        ilist = self.instlist()
        for i, _ in enumerate(ilist):
            for j, nick in enumerate(self.names):
                label = nick[0:self.width].center(self.width)
                ret = ret + THEME['subtitle'] + label + THEME['default']
                if self.grouptype == 4:  # top
                    return ret
                if j + 1 != len(self.names):
                    ret = ret + CHAR['space']
            if i + 1 != len(ilist):
                ret = ret + THEME['frame'] + CHAR['colon']
        return ret

    def csvtitle(self):
        if self.grouptype is None or self.grouptype == 4:
            ret = '"' + self.label
            if self.grouptype is None:
                return ret + '"' + CHAR['sep'] * (len(self.names) - 1)
            return ret + ' ' + self.na                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     