#!/usr/bin/env pmpython
#
# Copyright (c) 2022 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
# pylint: disable=redefined-outer-name,unnecessary-lambda
#


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

process_state_info = {}

PSSTAT_METRICS = ['kernel.uname.nodename', 'kernel.uname.release', 'kernel.uname.sysname',
                  'kernel.uname.machine', 'hinv.ncpu', 'proc.psinfo.pid', 'proc.psinfo.guest_time',
                  'proc.psinfo.utime', 'proc.psinfo.ppid', 'proc.psinfo.rt_priority', 'proc.psinfo.rss',
                  'proc.id.uid_nm', 'proc.psinfo.stime', 'kernel.all.boottime', 'proc.psinfo.sname',
                  'proc.psinfo.start_time', 'proc.psinfo.vsize', 'proc.psinfo.priority',
                  'proc.psinfo.nice', 'proc.psinfo.wchan_s', 'proc.psinfo.psargs', 'proc.psinfo.cmd',
                  'proc.psinfo.ttyname', 'mem.physmem', 'proc.psinfo.policy']

SCHED_POLICY = ['NORMAL', 'FIFO', 'RR', 'BATCH', '', 'IDLE', 'DEADLINE']


class StdoutPrinter:
    def Print(self, args):
        print(args)


class NoneHandlingPrinterDecorator:
    def __init__(self, printer):
        self.printer = printer

    def Print(self, args):
        new_args = args.replace('None', '?')
        self.printer.Print(new_args)


# After fetching non singular metric values, create a mapping of instance id
# to instance value rather than instance name to instance value.
# The reason is, in PCP, instance names require a separate pmGetIndom() request
# and some of the names may not be available.
class ReportingMetricRepository:
    def __init__(self, group):
        self.group = group
        self.current_cached_values = {}
        self.previous_cached_values = {}

    def __fetch_current_values(self, metric, instance):
        if instance:
            return dict(map(lambda x: (x[0].inst, x[2]), self.group[metric].netValues))
        else:
            return self.group[metric].netValues[0][2]

    def __fetch_previous_values(self, metric, instance):
        if instance:
            return dict(map(lambda x: (x[0].inst, x[2]), self.group[metric].netPrevValues))
        else:
            return self.group[metric].netPrevValues[0][2]

    def current_value(self, metric, instance):
        if not metric in self.group:
            return None
        if instance:
            if self.current_cached_values.get(metric, None) is None:
                lst = self.__fetch_current_values(metric, instance)
                self.current_cached_values[metric] = lst

            return self.current_cached_values[metric].get(instance, None)
        else:
            if self.current_cached_values.get(metric, None) is None:
                self.current_cached_values[metric] = self.__fetch_current_values(metric, instance)
            return self.current_cached_values.get(metric, None)

    def previous_value(self, metric, instance):
        if not metric in self.group:
            return None
        if instance:
            if self.previous_cached_values.get(metric, None) is None:
                lst = self.__fetch_previous_values(metric, instance)
                self.previous_cached_values[metric] = lst

            return self.previous_cached_values[metric].get(instance, None)
        else:
            if self.previous_cached_values.get(metric, None) is None:
                self.previous_cached_values[metric] = self.__fetch_previous_values(metric, instance)
            return self.previous_cached_values.get(metric, None)

    def current_values(self, metric_name):
        if self.group.get(metric_name, None) is None:
            return None
        if self.current_cached_values.get(metric_name, None) is None:
            self.current_cached_values[metric_name] = self.__fetch_current_values(metric_name, True)
        return self.current_cached_values.get(metric_name, None)

    def previous_values(self, metric_name):
        if self.group.get(metric_name, None) is None:
            return None
        if self.previous_cached_values.get(metric_name, None) is None:
            self.previous_cached_values[metric_name] = self.__fetch_previous_values(metric_name, True)
        return self.previous_cached_values.get(metric_name, None)


