#! /usr/bin/env python
###############################################################################
#                                                                             #
#   Copyright 2005 University of Cambridge Computer Laboratory.               #
#                                                                             #
#   This file is part of Nprobe.                                              #
#                                                                             #
#   Nprobe 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.                                       #
#                                                                             #
#   Nprobe 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.                              #
#                                                                             #
#   You should have received a copy of the GNU General Public License         #
#   along with Nprobe; if not, write to the Free Software                     #
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA #
#                                                                             #
###############################################################################


##############################################################################
##############################################################################
## 
##
## 
## 
##
## 

############################################################################ 
############################################################################ 


import string
import sys
import os
from sys import argv
import getopt

from Tkinter import *

import print_col
#import np_tcp
from nprobe import http_server_objtype_string, http_server_returncode_string, _inet_aton, intoa_string, n2hl
from np_lookups import np_hostname
from  np_longutil import *
from minmax import *
from np_plot import *
from np_widgets import PListB, XYSCROLL

from tkSimpleDialog import askstring
import re

############################################################################
############################################################################

#
# Raised by error in callback fn
#

class CallBackError:

    def __init__(self, val):
        self.val = val

    def __str__(self):
	return self.val

############################################################################
############################################################################ 


class Sellist:

    def __init__(self, draw_menu, log, obdict, callback, root=None,
                 anonymize=0):

	#self.path = path
	#self.logfun = logfun
        self.draw_menu = draw_menu
        self.obdict = obdict
        self.callback = callback
        self.anonymize = anonymize

        if anonymize:
            self.subob = re.compile('\d+\.\d+\.\d+\.\d+')
        else:
            self.subob = None

	self.make_items(log)

        if not root:
            self.root = Tk()
            self.root.winfo_toplevel().title('Analysis log')
        else:
            self.root = root
            
        self.root.geometry('500x500')
        self.root.minsize(200, 200)

	self.frame = Frame(self.root)
	self.frame.config(width=200, height=200, bg='red')
	self.frame.pack(side=LEFT, fill=BOTH, expand=1)


	self.toolbar = toolbar = Menu(self.frame)
	toolbar.configure(bg='grey')


	toolbar.add_command(label="Quit", command=self.quitfun,
                             background='grey', foreground='red')


	self.v = IntVar()
	toolbar.add_radiobutton(label="By_Id", background='grey',
				  variable=self.v, value=1, indicatoron=0, 
				  command=self.mode_id)
	toolbar.add_radiobutton(label="By_Type", background='grey',
				    variable=self.v, value=2, indicatoron=0, 
				    command=self.mode_type)
	toolbar.add_command(label="Expand", background='grey', 
			  command=self.expand)

	toolbar.add_command(label="Clear", background='grey', 
			  command=self.clear, state=DISABLED)

        db = self.make_drawmenu()
        toolbar.add_cascade(label='Draw', menu=db)
        if not callback:
            self.toolbar.entryconfig('Draw', state=DISABLED)

        # Edit entries
	toolbar.add_command(label="", background='grey', 
			  command=self.edit)

        self.root.config(menu=toolbar)

	self.do_draw = None

	self.expanded = 0
	self.expand_all = 0
        self.expand_see = 0
	self.expdict = {}

        self.listbox = PListB(self.frame, XYSCROLL) 
 	self.listbox.config(bg='white',  fg='black', selectborderwidth=0, 
 			       selectbackground='black', 
 			       selectforeground='white', 
 			       font = ('helvetica', 10), selectmode=EXTENDED)
 	self.listbox.pack(side=LEFT, fill=BOTH, expand=1)

        # Over-ride listbox method to give better results
        self.listbox.inner_see = self.listbox.see
        self.listbox.see = self.see
        
        #
        # Bind this to the root window so get listbox default behaviour
        # (ie selection) PLUS a poll to identify the selection
        #
        self.root.bind("<Button-1>", self.poll_now)
        self.listbox.bind("<Double-Button-1>", self.expand_lb)

	self.mode_type()
	self.poll() # start polling the list

	self.root.mainloop() 

