#!/usr/bin/python

#
# pmcollectl.py
#
# Copyright (C) 2012-2014 Red Hat Inc.
#
# 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.
#

"""System status collector using the libpcp Wrapper module

Additional Information:

Performance Co-Pilot Web Site
http://www.performancecopilot.org
"""

# ignore line too long, missing docstring, method could be a function,
#        too many public methods
# pylint: disable=C0301
# pylint: disable=C0111
# pylint: disable=R0201
# pylint: disable=R0904

##############################################################################
#
# imports
#

import os
import sys
import time
import cpmapi as c_api
import cpmgui as c_gui
from pcp import pmapi, pmgui
from pcp.pmsubsys import Subsystem

ME = "pmcollectl"


# scale  -----------------------------------------------------------------


def scale(value, magnitude):
    return (value + (magnitude / 2)) / magnitude


# record ---------------------------------------------------------------

def record(context, config, duration, path, host):
    if os.path.exists(path):
        print ME + "archive %s already exists\n" % path
        sys.exit(1)
    # Non-graphical application using libpcp_gui services - never want
    # to see popup dialogs from pmlogger(1) here, so force the issue.
    os.environ['PCP_XCONFIRM_PROG'] = '/bin/true'
    interval = pmapi.timeval.fromInterval(str(duration) + " seconds")
    context.pmRecordSetup(path, ME, 0) # pylint: disable=W0621
    context.pmRecordAddHost(host, 1, config) # just a filename
    deadhand = "-T" + str(interval) + "seconds"
    context.pmRecordControl(0, c_gui.PM_REC_SETARG, deadhand)
    context.pmRecordControl(0, c_gui.PM_REC_ON, "")
    interval.sleep()
    context.pmRecordControl(0, c_gui.PM_REC_OFF, "")
    # Note: pmlogger has a deadhand timer that will make it stop of its
    # own accord once -T limit is reached; but we send an OFF-recording
    # message anyway for cleanliness, just prior to pmcollectl exiting.

# record_add_creator ------------------------------------------------------

def record_add_creator(path):
    fdesc = open(path, "a+")
    args = ""
    for i in sys.argv:
        args = args + i + " "
    fdesc.write("# Created by " + args)
    fdesc.write("\n#\n")
    fdesc.close()

# _CollectPrint -------------------------------------------------------


class _CollectPrint(object):
    def __init__(self, ss):
        self.ss = ss
    def print_header1(self):
        if self.verbosity == "brief":
            self.print_header1_brief()
        elif self.verbosity == "detail":
            self.print_header1_detail()
        elif self.verbosity == "verbose":
            self.print_header1_verbose()
	sys.stdout.flush()
    def print_header2(self):
        if self.verbosity == "brief":
            self.print_header2_brief()
        elif self.verbosity == "detail":
            self.print_header2_detail()
        elif self.verbosity == "verbose":
            self.print_header2_verbose()
	sys.stdout.flush()
    def print_header1_brief(self):
        True                        # pylint: disable-msg=W0104
    def print_header2_brief(self):
        True                        # pylint: disable-msg=W0104
    def print_header1_detail(self):
        True                        # pylint: disable-msg=W0104
    def print_header2_detail(self):
        True                        # pylint: disable-msg=W0104
    def print_header1_verbose(self):
        True                        # pylint: disable-msg=W0104
    def print_header2_verbose(self):
        True                        # pylint: disable-msg=W0104
    def print_line(self):
        if self.verbosity == "brief":
            self.print_brief()
        elif self.verbosity == "detail":
            self.print_detail()
        elif self.verbosity == "verbose":
            self.print_verbose()
    def print_brief(self):
        True                        # pylint: disable-msg=W0104
    def print_verbose(self):
        True                        # pylint: disable-msg=W0104
    def print_detail(self):
        True                        # pylint: disable-msg=W0104
    def divide_check(self, dividend, divisor):
        if divisor == 0:
            return 0
        else:
            return dividend / divisor
    def set_verbosity(self, cpverbosity):
        self.verbosity = cpverbosity # pylint: disable-msg=W0201


