import subprocess, shlex, os, time
from StringIO import StringIO
import whatap.util.file_util as file_util
from whatap.util.ip_util import IPUtil as ip_util
from whatap.io.data_outputx import DataOutputX
import whatap.util.process_util as process_util
import whatap.agent.conf.configure as conf
import whatap.util.logging_util as logging_util
import psutil
import copy
import procgroup_rule

import multiprocessing

PROC_STATUSES = {
    psutil._pshpux.cext.SZOMB: psutil._common.STATUS_ZOMBIE,
}



class ProcessPerfInfo:
    def __init__(self):
        self.ppid = None
        self.pid = None
        self.cpu = None
        self.cpuUser = 0.0
        self.cpuSys = 0.0
        self.memoryBytes = None
        self.memoryPercent = None
        self.readBps = 0.0
        self.writeBps = 0.0
        self.cmd1 = None
        self.cmd2 = None
        self.readIops = 0.0
        self.writeIops = 0.0
        self.user = None
        self.state = None
        self.name = None
        self.createTime = None
        self.net = []
        self.file = []
        self.sharedMemory = 0
        self.pids = []

    def __str__(self):
        b = StringIO()
        b.write('ppid:')
        b.write(self.ppid)
        b.write('\npid')
        b.write(self.pid)
        b.write('\ncpu')
        b.write(self.cpu)
        b.write('\nmemory')
        b.write(self.memoryBytes)
        b.write('\nsharedMemory')
        b.write(self.sharedMemory)

        return b.getvalue()

class IpPort:
    def __init__(self, localip=0, localport=0, conn=0, ilocaladdr=0, inode=0):
        self.localip = localip
        self.localport = localport
        self.ilocaladdr = ilocaladdr
        self.iremoteaddr = 0
        self.conn = conn
        self.inode = inode

    def __str__(self):
        buf = StringIO()
        buf.write("localip=")
        buf.write(self.localip)
        buf.write("\nlocalport=")
        buf.write(self.localport)
        buf.write("\nilocaladdr=")
        buf.write(self.ilocaladdr)
        buf.write("\niremoteaddr=")
        buf.write(self.iremoteaddr)
        buf.write("\nconn=")
        buf.write(self.conn)
        buf.write("\ninode=")
        buf.write(self.inode)

        return buf.getvalue()


class FileInfo:
    def __init__(self, name=None, size=0):
        self.name = name
        self.size = size

def GetProc():
    procPerfs = getProcessPerfList()

    return procPerfs

class _ProcPerfCache:
    def __init__(self, cpu_times = None, proc_map = None, timestamp = None):
        self.cpu_times = cpu_times
        self.proc_map = proc_map
        if timestamp:
            self.timestamp = timestamp
        else:
            self.timestamp = time.time()

def parseNetwork(conns):
    pass


def parseOpenFile(files):
    flookup = {}
    fileusage =[]

    for f in files:
        if f.path and f.path.endswith('.log'):
            flookup[f.path] = 0
    for p in flookup.keys():
        filestat = os.stat(p)
        if filestat:
            fileusage.append(FileInfo(name=p, size=filestat.st_size))
    return fileusage

def calcPs(v, lastv, timediff):
    return float(v - lastv) / float(timediff)



proc_shm_cache = {}
oldProcPerfCache = None
cpuCore= psutil.cpu_count()

def getProcessPerfList():
    global proc_shm_cache
    proc_map = {}

    vmem = psutil.virtual_memory()
    totalMem = vmem.total
    pageSize = vmem.pagesize
    proc_perfs = []


    global oldProcPerfCache

    for proc in psutil.process_iter():
        with proc.oneshot():
            try:
                timestamp = time.time()
                iocounters = proc.io_counters()
                #cputimes = proc.cpu_times()
                proc_map[proc.pid] = (proc, iocounters, timestamp)

                if not oldProcPerfCache:
                    continue
                else:
                    if proc.pid == 2 or proc.ppid() == 2 or proc.pid not in oldProcPerfCache.proc_map:
                        continue

                    p = ProcessPerfInfo()
                    p.pid = proc.pid
                    p.ppid = proc.ppid()
                    p.user = proc.username()
                    p.memoryBytes = proc.memory_info().rss * pageSize
                    p.state = proc.status()

                    p.memoryPercent = 100.0 * p.memoryBytes / totalMem

                    p.cmd1 = proc.exe()
                    p.cmd2 = ' '.join(proc.cmdline())

                    p.createTime = int(proc.create_time())

                    _, old_iocounters, old_timestamp = oldProcPerfCache.proc_map[proc.pid]
                    timediff = timestamp - old_timestamp

                    p.cpu = proc.cpu_times().pcpu / cpuCore
                    #cputick 

                    p.readBps = calcPs(iocounters.total_bytes, old_iocounters.total_bytes, timediff)
                    p.writeBps = float(0)
                    p.readIops = float(0)
                    p.writeIops = float(0)

                    p.net = []
                    #p.net = parseNetwork(proc.connections())
                    if conf.GetConfig().serverProcessFDCheck:
                        p.file = parseOpenFile(proc.open_files())

                    proc_perfs.append(p)

            except psutil.ZombieProcess, e:
                continue
            except psutil.NoSuchProcess, e:
                continue

    procPerfCache = _ProcPerfCache(proc_map=proc_map)
    oldProcPerfCache = procPerfCache
    return proc_perfs
 