############################################################################ 

    def quitfun(self):

	sys.exit(0) 

############################################################################

    def wheelscroll(self, ev):
        #print 'scroll button %d' % (ev.num)
        if ev.num == 4:
            self.listbox.yview(SCROLL, -1, UNITS)
        else:
            self.listbox.yview(SCROLL, 1, UNITS)

############################################################################

    def edit(self):

         items = self.listbox.curselection()
         if not items:
             return
         indx = int(items[0])

         s = self.listbox.get(indx)
         print s

         news = askstring('Log entry editor', 'Edit entry', initialvalue=s)

         if news == None:
             return
         elif news and news == s:
             return
         else:
              self.listbox.delete(indx)
              if not news:
                  return
              newlines = news.split('\\')
              for line in newlines:
                  line = line.rstrip()
                  self.listbox.insert(indx, line)
                  indx += 1

             

############################################################################

    def make_drawmenu(self):

        #
        # keep track of draw menu entries
        #

        self.draw_what = StringVar() # radio button variable
        # need these to keep track of button label for config indexing
	self.men_centry = None
	self.men_tentry = None
        self.draw_menu.append(['Cancel', ['Cancel'], 0])

        db = self.db = Menu(self.toolbar)
        db.configure(background='grey')
        self.button_groups = {}
        i = 0
        #print self.draw_menu
        for l, gs, s in self.draw_menu:
            db.add_radiobutton(label=l, command=self.drawfun, state=DISABLED,
                             indicatoron=0, variable= self.draw_what, value=l,
                               background='grey')
            if s:
                db.add_separator()
            # register the button with its group
            for g in gs: 
                try:
                    self.button_groups[g].append(l)
                except KeyError:
                    self.button_groups[g] = [l]
            i += 1

        return db 
            
            

############################################################################

    def poll_now(self, arg):

        for e in self.draw_menu:
            self.db.entryconfig(e[0], state=DISABLED)

        self.poll()           

############################################################################ 

    def poll(self):

	draw_conns = 0
	draw_trees = 0
	draw_ranks = 0
        
        try:
            items = map(int, self.listbox.curselection())
        except ValueError: pass
        
	#print '%d items selected' % (len(items))
	if len(items):
	    self.toolbar.entryconfig('Clear', state=NORMAL)
	    #self.toolbar.drawb.entryconfig('Cancel', state=NORMAL)
            active_buttons = {'Cancel': 1}
	    for i in items:
		entry = self.entrymap[i]
                if not entry[0]:
                    continue
                for ent in entry[0]:
                    type  = ent[2]
                    try:
                        buttons = self.button_groups[type] # activated for type
                        # register the buttons to activate
                        for button in buttons:
                            active_buttons[button] = 1
                    except KeyError:
                        pass

            # activate
            for button in active_buttons.keys():
                self.db.entryconfig(button, state=NORMAL)
 
            
	else:
	    self.toolbar.entryconfig('Clear', state=DISABLED)
            for e in self.draw_menu:
                self.db.entryconfig(e[0], state=DISABLED)

            self.pollid = self.root.after(100, self.poll)


	self.selections = items
        self.root.update_idletasks()

	
	#print 'poll'

############################################################################

    def see(self, indx):

        indx = min(indx+10, len(self.entrymap))

        self.listbox.inner_see(indx)

############################################################################

    def draw_cancel(self):
	
	for i in self.selections:
	    self.listbox.select_clear(i)
	self.selections = []

        for e in self.draw_menu:
            self.db.entryconfig(e[0], state=DISABLED)  

	self.poll() # re-start

	

############################################################################

    def drawfun(self):

        what = self.draw_what.get()
        #print '%x' % (what)

        if what == 'Cancel':
            self.draw_cancel()
            return

	items = self.selections
	#print items
	self.draw_cancel()

	want = {} # objects to draw
	for i in items:
	    entry = self.entrymap[i]
            if not entry[0]:
                continue
            for ent in entry[0]:
                tag = ent[0]
                if not want.has_key(ent):
                    want[ent] = None # just want the entry

        taglist = want.keys()
        try:
            self.callback(what, taglist, self.obdict)
        except CallBackError, s:
            print 'CallBackError \'%s\'' % (s)

	# Finally re-start poll
	self.poll()