# _cpuCollectPrint --------------------------------------------------


class _cpuCollectPrint(_CollectPrint):
    def print_header1_brief(self):
        sys.stdout.write('#<--------CPU-------->')
    def print_header1_detail(self):
        print '# SINGLE CPU STATISTICS'
    def print_header1_verbose(self):
        print '# CPU SUMMARY (INTR, CTXSW & PROC /sec)'

    def print_header2_brief(self):
        sys.stdout.write('#cpu sys inter  ctxsw')
    def print_header2_detail(self):
        print '#   Cpu  User Nice  Sys Wait IRQ  Soft Steal Idle'
    def print_header2_verbose(self):
        print '#User  Nice   Sys  Wait   IRQ  Soft Steal  Idle  CPUs  Intr  Ctxsw  Proc  RunQ   Run   Avg1  Avg5 Avg15 RunT BlkT'

    def print_brief(self):
        print "%4d" % (100 * (self.ss.get_metric_value('kernel.all.cpu.nice') +
                              self.ss.get_metric_value('kernel.all.cpu.user') +
                              self.ss.get_metric_value('kernel.all.cpu.intr') +
                              self.ss.get_metric_value('kernel.all.cpu.sys') +
                              self.ss.get_metric_value('kernel.all.cpu.steal') +
                              self.ss.get_metric_value('kernel.all.cpu.irq.hard') +
                              self.ss.get_metric_value('kernel.all.cpu.irq.soft')) /
                       ss.cpu_total),
        print "%3d" % (100 * (self.ss.get_metric_value('kernel.all.cpu.intr') +
                              self.ss.get_metric_value('kernel.all.cpu.sys') +
                              self.ss.get_metric_value('kernel.all.cpu.steal') +
                              self.ss.get_metric_value('kernel.all.cpu.irq.hard') +
                              self.ss.get_metric_value('kernel.all.cpu.irq.soft')) /
                       ss.cpu_total),
        print "%5d %6d" % (self.ss.get_metric_value('kernel.all.intr'),
                           self.ss.get_metric_value('kernel.all.pswitch')),
    def print_detail(self):
        for k in range(self.ss.get_len(self.ss.get_metric_value('kernel.percpu.cpu.user'))):
            print "    %3d  %4d %4d  %3d %4d %3d  %4d %5d %4d" % (
                k,
                (100 * (self.ss.get_scalar_value('kernel.percpu.cpu.nice', k) +
                        self.ss.get_scalar_value('kernel.percpu.cpu.user', k) +
                        self.ss.get_scalar_value('kernel.percpu.cpu.intr', k) +
                        self.ss.get_scalar_value('kernel.percpu.cpu.sys', k) +
                        self.ss.get_scalar_value('kernel.percpu.cpu.steal', k) +
                        self.ss.get_scalar_value('kernel.percpu.cpu.irq.hard', k) +
                        self.ss.get_scalar_value('kernel.percpu.cpu.irq.soft', k)) /
                ss.cpu_total),
            self.ss.get_scalar_value('kernel.percpu.cpu.nice', k),
            (100 * (self.ss.get_scalar_value('kernel.percpu.cpu.intr', k) +
                    self.ss.get_scalar_value('kernel.percpu.cpu.sys', k) +
                    self.ss.get_scalar_value('kernel.percpu.cpu.steal', k) +
                    self.ss.get_scalar_value('kernel.percpu.cpu.irq.hard', k) +
                    self.ss.get_scalar_value('kernel.percpu.cpu.irq.soft', k)) /
             ss.cpu_total),
            self.ss.get_scalar_value('kernel.percpu.cpu.wait.total', k),
            self.ss.get_scalar_value('kernel.percpu.cpu.irq.hard', k),
            self.ss.get_scalar_value('kernel.percpu.cpu.irq.soft', k),
            self.ss.get_scalar_value('kernel.percpu.cpu.steal', k),
                self.ss.get_scalar_value('kernel.percpu.cpu.idle', k) / 10)
    def print_verbose(self):
        ncpu = self.ss.get_metric_value('hinv.ncpu')
        print "%4d %6d %5d %4d %4d %5d " % (
            (100 * (self.ss.get_metric_value('kernel.all.cpu.nice') +
                    self.ss.get_metric_value('kernel.all.cpu.user') +
                    self.ss.get_metric_value('kernel.all.cpu.intr') +
                    self.ss.get_metric_value('kernel.all.cpu.sys') +
                    self.ss.get_metric_value('kernel.all.cpu.steal') +
                    self.ss.get_metric_value('kernel.all.cpu.irq.hard') +
                    self.ss.get_metric_value('kernel.all.cpu.irq.soft')) /
             ss.cpu_total),
            self.ss.get_metric_value('kernel.all.cpu.nice'),
            (100 * (self.ss.get_metric_value('kernel.all.cpu.intr') +
                    self.ss.get_metric_value('kernel.all.cpu.sys') +
                    self.ss.get_metric_value('kernel.all.cpu.steal') +
                    self.ss.get_metric_value('kernel.all.cpu.irq.hard') +
                    self.ss.get_metric_value('kernel.all.cpu.irq.soft')) /
             ss.cpu_total),
            self.ss.get_metric_value('kernel.all.cpu.wait.total'),
            self.ss.get_metric_value('kernel.all.cpu.irq.hard'),
            self.ss.get_metric_value('kernel.all.cpu.irq.soft')
            ),
        print "%6d %6d %5d %5d %6d" % (
            self.ss.get_metric_value('kernel.all.cpu.steal'),
            self.ss.get_metric_value('kernel.all.cpu.idle') / (10 * ncpu),
            ncpu,
            self.ss.get_metric_value('kernel.all.intr'),
            self.ss.get_metric_value('kernel.all.pswitch')
            ),
        print "%5d %5d %5d %5.2f %5.2f %5.2f %4d %4d" % (
            self.ss.get_metric_value('kernel.all.nprocs'),
            self.ss.get_metric_value('kernel.all.runnable'),
            self.ss.get_metric_value('proc.runq.runnable'),
            self.ss.get_metric_value('kernel.all.load')[0],
            self.ss.get_metric_value('kernel.all.load')[1],
            self.ss.get_metric_value('kernel.all.load')[2],
            self.ss.get_metric_value('kernel.all.runnable'),
            self.ss.get_metric_value('proc.runq.blocked'))