def GetTotalProc():
    try:
        proc, thread, defunct = psutil.proc_total_info()
        return (proc, thread, defunct)
    except:
        return (None, None, None)


oldGroupProcPerfCache = None
def GetTopNAndGroupProc(n):
    tick = os.sysconf("SC_CLK_TCK")
    cpu_threshold = 0.1
    rss_threshold = 20 * 1024 * 1024

    vmem = psutil.virtual_memory()
    pageSize = vmem.pagesize

    cpu_topn = n / 4 * 3
    memory_topn = n / 4 

    proc_map = {}
    proc_list = []

    timediff = 0
    now = time.time()
    global oldGroupProcPerfCache 
    if oldGroupProcPerfCache:
        oldTime = oldGroupProcPerfCache.timestamp
        timediff = float(now - oldTime)

    try:
        procInfo = psutil.proc_detail_info()

        #0 pid
        #1 ppid
        #2 name
        #3 cmdline  // split[0] = exe
        #4 username
        #5 rss
        #6 vms
        #7 createtime
        #8 status code
        #9 thread count
        #10 utime
        #11 stime
        #12 pcpu
        #13 ioch
        #...
        #20 cputick
        for proc in procInfo.values():
            if proc[2] == "wait" or proc[2] == "swapper":
                continue
            total_char_io = proc[13]
            now_utime = proc[10]
            now_stime = proc[11]
            total_cpu_tick = proc[20]
            pid = proc[0]

            proc_map[pid] = (total_char_io, total_cpu_tick, now_utime, now_stime)

            if not oldGroupProcPerfCache:
                continue

            if pid not in oldGroupProcPerfCache.proc_map:
                continue

            p = ProcessPerfInfo()
            p.pid = pid
            p.ppid = proc[1]
            p.user = proc[4]

            old_entry = oldGroupProcPerfCache.proc_map[pid]
            old_total_char_io = old_entry[0]
            old_total_cpu_tick = old_entry[1]
            old_utime = old_entry[2] if len(old_entry) > 2 else 0
            old_stime = old_entry[3] if len(old_entry) > 3 else 0

            if timediff > 0:
                p.cpu = float(total_cpu_tick - old_total_cpu_tick) / float(timediff) / float(tick) * 100 / cpuCore
                total_u_s = now_utime + now_stime
                if total_u_s > 0:
                    ratio_u = float(now_utime) / float(total_u_s)
                else:
                    ratio_u = 0.0
                p.cpuUser = p.cpu * ratio_u
                p.cpuSys = p.cpu * (1.0 - ratio_u)
            else:
                p.cpu = 0.0
                p.cpuUser = 0.0
                p.cpuSys = 0.0

            p.totalCharIO = float(total_char_io - old_total_char_io) / float(timediff)
            p.memoryBytes = proc[5] * pageSize
            p.vsz = proc[6] * pageSize
            p.cmd2 = proc[3]
            p.cmd1 = proc[3].split(' ')[0] if proc[3] else proc[2]
            p.createTime = int(proc[7])
            p.state = PROC_STATUSES.get(proc[8], '?')
            p.threadCount = proc[9]
            p.processCount = 1
            p.pids = [pid]

            proc_list.append(p)
    except Exception as e:
        logging_util.error("proc(TopN_Group) detail error : ", e)
        return None, None

    procPerf = _ProcPerfCache(proc_map = proc_map, timestamp = now)
    oldGroupProcPerfCache = procPerf
    procgroup_rule.gc_pid_cache(set(proc_map.keys()))
    cpu_top = sorted([proc for proc in proc_list if proc.cpu > cpu_threshold], key = lambda p: p.cpu, reverse = True)[:cpu_topn]
    cpu_top_pids = {proc.pid for proc in cpu_top}

    rss_top = sorted([proc for proc in proc_list if proc.pid not in cpu_top_pids and proc.memoryBytes > rss_threshold], key = lambda p: p.memoryBytes, reverse = True)[:memory_topn]

    topn = cpu_top + rss_top

    process_group = {}

    for proc in proc_list:
        if proc.state == 'zombie':
            key = ('defunct', proc.user)
            group_name = 'defunct'
        else:
            matched = procgroup_rule.find_custom_group(proc.pid, proc.user, proc.cmd2)
            if matched:
                key = (proc.user, matched)
                group_name = matched
            else:
                key = (proc.cmd1, proc.user)
                group_name = None

        if key not in process_group:
            p = copy.deepcopy(proc)
            p.name = group_name if group_name else proc.cmd1
            process_group[key] = p
        else:
            process_group[key].cpu += proc.cpu
            process_group[key].cpuUser += proc.cpuUser
            process_group[key].cpuSys += proc.cpuSys
            process_group[key].memoryBytes += proc.memoryBytes
            process_group[key].vsz += proc.vsz
            process_group[key].threadCount += proc.threadCount
            process_group[key].processCount += 1
            process_group[key].totalCharIO += proc.totalCharIO
            process_group[key].pids.extend(proc.pids)

    return (topn, process_group)







def test():
    while True:
        procs = GetProc()
        print
        procs
        if not procs:
            time.sleep(5)
            continue
        time.sleep(5)


if __name__ == '__main__':
    test()