############################################################################

    def rentries(self):

        #pass

        if len(self.rlist):
            # fields are: ([0] subtype, [1] label,
            #  [2] tag, [3] None)

            self.listbox.insert(END, 'Ranks') 
            self.entrymap.append((None, None))
            # delimiting blank line
            self.listbox.insert(END, '') 
            self.entrymap.append((None, None))

            this_subtype = '' 

            for e in self.rlist:
                subtype = e[0] 
                if subtype != this_subtype:
                    this_subtype = subtype
                    #print 'subtype', subtype
                    self.listbox.insert(END, '  ' + subtype) 
                    self.entrymap.append((None, subtype))
                if self.expdict.has_key(subtype):
                    label = '    ' + e[1]
                    tag = e[2]
                    estr = label.replace(subtype, '')
                    #print 'exp subtype', estr
                    if self.anonymize and label.find('Client') >= 0:
                        estr = self.subob.sub('xx.xx.xx.xx', estr)
                    #self.listbox.insert(END, label)
                    self.listbox.insert(END, estr) 
                    self.entrymap.append(([(tag, label, 'Rank '+subtype)], label))
                    # To support this add list of conns to obdict entry
                    
##                     if self.expdict.has_key(label):
##                         # NB changed to tag[0]
##                         for connid in self.obdict[tag][0]:
##                             lab = '      #%d' % (connid)
##                             self.listbox.insert(END, lab)
##                             self.entrymap.append(([(connid, lab, 'TCPConn')], lab))  
                       
            # delimiting blank line
            self.listbox.insert(END, '') 
            self.entrymap.append((None, None))


############################################################################

    def mentries(self):

        if len(self.mlist):
            
            for e in self.mlist:
                if not len(e):
                    # blank line
                    self.listbox.insert(END, '') 
                    self.entrymap.append((None, None))
                self.listbox.insert(END, e)
                self.entrymap.append((None, e))
                
            # delimiting blank line
            self.listbox.insert(END, '') 
            self.entrymap.append((None, None))


############################################################################

    def meentries(self):

        def bl():

            # insert a blank line
            self.listbox.insert(END, '') 
            self.entrymap.append((None, None))

        def entry(e, ind):
            
            if not len(e):
                # blank line
                self.listbox.insert(END, '') 
                self.entrymap.append((None, None))
                
            self.listbox.insert(END, '   '*ind + e[0]) 
            self.entrymap.append((None, e[0]))
            if self.expand_all or self.expdict.has_key(e[0]):
                ind += 1
                if ind < 3 and e[1]:
                    bl()
                for en in e[1]:
                    entry(en, ind)
                if ind < 3 and e[1]:
                    bl()
            

        for e in self.emlist:
            entry(e, 0)

        # delimiting blank line
        bl()


############################################################################

    def mxentries(self):

      ##   for i in self.xlist:
##             print i
        
        
        if len(self.xlist):
            for e in self.xlist:
                k = e[0]
                ents = e[1]
                self.listbox.insert(END, k + ' (%d)' % (len(e[1]))) 
                self.entrymap.append((ents, k))
                if self.expand_all or self.expdict.has_key(k):
                    for ent in ents:
                        if ent[1] != 'X':
                            # subsidiary entries
                            self.listbox.insert(END, '    ' + ent[1] + ' (%d)' % (len(ent[0])))
                            self.entrymap.append(([ent], ent[1]))

                # delimiting blank line
                self.listbox.insert(END, '') 
                self.entrymap.append((None, None))


############################################################################

    def tidy_end(self):
        
        self.listbox.insert(END, '') 
        self.entrymap.append((None, None))
        self.listbox.insert(END, ' - End -') 
        self.entrymap.append((None, None))
        self.listbox.insert(END, '') 
        self.entrymap.append((None, None))

        