# _interruptCollectPrint ---------------------------------------------


class _interruptCollectPrint(_CollectPrint):
    def print_header1_brief(self):
        ndashes = (((self.ss.get_len(self.ss.get_metric_value('kernel.percpu.interrupts.THR'))) * 6) - 6) / 2
        hdr = "#<"
        for k in range(ndashes):
            hdr += "-"
        hdr += "Int"
        for k in range(ndashes):
            hdr += "-"
        hdr += ">"
        print hdr,
    def print_header1_detail(self):
        print '# INTERRUPT DETAILS'
        print '# Int    ',
        for k in range(self.ss.get_len(self.ss.get_metric_value('kernel.percpu.interrupts.THR'))):
            print 'Cpu%d ' % k,
        print 'Type            Device(s)'
    def print_header1_verbose(self):
        print '# INTERRUPT SUMMARY'
    def print_header2_brief(self):
        for k in range(self.ss.get_len(self.ss.get_metric_value('kernel.percpu.interrupts.THR'))):
            if k == 0:
                print '#Cpu%d ' % k,
            else:
                print 'Cpu%d ' % k,
    def print_header2_verbose(self):
        print '#    ',
        for k in range(self.ss.get_len(self.ss.get_metric_value('kernel.percpu.interrupts.THR'))):
            print 'Cpu%d ' % k,
        print
    def print_brief(self):
        int_count = []
        for k in range(self.ss.get_len(self.ss.get_metric_value('kernel.percpu.interrupts.THR'))):
            int_count.append(0)
            for j in ss.metrics:
                if j[0:24] == 'kernel.percpu.interrupts':
                    int_count[k] += self.ss.get_scalar_value(self.ss.metrics_dict[j], k)

        for k in range(self.ss.get_len(self.ss.get_metric_value('kernel.percpu.interrupts.THR'))):
            print "%4d " % (int_count[k]),
    def print_detail(self):
        for j in ss.metrics:
            if j[0:24] != 'kernel.percpu.interrupts':
                continue
            j_i = self.ss.metrics_dict[j]
            have_nonzero_value = False
            for k in range(self.ss.get_len(self.ss.get_metric_value('kernel.percpu.interrupts.THR'))):
                if self.ss.get_scalar_value(j_i, k) != 0:
                    have_nonzero_value = True
                if not have_nonzero_value:
                    continue
            if have_nonzero_value:
                # pcp does not give the interrupt # so print spaces
                print "%-8s" % self.ss.metrics[j_i].split(".")[3],
                for i in range(self.ss.get_len(self.ss.get_metric_value('kernel.percpu.interrupts.THR'))):
                    print "%4d " % (self.ss.get_scalar_value(j_i, i)),
                text = (pm.pmLookupText(self.ss.metric_pmids[j_i], c_api.PM_TEXT_ONELINE))
                print "%-18s %s" % (text[:(str.index(text, " "))],
                                 text[(str.index(text, " ")):])
    def print_verbose(self):
        print "     ",
        self.print_brief()
        print