class ProcessFilter:
    def __init__(self, options):
        self.options = options

    def filter_processes(self, processes):
        return filter(lambda p: self.__predicate(p), processes)

    def __predicate(self, process):
        if self.options.filter_flag:
            return bool(self.__matches_process_username(process)
                        and self.__matches_process_pid(process)
                        and self.__matches_process_name(process)
                        and self.__matches_process_ppid(process))
        else:
            return True

    def __matches_process_username(self, process):
        if self.options.username_filter_flag is True and self.options.filtered_process_user is not None:
            return process.user_name().strip() == self.options.filtered_process_user.strip()
        else:
            return True

    def __matches_process_pid(self, process):
        if self.options.pid_filter_flag:
            if self.options.pid_list is not None:
                pid = int(process.pid())
                return bool(pid in self.options.pid_list)
        return True

    def __matches_process_ppid(self, process):
        if self.options.ppid_filter_flag:
            if self.options.ppid_list is not None:
                ppid = int(process.ppid())
                return bool(ppid in self.options.ppid_list)
        return True

    def __matches_process_name(self, process):
        name = process.process_name()
        if self.options.command_filter_flag is True and self.options.command_list is not None and name is not None:
            return name.strip() in self.options.command_list
        else:
            return True


class ProcessStatusUtil:
    def __init__(self, instance, manager, delta_time, metrics_repository):
        self.instance = instance
        self.manager = manager
        self.__delta_time = delta_time
        self.__metric_repository = metrics_repository

    def pid(self):
        data = str(self.__metric_repository.current_value('proc.psinfo.pid', self.instance))
        if len(str(data)) < 8:
            whitespace = 8 - len(str(data))
            res = data.ljust(whitespace + len(str(data)), ' ')
            return res
        else:
            return data

    def ppid(self):
        data = str(self.__metric_repository.current_value('proc.psinfo.ppid', self.instance))
        if len(str(data)) < 8:
            whitespace = 8 - len(str(data))
            res = data.ljust(whitespace + len(str(data)), ' ')
            return res
        else:
            return data

    def user_name(self):
        data = self.__metric_repository.current_value('proc.id.uid_nm', self.instance)[:10]
        if len(data) < 10:
            whitespace = 10 - len(data)
            res = data.ljust(whitespace + len(data), ' ')
            return res
        else:
            return data

    def process_name(self):
        try:
            data = self.__metric_repository.current_value('proc.psinfo.cmd', self.instance)[:20]
            if len(data) < 20:
                whitespace = 20 - len(data)
                res = data.ljust(whitespace + len(data), ' ')
                return res
            else:
                return data
        except TypeError:
            data = '-'
            return data

    def process_name_with_args(self,flag = False):
        if flag is True:
            data = self.__metric_repository.current_value('proc.psinfo.psargs', self.instance)
        else:
            data = self.__metric_repository.current_value('proc.psinfo.psargs', self.instance)[:30]
        if len(data) < 30:
            whitespace = 30 - len(data)
            res = data.ljust(whitespace + len(data), ' ')
            return res
        else:
            return data

    def process_name_with_args_last(self):
        return self.process_name_with_args(True)
    def vsize(self):
        return self.__metric_repository.current_value('proc.psinfo.vsize', self.instance)

    def rss(self):
        return self.__metric_repository.current_value('proc.psinfo.rss', self.instance)

    def mem(self):
        total_mem = self.__metric_repository.current_value('mem.physmem', None)
        rss = self.__metric_repository.current_value('proc.psinfo.rss', self.instance)
        if total_mem is not None and rss is not None:
            return float("%.2f" % (100 * float(rss) / total_mem))
        else:
            return None

    def s_name(self):
        return self.__metric_repository.current_value('proc.psinfo.sname', self.instance)

    def cpu_number(self):
        return self.__metric_repository.current_value('proc.psinfo.processor', self.instance)

    def system_percent(self):
        c_systemtime = self.__metric_repository.current_value('proc.psinfo.stime', self.instance)
        p_systemtime = self.__metric_repository.previous_value('proc.psinfo.stime', self.instance)
        if c_systemtime is not None and p_systemtime is not None:
            percent_of_time = 100 * float(c_systemtime - p_systemtime) / float(1000 * self.__delta_time)
            return float("%.2f" % percent_of_time)
        else:
            return None

    def wchan_s(self):
        process = self.__metric_repository.current_value('proc.psinfo.wchan_s', self.instance)
        if process is None:
            process = '-'
            return process
        elif len(process) < 30:
            whitespace = 30 - len(process)
            res = process.ljust(whitespace + len(process), ' ')
            return res
        return process[:30]

    def priority(self):
        return self.__metric_repository.current_value('proc.psinfo.priority', self.instance)

    def user_percent(self):
        c_usertime = self.__metric_repository.current_value('proc.psinfo.utime', self.instance)
        p_usertime = self.__metric_repository.previous_value('proc.psinfo.utime', self.instance)
        if c_usertime is not None and p_usertime is not None:
            percent_of_time = 100 * float(c_usertime - p_usertime) / float(1000 * self.__delta_time)
            return float("%.2f" % percent_of_time)
        else:
            return None

    def guest_percent(self):
        c_guesttime = self.__metric_repository.current_value('proc.psinfo.guest_time', self.instance)
        p_guesttime = self.__metric_repository.previous_value('proc.psinfo.guest_time', self.instance)
        if c_guesttime is not None and p_guesttime is not None:
            percent_of_time = 100 * float(c_guesttime - p_guesttime) / float(1000 * self.__delta_time)
            return float("%.2f" % percent_of_time)
        else:
            return None

    def total_percent(self):
        if self.user_percent() is not None and self.guest_percent() is not None and self.system_percent() is not None:
            return float("%.2f" % (self.user_percent() + self.guest_percent() + self.system_percent()))
        else:
            return None

    def stime(self):
        c_systime = self.__metric_repository.current_value('proc.psinfo.stime', self.instance)
        p_systime = self.__metric_repository.previous_value('proc.psinfo.stime', self.instance)
        # sometimes the previous_value seems to be Nonetype, not sure why
        if p_systime is None:  # print a '?' here
            return '?'
        else:
            return c_systime - p_systime

    def start(self):
        s_time = self.__metric_repository.current_value('proc.psinfo.start_time', self.instance)
        group = self.manager['psstat']
        kernel_boottime = group['kernel.all.boottime'].netValues[0][2]
        ts = group.contextCache.pmLocaltime(int(kernel_boottime + (s_time / 1000)))
        if group.timestamp.tv_sec - (kernel_boottime + s_time / 1000) >= 24*60*60:
            # started one day or more ago, use MmmDD HH:MM
            return time.strftime("%b%d %H:%M", ts.struct_time())
        else:
            # started less than one day ago, use HH:MM:SS
            return time.strftime("%H:%M:%S", ts.struct_time())


    def total_time(self):
        c_usertime = self.__metric_repository.current_value('proc.psinfo.stime', self.instance)
        p_guesttime = self.__metric_repository.previous_value('proc.psinfo.utime', self.instance)
        timefmt = "%H:%M:%S"
        if c_usertime and p_guesttime is not None:
            total_time = (c_usertime / 1000) + (p_guesttime / 1000)
        else:
            total_time = 0
        return time.strftime(timefmt, time.gmtime(total_time))

    def tty_name(self):
        return self.__metric_repository.current_value('proc.psinfo.ttyname', self.instance)

    def user_id(self):
        return self.__metric_repository.current_value('proc.id.uid', self.instance)

    def start_time(self):
        return self.__metric_repository.current_value('proc.psinfo.start_time', self.instance)

    def func_state(self):
        s_name = self.__metric_repository.current_value('proc.psinfo.sname', self.instance)
        if s_name == 'R':
            return 'N/A'
        elif s_name is None:
            return '?'
        else:
            return self.wchan_s()

    def policy(self):
        policy_int = self.__metric_repository.current_value('proc.psinfo.policy', self.instance)
        if policy_int is not None and policy_int <= len(SCHED_POLICY):
            # return policy_int
            return SCHED_POLICY[policy_int]
        return None