############################################################################

    def mode_id(self):

	#print 'mode_id'

	self.mode = 'id'
	
	self.listbox.delete(0, END)

	self.entrymap = []

	self.mentries()
        self.meentries()
	self.rentries()
	self.mxentries()

	for i in self.ilist:
	    tagf = i[0]
            tag = tagf[0]
            label = tagf[1]
            #type = tagf[2]
            subents = i[1]
            
                
	    
            # major list box entry: tag - one per obj
            #print 'foo1', label
            if self.anonymize:
                anlab = self.subob.sub('xx.xx.xx.xx', label)
            else:
                anlab = label
            self.listbox.insert(END, anlab + ' (%d)' % (len(subents)))
            self.entrymap.append(([tagf], label))
	    if self.expand_all or self.expdict.has_key(label):
		for e in subents:
		    # subsidiary entries - msg types associated with obj
		    self.listbox.insert(END, '    ' + e)
		    self.entrymap.append(([tagf], None))

        self.tidy_end()
        
        # make sure any expanded selection stays in sight
        self.listbox.see(self.expand_see)

        self.toolbar.entryconfig('By_Id', state=DISABLED)
        self.toolbar.entryconfig('By_Type', state=NORMAL)
	    
############################################################################

    def mode_type(self):

	#print 'mode_type expand_all=%d' % (self.expand_all)

	self.mode = 'type'
	
	self.listbox.delete(0, END)

	self.entrymap = []

	self.mentries()
        self.meentries()
	self.rentries()
	self.mxentries()

	for i in self.slist:
            #print i
	    # one major entry per msg type
            label = i[0]
            subents = i[1]
	    self.listbox.insert(END, label + ' (%d)' % (len(subents)))
            #entrytype = subents[0][2]
	    self.entrymap.append((subents, label))
	    if self.expand_all or self.expdict.has_key(label):
		for tagf in subents:
                    #tag = e[0]
                    label = tagf[1]
                    if self.anonymize:
                        anlab = self.subob.sub('xx.xx.xx.xx', label)
                    else:
                        anlab = label
                    #type = e[2]
                    # subsidiary entries - objs associated with message type
                    self.listbox.insert(END, '    ' + anlab)
                    self.entrymap.append(([tagf], None))

        self.tidy_end()

        # make sure any expanded selection stays in sight
        self.listbox.see(self.expand_see)

        self.toolbar.entryconfig('By_Type', state=DISABLED)
        self.toolbar.entryconfig('By_Id', state=NORMAL)


############################################################################

    def expand(self):

	if self.expanded:
	    self.toolbar.entryconfig(4, label='Expand')
	    self.expdict = {}
	    self.expand_all = 0
	else:
	    self.toolbar.entryconfig(4, label='Shrink')
	    items = self.selections
            if len(items):
                self.expand_all = 0
                self.expand_see = items[0]
                for i in items:
                    #self.expand_all = 0
                    self.expdict[self.entrymap[i][1]] = 1
		#print 'exp added %s' % (self.entrymap[i][2])
	    else:
		self.expand_all = 1
                self.expand_see = 0

	self.expanded = ~self.expanded

	if self.mode == 'type':
	    self.mode_type()
	else:
	    self.mode_id()

	#restart
	self.poll()


############################################################################

    def expand_lb(self, arg): 

        items = self.listbox.curselection()
        try:
            items = map(string.atoi, items)
        except ValueError: pass

        if len(items):
            key = self.entrymap[items[0]][1]
            if self.expdict.has_key(key):
                del self.expdict[key]
            else:
                self.expdict[key] = 1

            self.expand_see = items[0]

            if self.expdict:
                self.toolbar.entryconfig(4, label='Shrink')
                self.expanded = 1
                

            if self.mode == 'type':
                self.mode_type()
            else:
                self.mode_id()


############################################################################

    def clear(self):
	
	self.root.after_cancel(self.pollid)
	#self.clearb.config(state=DISABLED)
	for i in self.selections:
	    self.listbox.select_clear(i)
	self.selections = []
	# restart
	self.poll()
	    