# _diskCollectPrint --------------------------------------------------


class _diskCollectPrint(_CollectPrint):
    def print_header1_brief(self):
        sys.stdout.write('<----------Disks----------->')
    def print_header1_detail(self):
        print '# DISK STATISTICS (/sec)'
    def print_header1_verbose(self):
        print '\n\n# DISK SUMMARY (/sec)'
    def print_header2_brief(self):
        sys.stdout.write(' KBRead  Reads KBWrit Writes')
    def print_header2_detail(self):
        print '#          <---------reads---------><---------writes---------><--------averages--------> Pct'
        print '#Name       KBytes Merged  IOs Size  KBytes Merged  IOs Size  RWSize  QLen  Wait SvcTim Util'
    def print_header2_verbose(self):
        sys.stdout.write('#KBRead RMerged  Reads SizeKB  KBWrite WMerged Writes SizeKB\n')
    def print_brief(self):
        sys.stdout.write("%6d %6d %6d %6d" % (
            self.ss.get_metric_value('disk.all.read_bytes'),
            self.ss.get_metric_value('disk.all.read'),
            self.ss.get_metric_value('disk.all.write_bytes'),
            self.ss.get_metric_value('disk.all.write')))
    def print_detail(self):
        for j in xrange(len(self.ss.metric_pmids)):
            try:
		if self.ss.metrics[j] == 'disk.dev.read':
		    (inst, iname) = pm.pmGetInDom(self.ss.metric_descs[j])
		    break
            except pmapi.pmErr, e:
                iname = "X"

        # metric values may be scalars or arrays depending on # of disks
        for j in xrange(len(iname)):
            print "%-10s %6d %6d %4d %4d  %6d %6d %4d %4d  %6d %6d %4d %6d %4d" % (
                iname[j],
                self.ss.get_scalar_value('disk.dev.read_bytes', j),
                self.ss.get_scalar_value('disk.dev.read_merge', j),
                self.ss.get_scalar_value('disk.dev.read', j),
                self.ss.get_scalar_value('disk.dev.blkread', j),
                self.ss.get_scalar_value('disk.dev.write_bytes', j),
                self.ss.get_scalar_value('disk.dev.write_merge', j),
                self.ss.get_scalar_value('disk.dev.write', j),
                self.ss.get_scalar_value('disk.dev.blkwrite', j),
                0, 0, 0, 0, 0)