PIDINFO_PAIR = {"%cpu": ('%CPU', ProcessStatusUtil.system_percent),
                "%mem": ('%MEM', ProcessStatusUtil.mem),
                "start": ("START\t", ProcessStatusUtil.start),
                "time": ("TIME\t", ProcessStatusUtil.total_time),
                "cls": ("CLS", ProcessStatusUtil.policy),
                "cmd": ("Command\t\t\t", ProcessStatusUtil.process_name),
                "args": ("Command\t\t\t", ProcessStatusUtil.process_name_with_args),
                "args_last": ("Command\t\t\t", ProcessStatusUtil.process_name_with_args_last),
                "pid": ("PID\t", ProcessStatusUtil.pid),
                "ppid": ("PPID\t", ProcessStatusUtil.ppid),
                "pri": ("PRI", ProcessStatusUtil.priority),
                "state": ("S", ProcessStatusUtil.s_name),
                "rss": ("RSS", ProcessStatusUtil.rss),
                "rtprio": ("RTPRIO", ProcessStatusUtil.priority),
                "tty": ("TTY\t", ProcessStatusUtil.tty_name),
                "pname": ("Pname\t\t", ProcessStatusUtil.process_name),
                "vsize": ("VSZ", ProcessStatusUtil.vsize),
                "uname": ("USER\t", ProcessStatusUtil.user_name),
                "uid": ("USER_ID", ProcessStatusUtil.user_id),
                "wchan": ("WCHAN\t\t\t", ProcessStatusUtil.wchan_s)}


