#! /usr/bin/python
#-------------------------------------------------------------------------------
#
# Copyright 2013,2014,2015,2016,2017,2018,2019,2020 Cumulus Networks Inc.
# All rights reserved
#
#-------------------------------------------------------------------------------
#
#-------------------------------------------------------------------------------
#
# Imports
#

import syslog
import exceptions
import warnings
import re
import os
import sys
import time
import optparse
import signal
import traceback
import argparse
import cumulus.platforms

#-------------------------------------------------------------------------------
#
# Classes
#

class PWMDRuntimeError(RuntimeError):
    pass

#-------------------------------------------------------------------------------
#
# Functions
#

#--------------------
#
# warning formats
#
def pwmdwarn(message, category, filename, lineno, line=None):
    return '%s:%s : %s : %s\n' % (filename, lineno, category.__name__, message)

#--------------------
#
# check to see if an instance is already running
#
def already_running(pidfile):
    myname=os.path.basename(sys.argv[0])
    try:
        if not os.path.isfile(pidfile):
            return False
        oldpid = re.findall('\D*(\d+).*', (file(pidfile, 'r').readline()))[0]
        if not os.path.exists('/proc/%s' % oldpid):
            return False
        if myname not in file('/proc/%s/cmdline' % oldpid, 'r').readline():
            return False
        sys.stderr.write("%s already running as process %s\n" % (myname, oldpid))
        return True
    except Exception as inst:
        raise PWMDRuntimeError("unable to validate pidfile %s: %s" %
                               (pidfile, str(inst)))

#--------------------
#
# normal exit
#
def exit_normally(signum=0, frame=None):
    syslog.syslog(syslog.LOG_INFO, "exiting normally")
    sys.stderr.write("%s : exiting normally\n" % sys.argv[0])
    exit(0)


def pwm_update_units(platform, args):
    units = []
    for unit in platform.pwms.units:
        cp = unit
        units.append(cp)

        if args.display:
            print cp.attrs['name']
            print '\tFans:'
            for fan in cp.fans:
                fan.find_and_add_attrs()
                rpm = 'unknown'
                pwm = rpm
                if fan.detect_state() == 'OK' or fan.detect_state() == 'LOW':
                    for key, value in fan.attrs.iteritems():
                        if re.match('fan[0-9]*_input', key):
                            rpm = value
                    pwm = cp.read_speed(fan)
                if fan.rpmmode:
                    print '\t\t%s Target RPM: %-7s    Measured RPM: %s' %(fan.attrs['name'], pwm, rpm)
                else:
                    print '\t\t%s PWM: %-7s     Measured RPM: %s' %(fan.attrs['name'], pwm, rpm)
            print '\tTemps:'
            for temp in cp.temps:
                temp.find_and_add_attrs()
                val = 'unknown'
                for key, value in temp.attrs.iteritems():
                    if re.match('temp[0-9]*_input', key):
                        try:
                            val = int(value)
                        except (ValueError, TypeError):
                            syslog.syslog(syslog.LOG_ERROR,
                                    "Value (%s) is not a number; it is  %s",
                                    temp.attrs['name'], val)
                            continue

                print '\t\t%s %s C' %(temp.attrs['name'], val)

    if args.display:
        print 'Use smonctl to get more info on temps and fans'
        exit(0)
    return units 
                    
#-------------------------------------------------------------------------------
#
# Main
#
def main():
    units = []
    platform = cumulus.platforms.probe()
    interval = 30
    if hasattr(platform.pwms, 'interval'):
        interval = platform.pwms.interval

    parser = argparse.ArgumentParser(description="Update fan speeds(PWM) on the platform in a loop")
    parser.add_argument('-d', '--display', help =argparse.SUPPRESS,
                        action="store_true")
    parser.add_argument('-i', '--interval', type = int,
                        help ="Update Interval in secs (default is %d secs)" %interval)
    args = parser.parse_args()

    if args.interval:
        interval = args.interval
    
    if platform.pwms is None:
        units = None
        if args.display:
            print 'Fans are not managed by pwmd on this platform'
            print 'Use smonctl to get more info on temps and fans'
            exit(0)
        else:
            syslog.syslog(syslog.LOG_INFO, "PWM Daemon doesn't manage fans on this platform")
    else:     
        units = pwm_update_units(platform, args)
        syslog.syslog(syslog.LOG_INFO, "Starting PWM Daemon")

    pidfile = "/var/run/pwmd.pid"
    if already_running(pidfile):
        sys.exit()
    else:
        file(pidfile, 'w').write(str(os.getpid()))
        
    while True:
        if units:
            for unit in units:
                unit.run_state()
        time.sleep(interval)
    return 0


#--------------------
#
# execution check
#
if __name__ == "__main__":
    try:
        signal.signal(signal.SIGTERM, exit_normally)
        warnings.simplefilter("always")
        warnings.formatwarning = pwmdwarn
        syslog.openlog(": %s : " % sys.argv[0])
        syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_INFO))
        exit(main())
    except PWMDRuntimeError, errstr:
        syslog.syslog(syslog.LOG_ERR, "ERROR : %s" % str(errstr))
        sys.stderr.write("%s : ERROR : %s\n" % (sys.argv[0], str(errstr)))
        exit(1)
    except KeyboardInterrupt:
        exit_normally()
    except Exception:
        (exc_type, exc_value, exc_traceback) = sys.exc_info()
        err = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback))
        log = 'Unhandled Exception : %s' % err
        syslog.syslog(syslog.LOG_ERR, log)
        sys.stderr.write(log)