############################################################################

    def get_entries(self, log):

        #
        # Items list
        # entries = (type, label, tag, message, message extension)
        items = []

        find = string.find
        subob = self.subob

        for s in log:
            #print '\'%s\'' % (s)
            s = s.lstrip().rstrip()
            if not len(s):
                continue

            delim = find(s, ' ')
            etype = s[:delim]
            #print etype
            
            if etype == 'TCPConn':
                start = find(s, '#', delim) + 1
                delim = find(s, ' ', start)
                label = s[start:delim]
                mid = find(s, ':', delim)
                if mid == -1:
                    msg = s[delim]
                    msge = ''
                else:
                    msg = s[delim:mid]
                    msge = s[mid+2:]
                try:
                    # connection id?
                    tag = int(s[start:delim])
                except ValueError:
                    print '%s not a conn tag' % (s[start:delim])
                    sys.exit(1)
                items.append((etype, label, tag, msg, msge))
            elif etype == 'WebClient':
                start = find(s, '#', delim) + 1
                delim = find(s, ' ', start)
                label = s[start:delim]
                mid = find(s, ':', delim)
                if mid == -1:
                    msg = s[delim:]
                    msge = ''
                else:
                    msg = s[delim:mid]
                    msge = s[mid+2:]
                # client tree reference?
                tag = _inet_aton(s[start:delim])
                if tag == 0:
                    print '_inet_aton fail line = %s' % (s)
                    sys.exit(1)
                items.append((etype, label, tag, msg, msge))
            elif etype == 'Rank':
                start = delim+1
                delim = find(s, ' ', start)
                subtype = s[start:delim]
                items.append((etype, subtype, s[start:], s, None))
            elif etype == 'XMsg':
                start = delim+1
                fields = s[start:].split(':')
                items.append((etype, fields[0].lstrip().rstrip(),
                              fields[1].lstrip().rstrip(),
                              fields[2].lstrip().rstrip(),
                              fields[3].lstrip().rstrip()))

            else:
                # No recognisable type - just a message
                #print s
                #print s[:end]
                items.append(('Msg', s[:], None, None, None))
        #for i in items:
            #print i
        return items  