class ProcessStatus:
    def __init__(self, manager, metric_repository):
        self.__manager = manager
        self.__metric_repository = metric_repository

    def get_processes(self, delta_time):
        return map(lambda pid:
                   (ProcessStatusUtil(pid, self.__manager, delta_time, self.__metric_repository)), self.__pids())

    def __pids(self):
        pid_dict = self.__metric_repository.current_values('proc.psinfo.pid')
        return sorted(pid_dict.values())


class DynamicProcessReporter:
    def __init__(self, process_report, process_filter, delta_time, printer, processStatOptions):
        self.delta_time = delta_time
        self.process_report = process_report
        self.process_filter = process_filter
        self.printer = printer
        self.processStatOptions = processStatOptions

    def _is_last_and_args(self, key):
        return (key == "args") and \
        self.processStatOptions.colum_list.index(key) == len(self.processStatOptions.colum_list) - 1
    def print_report(self, timestamp, header_indentation, value_indentation):

        # when the print count is exhausted exit the program gracefully
        # we can't use break here because it's being called by the run manager
        if self.processStatOptions.context is not PM_CONTEXT_ARCHIVE:
            if self.processStatOptions.print_count == 0:
                sys.exit(0)
            else:
                self.processStatOptions.print_count -= 1

        if self.processStatOptions.filterstate is not None:
            self.printer("Timestamp" + header_indentation +
                         "USER\t\tPID\t\tPPID\t\tPRI\t%CPU\t%MEM\tVSZ\tRSS\tS\tSTARTED\t\tTIME\t\tWCHAN\t\t\t\tCommand")
            processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time))
            for process in processes:
                total_percent = process.total_percent()
                current_process_pid = process.pid()
                current_process_sname = process.s_name()
                if process.wchan_s() is not None:
                    wchan = process.wchan_s()
                else:
                    wchan = '-'
                key = (current_process_sname, current_process_pid)
                if key in process_state_info:
                    process_state_info[key] = process_state_info[key] + self.delta_time
                else:
                    process_state_info[key] = self.delta_time
                process_name = process.process_name_with_args()
                if 7 < len(wchan) < 15:
                    self.printer("%s%s%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\t%s" %
                                 (timestamp, value_indentation, process.user_name(), process.pid(), process.ppid(),
                                  process.priority(), total_percent, process.system_percent(), process.vsize(),
                                  process.rss(), current_process_sname, process.start(), process.total_time(), wchan,
                                  process_name))
                elif len(wchan) >= 15:
                    self.printer("%s%s%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s" %
                                 (timestamp, value_indentation, process.user_name(), process.pid(), process.ppid(),
                                  process.priority(), total_percent, process.system_percent(), process.vsize(),
                                  process.rss(), current_process_sname, process.start(), process.total_time(), wchan,
                                  process_name))
                else:
                    self.printer("%s%s%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\t\t%s" %
                                 (timestamp, value_indentation, process.user_name(), process.pid(), process.ppid(),
                                  process.priority(), total_percent, process.system_percent(), process.vsize(),
                                  process.rss(), current_process_sname, process.start(), process.total_time(), wchan,
                                  process_name))
        elif self.processStatOptions.colum_list is not None:
            header = "Timestamp" + '\t'
            for key in self.processStatOptions.colum_list:
                if key in PIDINFO_PAIR:
                    header += PIDINFO_PAIR[key][0] + '\t\t'
            print(header)
            processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time))
            for process in processes:
                data_to_print = timestamp + '\t'
                for key in self.processStatOptions.colum_list:
                    if self._is_last_and_args(key):
                        data_to_print += str(PIDINFO_PAIR["args_last"][1](process)) + '\t\t'
                    elif key in PIDINFO_PAIR:
                        data_to_print += str(PIDINFO_PAIR[key][1](process)) + '\t\t'
                print(data_to_print)