# ??? replace 0 with required fields

    def print_verbose(self):
        avgrdsz = avgwrsz = 0
        if self.ss.get_metric_value('disk.all.read') > 0:
            avgrdsz = self.ss.get_metric_value('disk.all.read_bytes')
            avgrdsz /= self.ss.get_metric_value('disk.all.read')
        if self.ss.get_metric_value('disk.all.write') > 0:
            avgwrsz = self.ss.get_metric_value('disk.all.write_bytes')
            avgwrsz /= self.ss.get_metric_value('disk.all.write')

        print '%6d %6d %6d %6d %7d %8d %6d %6d' % (
            avgrdsz,
            self.ss.get_metric_value('disk.all.read_merge'),
            self.ss.get_metric_value('disk.all.read'),
            0,
            avgwrsz,
            self.ss.get_metric_value('disk.all.write_merge'),
            self.ss.get_metric_value('disk.all.write'),
            0)


# _memoryCollectPrint ------------------------------------------------


class _memoryCollectPrint(_CollectPrint):
    def print_header1_brief(self):
        sys.stdout.write('#<-----------Memory----------->')
    def print_header1_verbose(self):
        print '# MEMORY SUMMARY'
    def print_header2_brief(self):
        print '#Free Buff Cach Inac Slab  Map'
    def print_header2_verbose(self):
        print '#<-------------------------------Physical Memory--------------------------------------><-----------Swap------------><-------Paging------>'
        print '#   Total    Used    Free    Buff  Cached    Slab  Mapped    Anon  Commit  Locked Inact Total  Used  Free   In  Out Fault MajFt   In  Out'
    def print_brief(self):
        print "%4dM %3dM %3dM %3dM %3dM %3dM " % (
            scale(self.ss.get_metric_value('mem.freemem'), 1000),
            scale(self.ss.get_metric_value('mem.util.bufmem'), 1000),
            scale(self.ss.get_metric_value('mem.util.cached'), 1000),
            scale(self.ss.get_metric_value('mem.util.inactive'), 1000),
            scale(self.ss.get_metric_value('mem.util.slab'), 1000),
            scale(self.ss.get_metric_value('mem.util.mapped'), 1000)),
    def print_verbose(self):
        print "%8dM %6dM %6dM %6dM %6dM %6dM %6dM %6dM %6dM %6dM %5dM %5dM %5dM %5dM %6d %6d %6d %6d %6d %6d " % (
            scale(self.ss.get_metric_value('mem.physmem'), 1000),
            scale(self.ss.get_metric_value('mem.util.used'), 1000),
            scale(self.ss.get_metric_value('mem.freemem'), 1000),
            scale(self.ss.get_metric_value('mem.util.bufmem'), 1000),
            scale(self.ss.get_metric_value('mem.util.cached'), 1000),
            scale(self.ss.get_metric_value('mem.util.slab'), 1000),
            scale(self.ss.get_metric_value('mem.util.mapped'), 1000),
            scale(self.ss.get_metric_value('mem.util.anonpages'), 1000),
            scale(self.ss.get_metric_value('mem.util.committed_AS'), 1000),
            scale(self.ss.get_metric_value('mem.util.mlocked'), 1000),
            scale(self.ss.get_metric_value('mem.util.inactive'), 1000),
            scale(self.ss.get_metric_value('mem.util.swapTotal'), 1000),
            scale(self.ss.get_metric_value('swap.used'), 1000),
            scale(self.ss.get_metric_value('swap.free'), 1000),
            scale(self.ss.get_metric_value('swap.pagesin'), 1000),
            scale(self.ss.get_metric_value('swap.pagesout'), 1000),
            scale(self.ss.get_metric_value('mem.vmstat.pgfault') -
                  self.ss.get_metric_value('mem.vmstat.pgmajfault'), 1000),
            scale(self.ss.get_metric_value('mem.vmstat.pgmajfault'), 1000),
            scale(self.ss.get_metric_value('mem.vmstat.pgpgin'), 1000),
            scale(self.ss.get_metric_value('mem.vmstat.pgpgout'), 1000))


# _netCollectPrint --------------------------------------------------