############################################################################

    def make_items(self, log):

        #
        # wa is array of caller-specific items, logfile contains generic
        # entries

        def by_tag0(a, b):
            
            return by_tag(a[0], b[0])

        def by_tag1(a, b):
            
            return by_tag(a[1], b[1])

        def by_tag(a, b):

            typea = a[2]
            typeb = b[2]
            if typea != typeb:
                # different types
                if typea > typeb:
                    return 1
                else:
                    return -1
            elif typea == 'TConn':
                # connections - tag is integer conn id
                return a[0] - b[0]
            else:
                # trees - label is dd address
                ssa = string.split(a[1], '.')
                ssb = string.split(b[1], '.')
                for i in [0, 1, 2, 3]:
                    sa = ssa[i]
                    sb = ssb[i]
                    lena = len(sa)
                    lenb = len(sb)
                    if lena == lenb:
                        if sa > sb:
                            return 1
                        elif sa < sb:
                            return -1
                        else:
                            continue
                    else:
                        return lena - lenb
                
        # Main fn starts here
        
	idict = {}
	sdict = {}
        # 'expandable' messages
        xdict = {}
	self.ilist = []
	self.slist = []
	self.rlist = []
	self.mlist = []
        self.xlist = []

        mlist = []
        self.emlist = []

        # get the stuff from the log file
        items  = self.get_entries(log)

	for w in items:
	    #print w
	    
            type = w[0]
	    if type == 'TCPConn' or type == 'WebClient':
                # fields are: ([0] type, [1] label, [2] object tag,
                #  [3] message, [4] message extension)
                l = w[1] # label 
		t = w[2] # object tag
		s = w[3] # msg
		d = w[4] # detail string


		# collect by object tag
		if not idict.has_key((t, l, type)):
		    idict[(t, l, type)] = {}
		ent = idict[(t, l, type)]
		# collect message type and count for this object
		msg = s+d
		if not ent.has_key(msg):
		    ent[msg] = 0
		ent[msg] += 1

		# collect by message key
		if not sdict.has_key(s):
		    sdict[s] = {}    
		ent = sdict[s]
		# collect objects for this message
		if not ent.has_key((t, l, type)):
		    ent[(t, l, type)] = 0
		ent[t, l, type] += 1

	    elif type == 'Rank':
                # fields are: ([0] type, [1] subtype, [2] label,
                #  [3] tag, [4] None)
		self.rlist.append((w[1], w[2], w[3])) # already ordered
            elif type == 'XMsg':
                # fields are: ([0] type, [1] label, [2] obtype,
                #  [3] tag, [4] sub-label)
                if not xdict.has_key(w[1]):
                    xdict[w[1]] = {}
                d = xdict[w[1]]
                key = (w[4], w[2])
                if not d.has_key(key):
                    d[key] = []
                d[key].append(w[3])
	    elif type == 'Msg':
		#print 'appending mssg %s' % (w[1])
		mlist.append(w[1])

	# produce list of connections/trees 
	tmplist = idict.items() # ([0]=(tag, label, type), [1]={msg strings})
	tmplist.sort(by_tag0)
	for e in tmplist:
	    head = e[0]
	    ents = []
	    l = e[1].keys()
	    l.sort()
	    for f in l:
		ents.append(f)
	    # fields: ((object tag, label, type), [msg strings])
	    self.ilist.append((head, ents))

	# produce list of msg keys
	tmplist = sdict.items() # ([0]=msg string, [1]={object tags})
	tmplist.sort()
	for e in tmplist:
	    head = e[0]
	    ents = []
	    l = e[1].keys()
	    #print l
	    l.sort(by_tag)
	    for f in l:
		ents.append(f)
	    # fields (key string, [(object tag, label, type)])
	    self.slist.append((head, ents))

        # produce list of calleable messages
        tmplist = xdict.items()
        for e in tmplist:
            head = e[0]
            ents = []
            l = e[1].items()
            l.sort()
            for f in l:
                ents.append((tuple(f[1]), f[0][0], f[0][1]))
            # fields are (label, [((taglist), sub-label, obtype)]
            self.xlist.append((head, ents))

        # process plain/expandable messages
        emdict = {}
        ord = 0 # used to maintain entry ordering
        for m in mlist:
            if m.find('|') < 0:
                self.mlist.append(m)
            else:
                #print m
                splits = m.split('|')
                d = emdict
                for split in splits:
                    split = split.lstrip().rstrip()
                    if split:
                        d = d.setdefault(split, (ord, {}))[1]
                ord += 1

        self.listify(emdict, self.emlist)
	    
##############################################################################

    def listify(self, dict, list):

        def by_ord(a, b):
            return a[1][0] - b[1][0]

        l = dict.items()
        l.sort(by_ord)
        for e in l:
            newl = []
            if len(e[1][1]):
                list.append(('%s (%d)' % (e[0], len(e[1][1])), newl))
            else:
                list.append(('%s' % (e[0]), newl))
            self.listify(e[1][1], newl)
            
	    
##############################################################################
##############################################################################

def usage(scriptname):
    print "usage: " + scriptname + "<log file>"

    sys.exit(1)

############################################################################

#
# Fire up a NoteSel to summarise an nprobe analysis log
#

	
def main():

    scriptname = os.path.basename(argv[0])

    try:
        optlist, args = getopt.getopt(sys.argv[1:],
                                      '')

    except getopt.error, s:
        print '%s: %s' % (scriptname, s)
        usage(scriptname)
        sys.exit(1)

    optstr = ''

    for opt in optlist:
        pass

    if len(args) != 1:
        usage(scriptname)

    try:
        f = open(args[0], 'r')
    except IOError, s:
        print 'Can\'t open log file:', s
        sys.exit(1)

    ents = f.readlines()
    
    Sellist([], ents, None, None)


# Call main when run as script
if __name__ == '__main__':
    
    main()
        

	