class ProcessStatusReporter:
    def __init__(self, process_report, process_filter, delta_time, printer, processStatOptions):
        self.delta_time = delta_time
        self.process_report = process_report
        self.process_filter = process_filter
        self.printer = printer
        self.processStatOptions = processStatOptions

    def print_report(self, timestamp, header_indentation, value_indentation):

        # when the print count is exhausted exit the program gracefully
        # we can't use break here because it's being called by the run manager
        if self.processStatOptions.context is not PM_CONTEXT_ARCHIVE:
            if self.processStatOptions.print_count == 0:
                sys.exit(0)
            else:
                self.processStatOptions.print_count -= 1

        if self.processStatOptions.show_all_process:
            self.printer("Timestamp" + header_indentation + "PID\t\t\tTTY\tTIME\t\tCMD")
            processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time))
            for process in processes:
                command = process.process_name_with_args(True)
                ttyname = process.tty_name()
                self.printer("%s%s%s\t\t%s\t%s\t%s" % (timestamp, value_indentation, process.pid(), ttyname,
                                                       process.total_time(), command))
        elif self.processStatOptions.empty_arg_flag:
            self.printer("Timestamp" + header_indentation + "PID\t\tTIME\t\tCMD")
            processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time))
            for process in processes:
                pid = process.pid()
                command = process.process_name()
                self.printer("%s%s%s\t%s\t%s" % (timestamp, value_indentation, pid,
                                                 process.total_time(), command))
        elif self.processStatOptions.pid_filter_flag:
            self.printer("Timestamp" + header_indentation + "PID\t\tPPID\t\tTTY\tTIME\t\tCMD")
            processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time))
            for process in processes:
                pid = process.pid()
                command = process.process_name()
                ttyname = process.tty_name()
                self.printer("%s%s%s\t%s\t%s\t%s\t%s" % (timestamp, value_indentation, pid, process.ppid(),
                                                         ttyname, process.total_time(), command))
        elif self.processStatOptions.ppid_filter_flag:
            self.printer("Timestamp" + header_indentation + "PID\t\tPPID\t\tTTY\tTIME\t\tCMD")
            processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time))
            for process in processes:
                ppid = process.ppid()
                command = process.process_name()
                ttyname = process.tty_name()
                self.printer("%s%s%s\t%s\t%s\t%s\t%s" % (timestamp, value_indentation, process.pid(), ppid, ttyname,
                                                         process.total_time(), command))
        elif self.processStatOptions.username_filter_flag:
            self.printer("Timestamp" + header_indentation + "USERNAME\t\tPID\t\t%CPU\t%MEM\tVSZ\tRSS\t" +
                         "TTY\tSTAT\t\tTIME\t\tSTART\t\tCOMMAND")
            processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time))
            for process in processes:
                self.printer("%s%s%s\t\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s" % (
                    timestamp, value_indentation, process.user_name(), process.pid(),
                    process.system_percent(), process.total_percent(), process.vsize(), process.rss(),
                    process.tty_name(), process.ppid(), process.total_time(), process.start(),
                    process.process_name()))

        elif self.processStatOptions.user_oriented_format:
            self.printer("Timestamp" + header_indentation + "USERNAME\tPID\t\t%CPU\t%MEM\tVSZ\tRSS\t" +
                         "TTY\tSTAT\tTIME\t\tSTART\t\tCOMMAND")
            processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time))
            for process in processes:
                self.printer("%s%s%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s" % (
                    timestamp, value_indentation, process.user_name(), process.pid(),
                    process.system_percent(), process.total_percent(), process.vsize(), process.rss(),
                    process.tty_name(), process.s_name(), process.total_time(), process.start(),
                    process.process_name()))

        elif self.processStatOptions.command_filter_flag:
            self.printer("Timestamp" + header_indentation + "PID\t\tPPID\t\tTTY\tTIME\t\tCMD")
            processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time))
            for process in processes:
                ppid = process.ppid()
                command = process.process_name()
                ttyname = process.tty_name()
                self.printer("%s%s%s\t%s\t%s\t%s\t%s" % (timestamp, value_indentation, process.pid(), ppid, ttyname,
                                                         process.total_time(), command))