class _netCollectPrint(_CollectPrint):
    def print_header1_brief(self):
        sys.stdout.write('<----------Network---------->')
    def print_header1_detail(self):
        print '# NETWORK STATISTICS (/sec)'
    def print_header1_verbose(self):
        print '\n\n# NETWORK SUMMARY (/sec)'
    def print_header2_brief(self):
        sys.stdout.write(' KBIn  PktIn  KBOut  PktOut')
    def print_header2_detail(self):
        print '#Num    Name   KBIn  PktIn SizeIn  MultI   CmpI  ErrsI  KBOut PktOut  SizeO   CmpO ErrsO'
    def print_header2_verbose(self):
        print '# KBIn  PktIn SizeIn  MultI   CmpI  ErrsI  KBOut PktOut  SizeO   CmpO  ErrsO'
    def print_brief(self):
        print "%5d %6d %6d %6d" % (
            sum(self.ss.get_metric_value('network.interface.in.bytes')) / 1024,
            sum(self.ss.get_metric_value('network.interface.in.packets')),
            sum(self.ss.get_metric_value('network.interface.out.bytes')) / 1024,
            sum(self.ss.get_metric_value('network.interface.out.packets'))),
    def average_packet_size(self, bytes, packets):
        # calculate mean packet size safely (note that divisor may be zero)
        result = 0
        bin = sum(self.ss.get_metric_value('network.interface.' + bytes))
        pin = sum(self.ss.get_metric_value('network.interface.' + packets))
        if pin > 0:
            result = bin / pin
        return result
    def print_verbose(self):
        # don't include loopback; TODO: pmDelProfile would be more appropriate
        self.ss.get_metric_value('network.interface.in.bytes')[0] = 0
        self.ss.get_metric_value('network.interface.out.bytes')[0] = 0
        print '%6d %5d %6d %6d %6d %6d %6d %6d %6d %6d %7d' % (
            sum(self.ss.get_metric_value('network.interface.in.bytes')) / 1024,
            sum(self.ss.get_metric_value('network.interface.in.packets')),
            self.average_packet_size('in.bytes', 'in.packets'),
            sum(self.ss.get_metric_value('network.interface.in.mcasts')),
            sum(self.ss.get_metric_value('network.interface.in.compressed')),
            sum(self.ss.get_metric_value('network.interface.in.errors')),
            sum(self.ss.get_metric_value('network.interface.out.bytes')) / 1024,
            sum(self.ss.get_metric_value('network.interface.out.packets')),
            self.average_packet_size('out.bytes', 'out.packets'),
            sum(self.ss.get_metric_value('network.interface.total.mcasts')),
            sum(self.ss.get_metric_value('network.interface.out.errors')))
    def print_detail(self):
        for j in xrange(len(self.ss.metric_pmids)):
            try:
		if self.ss.metrics[j] == 'network.interface.in.bytes':
		    (inst, iname) = pm.pmGetInDom(self.ss.metric_descs[j])
		    break
            except pmapi.pmErr, e: # pylint: disable-msg=C0103
                iname = "X"

        for j in xrange(len(iname)):
            print '%4d %-7s %6d %5d %6d %6d %6d %6d %6d %6d %6d %6d %7d' % (
                j, iname[j],
                self.ss.get_metric_value('network.interface.in.bytes')[j] / 1024,
                self.ss.get_metric_value('network.interface.in.packets')[j],
                self.divide_check(self.ss.get_metric_value('network.interface.in.bytes')[j],
                                   self.ss.get_metric_value('network.interface.in.packets')[j]),
                self.ss.get_metric_value('network.interface.in.mcasts')[j],
                self.ss.get_metric_value('network.interface.in.compressed')[j],
                self.ss.get_metric_value('network.interface.in.errors')[j],
                self.ss.get_metric_value('network.interface.in.packets')[j],
                self.ss.get_metric_value('network.interface.out.packets')[j],
                self.divide_check(self.ss.get_metric_value('network.interface.in.packets')[j],
                self.ss.get_metric_value('network.interface.out.packets')[j]) / 1024,
                    self.ss.get_metric_value('network.interface.total.mcasts')[j],
                    self.ss.get_metric_value(
                    'network.interface.out.compressed')[j])

