#!/usr/bin/env python

import sys
sys.path.append('/home/panstarrs/ippm/common/lib/python2.7/site-packages')

import psutil
import os
import pwd
import time
import datetime
import subprocess
import re
import socket

class Callable:
    """
    See http://code.activestate.com/recipes/52304-static-methods-aka-class-methods-in-python/
    """
    def __init__(self, anycallable):
        self.__call__ = anycallable

class Users:
    """
    Class where some information related to users is cached. More a
    container with convenient method actually.
    """
    ignore = ['root', 'apache', 'bin', 'nobody', 'nagios', 'mysql', ]
    expected = ['ipp', 'ippdor', 'ippdvo', 'iqanalysis', ]
    always = ['ipp', 'ippdvo', ]
    cmdline_ignore_re_progs_from_pattern = [ re.compile('.*condor_.*'), 
                                             re.compile('.*get_processes_information.py.*'),
                                             ]
    singleton = dict() # index is the (numeric) uid
    def get_user_name(uid):
        try:
            return Users.singleton[uid]
        except KeyError:
            Users.singleton[uid] = pwd.getpwuid(uid).pw_name
            return Users.singleton[uid]
    get_user_name = Callable(get_user_name)
    max_age_in_seconds = 6*3600
    min_cpu = 5.
    min_memory = 5.

class SmallProcess:
    def __init__(self, pid, username, cmdline, cpu_percent, memory_percent, age, count = 1):
        self.pid = pid
        self.cmdline = cmdline
        self.username = username
        self.cpu_percent = cpu_percent
        self.memory_percent = memory_percent
        self.age = age
        self.count = count

class Processes:
    processes = dict()
    def add_process(process):
        """Append the information relative to a process to the class
        'processes' container"""
        try:
            Processes.processes[process.pid].append(process)
        except KeyError:
            Processes.processes[process.pid] = []
            Processes.processes[process.pid].append(process)
    add_process = Callable(add_process)
    def statistics():
        data = []
        for key in sorted(Processes.processes.iterkeys()):
            item = Processes.processes[key]
            count = 0
            avg_cpu_perc = 0.
            avg_mem_perc = 0.
            for sprocess in item:
                avg_cpu_perc += sprocess.cpu_percent
                avg_mem_perc += sprocess.memory_percent
                count += 1.
            if count > 1:
                sp = SmallProcess(sprocess.pid, 
                                  sprocess.username, 
                                  sprocess.cmdline,
                                  avg_cpu_perc / count,
                                  avg_mem_perc / count,
                                  sprocess.age,
                                  count)
                data.append(sp)
        return data
    statistics = Callable(statistics)

class ProcessesSnapshot:
    """
    The container of all processes
    """
    def __init__(self, min_cpu = Users.min_cpu, min_memory = Users.min_memory, max_age_in_seconds = Users.max_age_in_seconds):
        processes = dict() # index is the pid
        ages = get_correct_processes_age()
        for process in psutil.process_iter():
            try:
                age = ages[process.pid]
            except KeyError:
                age = 0 # Process died while we were playing...
            try:
                sp = SmallProcess(process.pid, 
                                  Users.get_user_name(process.uids.real), 
                                  process.cmdline,
                                  process.get_cpu_percent(interval = 0), 
                                  process.get_memory_percent(),
                                  age)
                add_process = False
                if sp.cpu_percent > min_cpu or sp.memory_percent > min_memory:
                    add_process = True
                if sp.age > max_age_in_seconds and sp.username not in Users.ignore:
                    add_process = True
                for prog in Users.cmdline_ignore_re_progs_from_pattern:
                    if prog.match(" ".join(sp.cmdline)) is not None:
                        add_process = False
                if sp.username in Users.always:
                    add_process = True
                if add_process:
                    Processes.add_process(sp)
            except psutil.error.NoSuchProcess:
                pass