class ProcessStatReport(pmcc.MetricGroupPrinter):
    Machine_info_count = 0
    group = None
    def __init__(self, group=None):
        self.group = group

    def timeStampDelta(self):
        s = self.group.timestamp.tv_sec - self.group.prevTimestamp.tv_sec
        u = self.group.timestamp.tv_usec - self.group.prevTimestamp.tv_usec
        return s + u / 1000000.0

    def print_machine_info(self,context):
        timestamp = context.pmLocaltime(self.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 += self.group['kernel.uname.sysname'].netValues[0][2] + '  '
        header_string += self.group['kernel.uname.release'].netValues[0][2] + '  '
        header_string += '(' + self.group['kernel.uname.nodename'].netValues[0][2] + ')  '
        header_string += time_string + '  '
        header_string += self.group['kernel.uname.machine'].netValues[0][2] + '  '
        print("%s  (%s CPU)" % (header_string, self.__get_ncpu(self.group)))

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

    def __print_report(self, manager,timestamp, header_indentation, value_indentation,interval_in_seconds):
        metric_repository = ReportingMetricRepository(self.group)
        process_report = ProcessStatus(manager, metric_repository)
        process_filter = ProcessFilter(ProcessStatOptions)
        stdout = StdoutPrinter()
        printdecorator = NoneHandlingPrinterDecorator(stdout)
        report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds,
                                        printdecorator.Print, ProcessStatOptions)
        report.print_report(timestamp, header_indentation, value_indentation)
    def __print_dynamic_report(self, manager,timestamp, header_indentation, value_indentation,interval_in_seconds):
        metric_repository = ReportingMetricRepository(self.group)
        process_report = ProcessStatus(manager, metric_repository)
        process_filter = ProcessFilter(ProcessStatOptions)
        stdout = StdoutPrinter()
        printdecorator = NoneHandlingPrinterDecorator(stdout)
        report = DynamicProcessReporter(process_report, process_filter, interval_in_seconds,
                                        printdecorator.Print, ProcessStatOptions)
        report.print_report(timestamp, header_indentation, value_indentation)
    def __get_timestamp(self):
        ts = self.group.contextCache.pmLocaltime(int(self.group.timestamp))
        timestamp = time.strftime(ProcessStatOptions.timefmt, ts.struct_time())
        return timestamp

    def report(self, manager):
        try:
            if self.group['proc.psinfo.utime'].netPrevValues is None:
                # need two fetches to report rate converted counter metrics
                return
            if not self.group['hinv.ncpu'].netValues or not self.group['kernel.uname.sysname'].netValues:
                return
            try:
                if not self.Machine_info_count:
                    self.print_machine_info(manager)
                    self.Machine_info_count = 1
            except IndexError:
                return
            timestamp = self.__get_timestamp()
            interval_in_seconds = self.timeStampDelta()
            header_indentation = "        " if len(timestamp) < 9 else (len(timestamp) - 7) * " "
            value_indentation = ((len(header_indentation) + 9) - len(timestamp)) * " "

            # Doing this for one single print instance in case there is no count specified
            if ProcessStatOptions.print_count is None:
                ProcessStatOptions.print_count = 1
            # ================================================================
            if ProcessStatOptions.selective_colum_flag:
                self.__print_dynamic_report(manager,timestamp, header_indentation,
                                            value_indentation, interval_in_seconds)
            else:
                self.__print_report(manager,timestamp, header_indentation, value_indentation, interval_in_seconds)
        finally:
            sys.stdout.flush()