class _Options(object):
    def __init__(self):
        self.subsys_arg = ""
        self.verbosity = "brief"
        self.input_file = ""
        self.output_file = ""
        self.archive = []
        self.host = "local:"
        self.create_archive = False
        self.replay_archive = False
        self.interval_arg = 1
        self.n_samples = 0
        self.duration_arg = 0
        self.opts = self.setup()

    def setup(self):
        """ Setup default command line argument option handling """
        opts = pmapi.pmOptions()
        opts.pmSetOptionCallback(self.option_callback)
        opts.pmSetOverrideCallback(self.override)
        opts.pmSetShortOptions("vp:c:f:R:i:s:h:?")
        opts.pmSetLongOptionHeader("Options")
        opts.pmSetLongOption("verbose", 0, 'v', '', "Produce verbose output")
        opts.pmSetLongOption("playback", 0, 'p', '', "Read sample data from file")
        opts.pmSetLongOption("count", 1, 'c', 'COUNT', "Number of samples")
        opts.pmSetLongOption("filename", 1, 'f', 'FILENAME', "Name of output file")
        opts.pmSetLongOption("runtime", 1, 'R', 'N', "How long to take samples")
        opts.pmSetLongOption("interval", 1, 'i', 'N', "The sample time interval")
        opts.pmSetLongOption("subsys", 1, 's', 'SUBSYS', "The subsystem to sample")
        opts.pmSetShortUsage("[options]\nInteractive: [-v] [-h host] [-s subsys] [-c N] [-i N] [-R N]\nWrite raw logfile: pmcollectl -f rawfile [-c N] [-i N] [-R N]\nRead raw logfile: pmcollectl -p rawfile")
        opts.pmSetLongOptionHost()
        opts.pmSetLongOptionVersion()
        opts.pmSetLongOptionHelp()
        return opts


    def override(self, opt):
	""" Override a few standard PCP options to match free(1) """
	# pylint: disable=R0201
	if opt == 's' or opt == 'i' or opt == 'h' or opt == "p":
	    return 1
	return 0

    def option_callback(self, opt, optarg, index):
        """ Perform setup for an individual command line option """

        s_options = {"d":[disk, "brief"], "D":[disk, "detail"],
                 "c":[cpu, "brief"], "C":[cpu, "detail"],
                 "n":[net, "brief"], "N":[net, "detail"],
                 "j":[interrupt, "brief"], "J":[interrupt, "detail"],
                 "m":[memory, "brief"], # "M":[ss, "detail"],
                 }

        # pylint: disable=W0613
        if opt == 's':
            for ssx in xrange(len(optarg)):
                self.subsys_arg = optarg[ssx:ssx+1]
                try:
                    subsys.append(s_options[self.subsys_arg][0])
                except KeyError:
                    print sys.argv[0] + \
                    ": Unimplemented subsystem -s" + self.subsys_arg
                    sys.exit(1)
                if self.subsys_arg.isupper():
                    self.verbosity = s_options[self.subsys_arg][1]
        elif opt == 'R':
            self.duration_arg = optarg
        elif opt == 'p':
            self.opts.pmSetOptionArchiveFolio(optarg)
            self.input_file = optarg
            self.replay_archive = True
        elif opt == 'f':
            self.output_file = optarg
            self.create_archive = True
        elif opt == 'v':
            if self.verbosity != "detail":
                self.verbosity = "verbose"
        elif opt == 'i':
            self.opts.pmSetOptionInterval(optarg)
            self.interval_arg = self.opts.pmGetOptionInterval()
        elif opt == 'c':
            self.opts.pmSetOptionSamples(optarg)
            self.n_samples = int(self.opts.pmGetOptionSamples())
        elif opt == 'h':
            self.host = optarg

# main -----------------------------------------------------------------


# ignore These are actually global names; ignore invalid name warning for now
# TODO move main into a def and enable
# pylint: disable-msg=C0103


