#!/usr/bin/pcp python
'''
Python implementation of the "simple" Performance Metrics Domain Agent.
'''
#
# Copyright (c) 2013,2015 Red Hat.
# 
# 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.
# 

import os
import time
import ctypes
from ctypes import c_int, POINTER, cast
import cpmapi as c_api
from pcp.pmda import PMDA, pmdaMetric, pmdaIndom, pmdaInstid
from pcp.pmapi import pmUnits, pmContext as PCP

class SimplePMDA(PMDA):
    '''
    A simple Performance Metrics Domain Agent with very simple metrics.
    Install it and make basic use of it, as follows:

    # $PCP_PMDAS_DIR/simple/Install
    [select python option]
    $ pminfo -fmdtT simple

    Then experiment with the simple.conf file (see simple.now metrics).
    '''

    # simple.color instance domain
    red = 0
    green = 100
    blue = 200
    colors = [pmdaInstid(0, 'red'),
              pmdaInstid(1, 'green'),
              pmdaInstid(2, 'blue')]

    # simple.now instance domain
    configfile = ''
    timeslices = {}
    times = ()

    # simple.numfetch properties
    numfetch = 0
    oldfetch = -1


    def simple_instance(self, serial):
        ''' Called once per "instance request" PDU '''
        # self.log("instance update for %d" % serial)
        if (serial == 1):
            self.simple_timenow_check()


    def simple_fetch(self):
        ''' Called once per "fetch" PDU, before callbacks '''
        self.numfetch += 1
        self.simple_timenow_check()

    def simple_fetch_color_callback(self, item, inst):
        '''
        Returns a list of value,status (single pair) for color cluster
        Helper for the fetch callback
        '''
        if (item == 0):
            if (inst != c_api.PM_IN_NULL):
                return [c_api.PM_ERR_INST, 0]
            return [self.numfetch, 1]
        elif (item == 1):
            if (inst == 0):
                self.red = (self.red + 1) % 255
                return [self.red, 1]
            elif (inst == 1):
                self.green = (self.green + 1) % 255
                return [self.green, 1]
            elif (inst == 2):
                self.blue = (self.blue + 1) % 255
                return [self.blue, 1]
            else:
                return [c_api.PM_ERR_INST, 0]
        else:
            return [c_api.PM_ERR_PMID, 0]

    def simple_fetch_times_callback(self, item, inst):
        '''
        Returns a list of value,status (single pair) for times cluster
        Helper for the fetch callback
        '''
        if (inst != c_api.PM_IN_NULL):
            return [c_api.PM_ERR_INST, 0]
        if (self.oldfetch < self.numfetch):     # get current values, if needed
            self.times = os.times()
            self.oldfetch = self.numfetch
        if (item == 2):
            return [self.times[0], 1]
        elif (item == 3):
            return [self.times[1], 1]
        return [c_api.PM_ERR_PMID, 0]

    def simple_fetch_now_callback(self, item, inst):
        '''
        Returns a list of value,status (single pair) for "now" cluster
        Helper for the fetch callback
        '''
        if (item == 4):
            voidp = self.inst_lookup(self.now_indom, inst)
            if (voidp == None):
                return [c_api.PM_ERR_INST, 0]
            valuep = cast(voidp, POINTER(c_int))
            return [valuep.contents.value, 1]
        return [c_api.PM_ERR_PMID, 0]

    def simple_fetch_callback(self, cluster, item, inst):
        '''
        Main fetch callback, defers to helpers for each cluster.
        Returns a list of value,status (single pair) for requested pmid/inst
        '''
        # self.log("fetch callback for %d.%d[%d]" % (cluster, item, inst))
        if (cluster == 0):
            return self.simple_fetch_color_callback(item, inst)
        elif (cluster == 1):
            return self.simple_fetch_times_callback(item, inst)
        elif (cluster == 2):
            return self.simple_fetch_now_callback(item, inst)
        return [c_api.PM_ERR_PMID, 0]


    def simple_store_count_callback(self, val):
        ''' Helper for the store callback, handles simple.numfetch '''
        sts = 0
        if (val < 0):
            sts = c_api.PM_ERR_BADSTORE
            val = 0
        self.numfetch = val
        return sts

    def simple_store_color_callback(self, val, inst):
        ''' Helper for the store callback, handles simple.color '''
        sts = 0
        if (val < 0):
            sts = c_api.PM_ERR_BADSTORE
            val = 0
        elif (val > 255):
            sts = c_api.PM_ERR_BADSTORE
            val = 255

        if (inst == 0):
            self.red = val
        elif (inst == 1):
            self.green = val
        elif (inst == 2):
            self.blue = val
        else:
            sts = c_api.PM_ERR_INST
        return sts

    def simple_store_callback(self, cluster, item, inst, val):
        '''
        Store callback, executed when a request to write to a metric happens
        Defers to helpers for each storable metric.  Returns a single value.
        '''
        if (cluster == 0):
            if (item == 0):
                return self.simple_store_count_callback(val)
            elif (item == 1):
                return self.simple_store_color_callback(val, inst)
            else:
                return c_api.PM_ERR_PMID
        elif ((cluster == 1 and (item == 2 or item == 3))
            or (cluster == 2 and item == 4)):
            return c_api.PM_ERR_PERMISSION
        return c_api.PM_ERR_PMID


    def simple_timenow_check(self):
        '''
        Read our configuration file and update instance domain
        '''
        self.timeslices.clear()
        try:
            cfg = open(self.configfile)
            fields = time.localtime()
            values = {'sec': c_int(fields[5]),
                      'min': c_int(fields[4]),
                      'hour': c_int(fields[3])}
            config = cfg.readline().strip()
            for key in config.split(','):
                if (key != '' and values[key] != None):
                    self.timeslices[key] = values[key]
        finally:
            cfg.close()
        self.replace_indom(self.now_indom, self.timeslices)


    def __init__(self, name, domain):
        PMDA.__init__(self, name, domain)

        self.configfile = PCP.pmGetConfig('PCP_PMDAS_DIR')
        self.configfile += '/' + name + '/' + name + '.conf'

        self.connect_pmcd()

        self.color_indom = self.indom(0)
        self.add_indom(pmdaIndom(self.color_indom, self.colors))

        self.now_indom = self.indom(1)
        self.add_indom(pmdaIndom(self.now_indom, self.timeslices))

        self.add_metric(name + '.numfetch', pmdaMetric(self.pmid(0, 0),
                c_api.PM_TYPE_U32, c_api.PM_INDOM_NULL, c_api.PM_SEM_INSTANT,
                pmUnits(0, 0, 0, 0, 0, 0)))
        self.add_metric(name + '.color', pmdaMetric(self.pmid(0, 1),
                c_api.PM_TYPE_32, self.color_indom, c_api.PM_SEM_INSTANT,
                pmUnits(0, 0, 0, 0, 0, 0)))
        self.add_metric(name + '.time.user', pmdaMetric(self.pmid(1, 2),
                c_api.PM_TYPE_DOUBLE, c_api.PM_INDOM_NULL, c_api.PM_SEM_COUNTER,
                pmUnits(0, 1, 0, 0, c_api.PM_TIME_SEC, 0)))
        self.add_metric(name + '.time.sys', pmdaMetric(self.pmid(1, 3),
                c_api.PM_TYPE_DOUBLE, c_api.PM_INDOM_NULL, c_api.PM_SEM_COUNTER,
                pmUnits(0, 1, 0, 0, c_api.PM_TIME_SEC, 0)))
        self.add_metric(name + '.now', pmdaMetric(self.pmid(2, 4),
                c_api.PM_TYPE_U32, self.now_indom, c_api.PM_SEM_INSTANT,
                pmUnits(0, 0, 0, 0, 0, 0)))

        self.set_fetch(self.simple_fetch)
        self.set_instance(self.simple_instance)
        self.set_fetch_callback(self.simple_fetch_callback)
        self.set_store_callback(self.simple_store_callback)
        self.set_user(PCP.pmGetConfig('PCP_USER'))
        self.simple_timenow_check()


if __name__ == '__main__':

    SimplePMDA('simple', 253).run()