class ProcessStatOptions(pmapi.pmOptions):
    show_all_process = False
    command_filter_flag = False
    ppid_filter_flag = False
    pid_filter_flag = False
    username_filter_flag = False
    selective_colum_flag = False
    filter_flag = False
    user_oriented_format = False
    empty_arg_flag = False
    filterstate = None
    timefmt = "%H:%M:%S"
    print_count = None
    colum_list = []
    command_list = []
    pid_list = []
    ppid_list = []
    filtered_process_user = None
    context = None

    def __init__(self):
        pmapi.pmOptions.__init__(self, "t:c:e::p:ukVZ:z?:o:P:l:U:k")
        self.pmSetOptionCallback(self.extraOptions)
        self.pmSetOverrideCallback(self.override)
        self.options()

    def options(self):
        self.pmSetLongOptionHeader("General options")
        self.pmSetLongOption("", 0, "e", "",
                             "Show all process")
        self.pmSetLongOption("", 1, "c", "[Command name]",
                             "Show the stats for specified command name process")
        self.pmSetLongOption("", 1, "p", "[pid1,pid2,...]",
                             "Select the process by process ID")
        self.pmSetLongOption("", 1, "P", "[ppid1,ppid2,...]", "Select the process by process parent ID")
        self.pmSetLongOption("", 1, "U", "[User Name]", "Select the process by user name")
        self.pmSetLongOption("", 1, "o", "[col1,col2,... Or ALL]", "User -defined format " +
                             "USE -o [all] or -B [col1, col2 , ...]" +
                             "\n\t\t\tsupported user defined colums are command, wchan, started, Time, pid, ppid, "
                             "%mem, pri, user, %cpu and S "
                             "\n\t\t\tALL option shows USER,PID,PPID,PRI,%CPU,%MEM,VSZ,RSS,S,STARTED,TIME,WCHAN and "
                             "Command")
        self.pmSetLongOptionText("\tCOL\tHEADER \tDESCRIPTION")
        self.pmSetLongOptionText("\t%cpu\t%CPU \tcpu utilization of the process")
        self.pmSetLongOptionText("\t%mem\t%MEM  \tphysical memory on the machine expressed as a percentage")
        self.pmSetLongOptionText("\tstart\tSTART \ttime the command started")
        self.pmSetLongOptionText("\ttime\tTIME \taccumulated cpu time, user + system")
        self.pmSetLongOptionText("\tcls\tCLS \tscheduling class of the process")
        self.pmSetLongOptionText("\tcmd\tCMD\tsee args.  (alias args, command).")
        self.pmSetLongOptionText("\tpid\tPID \tthe process ID")
        self.pmSetLongOptionText("\tppid\tPPID\tparent process ID")
        self.pmSetLongOptionText("\tpri\tPRI \tpriority of the process")
        self.pmSetLongOptionText("\tstate\tS \tsee s")
        self.pmSetLongOptionText("\trss\tRSS \tthe non-swapped physical memory that a task has used")
        self.pmSetLongOptionText("\trtprio\tRTPRIO \trealtime priority")
        self.pmSetLongOptionText("\tpname\tPname\tProcess name")
        # self.pmSetLongOptionText("\ttime\tTIME \tcumulative CPU time")
        self.pmSetLongOptionText("\ttty\tTT \tcontrolling tty (terminal)")
        self.pmSetLongOptionText("\tuid\tUID \tsee euid")
        self.pmSetLongOptionText("\tvsize\tVSZ \tsee vsz")
        self.pmSetLongOptionText("\tuname\tUSER \tsee euser")
        self.pmSetLongOptionText("\twchan\tWCHAN \tname of the kernel function in which the process is sleeping")
        self.pmSetLongOption("", 0, 'u', "", "Display user-oriented format")
        self.pmSetLongOptionVersion()
        self.pmSetLongOptionTimeZone()
        self.pmSetLongOptionHostZone()
        self.pmSetLongOptionHelp()

    def override(self, opts):
        ProcessStatOptions.print_count = self.pmGetOptionSamples()
        # """Override standard Pcp-ps option to show all process """
        return bool(opts in ['p', 'c', 'o', 'P', 'U'])

    def extraOptions(self, opts, optarg, index):
        if opts == 'e':
            ProcessStatOptions.show_all_process = True
        elif opts == 'c':
            ProcessStatOptions.command_filter_flag = True
            ProcessStatOptions.filter_flag = True
            try:
                if optarg is not None:
                    ProcessStatOptions.command_list += optarg.replace(',', ' ').split(' ')
            except ValueError:
                print("Invalid command Id List: use comma separated pids without whitespaces")
                sys.exit(1)
        elif opts == 'p':
            ProcessStatOptions.filter_flag = True
            ProcessStatOptions.pid_filter_flag = True
            try:
                if optarg is not None:
                    dummy_list = optarg.replace(',', ' ').split(' ')
                    ProcessStatOptions.pid_list += [int(x) for x in dummy_list]
            except ValueError:
                print("Invalid pid Id List: use comma separated pids without whitespaces")
                sys.exit(1)
        elif opts == 'P':
            ProcessStatOptions.filter_flag = True
            ProcessStatOptions.ppid_filter_flag = True
            try:
                if optarg is not None:
                    dummy_list = optarg.replace(',', ' ').split(' ')
                    ProcessStatOptions.ppid_list += [int(x) for x in dummy_list]
            except ValueError:
                print("Invalid ppid Id List: use comma separated pids without whitespaces")
                sys.exit(1)
        elif opts == 'u':
            ProcessStatOptions.user_oriented_format = True
        elif opts == 'o':
            ProcessStatOptions.selective_colum_flag = True
            try:
                if optarg.upper() == "ALL":
                    ProcessStatOptions.filterstate = optarg.upper()
                else:
                    dummy_list = optarg.replace(',', ' ').split(' ')
                    for key in dummy_list:
                        if key.lower() in PIDINFO_PAIR:
                            ProcessStatOptions.colum_list.append(key.lower())
                        else:
                            raise ValueError
            except ValueError:
                print("Invalid ppid Id List: Either column name is not correct "
                      "or use comma separated column names without whitespaces")
                sys.exit(1)
        elif opts == 'U':
            ProcessStatOptions.username_filter_flag = True
            ProcessStatOptions.filter_flag = True
            ProcessStatOptions.filtered_process_user = optarg
        elif opts is None:
            ProcessStatOptions.show_all_process = True

    @staticmethod
    def checkOptions():
        if ProcessStatOptions.selective_colum_flag or \
                ProcessStatOptions.filter_flag or \
                ProcessStatOptions.user_oriented_format:
            return True
        else:
            ProcessStatOptions.empty_arg_flag = True
            return True


if __name__ == "__main__":
    try:
        opts = ProcessStatOptions()
        manager = pmcc.MetricGroupManager.builder(opts, sys.argv)
        ProcessStatOptions.context = manager.type
        if not opts.checkOptions():
            raise pmapi.pmUsageErr
        missing = manager.checkMissingMetrics(PSSTAT_METRICS)
        if missing is not None:
            sys.stderr.write('Error: not all required metrics are available\nMissing %s\n' % missing)
            sys.exit(1)
        manager['psstat'] = PSSTAT_METRICS
        manager.printer = ProcessStatReport(manager['psstat'])
        sts = manager.run()
        sys.exit(sts)
    except pmapi.pmErr as pmerror:
        sys.stderr.write('%s\n' % (pmerror.message()))
    except pmapi.pmUsageErr as usage:
        usage.message()
        sys.exit(1)
    except IOError:
        signal.signal(signal.SIGPIPE, signal.SIG_DFL)
    except KeyboardInterrupt:
        print("Interrupted")
        sys.exit(0)