if __name__ == '__main__':
    subsys = list()
    output_file = ""
    input_file = ""
    duration = 0.0

    ss = Subsystem()
    ss.init_processor_metrics()
    ss.init_interrupt_metrics()
    ss.init_disk_metrics()
    ss.init_memory_metrics()
    ss.init_network_metrics()

    cpu = _cpuCollectPrint(ss)
    interrupt = _interruptCollectPrint(ss)
    disk = _diskCollectPrint(ss)
    memory = _memoryCollectPrint(ss)
    net = _netCollectPrint(ss)

    # Establish a PMAPI context to archive, host or local, via args
    opts = _Options()
    if c_api.pmGetOptionsFromList(sys.argv) != 0:
        c_api.pmUsageMessage()
        sys.exit(1)

    if len(subsys) == 0:
        if opts.create_archive:
            map(subsys.append, (cpu, disk, net, interrupt, memory))
        else:
            map(subsys.append, (cpu, disk, net))

    if opts.duration_arg != 0:
        (timeval, errmsg) = pm.pmParseInterval(str(opts.duration_arg))
        duration = c_api.pmtimevalToReal(timeval)

    pm = pmapi.pmContext.fromOptions(opts.opts, sys.argv)
    if pm.type == c_api.PM_CONTEXT_ARCHIVE:
        pm.pmSetMode(c_api.PM_MODE_FORW, pmapi.timeval(0, 0), 0)

    # Find server-side pmcd host-name
    host = pm.pmGetContextHostName()

    (delta, errmsg) = pmapi.pmContext.pmParseInterval(str(opts.interval_arg) + " seconds")

    if opts.create_archive:
        delta_seconds = c_api.pmtimevalToReal(delta.tv_sec, delta.tv_usec)
        msec = str(int(1000.0 * delta_seconds))
        configuration = "log mandatory on every " + msec + " milliseconds { "
        configuration += ss.dump_metrics()
        configuration += "}"
        if duration == 0.0:
            if opts.n_samples != 0:
                duration = float(opts.n_samples) * delta_seconds
            else:
                duration = float(10) * delta_seconds
        record(pmgui.GuiClient(), configuration, duration, opts.output_file, host)
        record_add_creator(opts.output_file)
        sys.exit(0)

    try:
        ss.setup_metrics(pm)
        ss.get_stats(pm)
    except pmapi.pmErr, e:
        if opts.replay_archive:
            import textwrap
            print "One of the following metrics is required " + \
                  "but absent in " + input_file + "\n" + \
                  textwrap.fill(str(ss.metrics))
        else:
            print "unable to setup metrics"
            sys.exit(1)

    for ssx in subsys:
        ssx.set_verbosity(opts.verbosity)

    # brief headings for different subsystems are concatenated together
    if opts.verbosity == "brief":
        for ssx in subsys:
            if ssx == 0:
                continue
            ssx.print_header1()
        print
        for ssx in subsys:
            if ssx == 0:
                continue
            ssx.print_header2()
        print

    try:
        i_samples = 0
        while (i_samples < opts.n_samples) or (opts.n_samples == 0):
            pm.pmtimevalSleep(delta)
            if opts.verbosity != "brief" and len(subsys) > 1:
                print "\n### RECORD %d >>> %s <<< %s ###" % \
                      (i_samples+1, host, time.strftime("%a %b %d %H:%M:%S %Y"))

            try:
                ss.get_stats(pm)
                ss.get_total()
                for ssx in subsys:
                    if ssx == 0:
                        continue
                    if opts.verbosity != "brief" and (len(subsys) > 1 or i_samples == 0):
                        print
                        ssx.print_header1()
                        ssx.print_header2()
                    ssx.print_line()
                if opts.verbosity == "brief":
                    print
            except pmapi.pmErr, e:
                if str(e).find("PM_ERR_EOL") != -1:
                    print str(e)
                break

            i_samples += 1
    except KeyboardInterrupt:
        True                        # pylint: disable-msg=W0104