def get_correct_processes_age():
    p = subprocess.Popen('ps -eo pid,etime'.split(' '),
                         stderr = subprocess.PIPE,
                         stdout = subprocess.PIPE)
    p.poll()
    ages = dict()
    for complex_line in p.stdout:
        line = re.sub('[ \t][ \t]*', ' ', complex_line[:-1])
        line = re.sub('^ ', '', line)
        if line.startswith('PID'):
            continue
        (pid, etime) = line.split(' ')
        if '-' in etime:
            (days, etime) = etime.split('-')
        else:
            days = 0
        age = int(days)*24*60*60
        fields = etime.split(':')
        if len(fields) == 3:
            (hours, minutes, seconds) = fields
            age += int(seconds) + 60 * (int(minutes) + 60 * int(hours))
        elif len(fields) == 2:
            (minutes, seconds) = fields
            age += int(seconds) + 60 * int(minutes)
        elif len(fields) == 2:
            seconds = fields
            age += int(seconds)
        ages[int(pid)] = age
    return ages

if __name__ == '__main__':
    nodename = socket.gethostbyaddr(socket.gethostname())[0].split('.')[0]
    count = 0
    time_interval = .5
    iterations = 5
    while count < iterations:
        ProcessesSnapshot()
        count += 1
        time.sleep(time_interval)

    output = open('/home/panstarrs/ipp/htdocs/clusterMonitor2/nodes/%s.html' % nodename, 'w')
    # output.write('<html>\n')
    # output.write('<head>\n')
    # output.write('<meta http-Equiv="Cache-Control" Content="no-cache">\n')
    # output.write('<meta http-Equiv="Pragma" Content="no-cache">\n')
    # output.write('<meta http-Equiv="Expires" Content="0">\n')
    # output.write('</head>\n')
    # output.write('<body>\n')
    now = datetime.datetime.now()
    output.write('<a name="%s"><h2>%s</h2></a>\n' % (nodename, nodename))
    output.write('Last refresh: %s / Time interval: %5.2f / #Iterations: %d<br/>\n'  % (now.isoformat().split('.')[0],
                                                                                        time_interval,
                                                                                        iterations));
    output.write('<table class="processTable">\n')
    output.write('<tr><th>pid (#samples)</th><th>owner</th><th>cpu%</th><th>mem%</th><th>age (hrs)</th><th>cmd line</th></tr>\n')
    for sprocess in Processes.statistics():
        output.write(' <tr>\n')
        output.write('  <td class="pid" title="pid (#samples)">%d (%d)</td>\n' % (sprocess.pid, 
                                                                                  sprocess.count))
        if sprocess.username not in Users.expected:
            alert_color = '#ff0000'
        else:
            alert_color = '#000000'
        output.write('  <td class="owner" title="owner" style="color:%s;">%s</td>\n' % (alert_color, 
                                                                                        sprocess.username))
        if sprocess.cpu_percent >= Users.min_cpu:
            alert_color = '#ff0000'
        else:
            alert_color = '#000000'
        output.write('  <td class="cpu" title="CPU usage" style="color:%s;">%6.2f</td>\n' % (alert_color, 
                                                                                             sprocess.cpu_percent) )
        if sprocess.memory_percent >= Users.min_memory:
            alert_color = '#ff0000'
        else:
            alert_color = '#000000'
        output.write('  <td class="memory" title="memory usage" style="color:%s;">%6.2f</td>\n' % (alert_color, 
                                                                                                   sprocess.memory_percent) )
        if sprocess.age >= Users.max_age_in_seconds:
            alert_color = '#ff0000'
        else:
            alert_color = '#000000'
        output.write('  <td class="age" title="age (in hours") style="color:%s;">%10.2f</td>\n' % (alert_color, 
                                                                                                   sprocess.age/3600.) )
        output.write('  <td class="cmdline" title="command line">%s</td>\n' % (' '.join(sprocess.cmdline)))
        output.write('</tr>\n')
    output.write('</table>\n')
    # output.write('</body>\n')
    # output.write('</html>\n')
    output.close()
