#!/usr/bin/env python
# watch all ip addresses for Telekom in Arnsberg for a icmp packet
#
import time, os, string, sys, socket, curses, atexit, select


LOGF="/home/andreas/public_html/messages/andreas"

hosts={}
num=0
upcount=0
PORT=50003
DEBUG=False
verbose=False

INTERVAL=10
GRACE=10

visual=0

msgs=[]

stdscr=None
win=None
msgw=None
msgwB=None
msgwHeight=10

class Host:
  def __init__(self, name, addr):
	global num
	self.name=name
	self.addr=addr
	self.num=num
	self.last=time.time()
	self.upcount=0
	self.up=1
	self.uppercent="n/a"
	self.state="up"
	self.statetime=self.last
	self.interval=INTERVAL
	num+=1

  def getstate(self):
	return self.state

  def newstate(self, state):
	self.state=state
	now=time.time()
	s=now-self.statetime
	self.statetime=now
	if state == "up":
		self.up=1
	elif state == "down":
		self.up=0
	if visual:
		displaystatetime(self.name)
	return s


#
#
def dur(sec):
	sec=int(sec)
	h=sec / 3600
	m=(sec - h * 3600) / 60
	s=(sec - h * 3600) % 60
	if h > 0:
		return  "%d:%02d:%02d" % (h, m, s)
	if m > 0:
		return  "%d:%02d" % (m, s)
	return  "0:%02d" % s


#
#
#
def addhost(name, addr):
	if hosts.has_key(name):
		del htab[hosts[name].addr]
		hosts[name].addr=addr
		if visual:
			displayaddr(name)
		htab[addr]=name
		log("%s, changed address to %s" % (name, addr))
	else:
		hosts[name]=Host(name, addr)
		s=hosts.keys()
		s.sort()
		x=0
		for n in s:
			hosts[n].num=x
			x+=1
		htab[addr]=name
		if visual:
			display()
		
#
#
#
def on_exit():
	if visual:
		exitcurses()
	logf.close()
	print "exit"


def initlog():
	global logf
	logf=open(LOGF,"a")
#
#
#
def initwin():
	global win, msgw, msgwB, msgwHeight

	maxY,maxX=stdscr.getmaxyx()

	begin_x = 0
	begin_y = 2
	height = len(htab)+2
	if DEBUG: log("initwin called with %d" % height)
	win = curses.newwin(height, maxX, begin_y, begin_x)
	a=win.border(0,0,0,0,0,0,curses.ACS_LTEE,curses.ACS_RTEE)

	msgwB = curses.newwin(0, 0, height+1, begin_x)
	msgwB.border(0,0,0,0,curses.ACS_LTEE,curses.ACS_RTEE)

	msgwHeight=maxY-height-3
	msgw = curses.newwin(msgwHeight, maxX-2, height+2, begin_x+1)
	msgw.setscrreg(0, msgwHeight-1)
	msgw.scrollok(1)

	stdscr.addstr(0,0, "WatchArnsberg Version 1.0", curses.A_BOLD)
	stdscr.refresh()
	msgwB.refresh()
#
#
#
def displaytime():
	if visual:
		maxY,maxX=stdscr.getmaxyx()
		stdscr.addstr(0,maxX-8, time.strftime("%H:%M:%S", time.localtime(now)), curses.A_BOLD)
#		stdscr.addstr(0,67,"%s.%04d" % (time.strftime("%H:%M:%S", time.localtime(now)),int((now-int(now))*10000) ), curses.A_BOLD)
	
	for h in hosts:
		if hosts[h].last != 0:
			attr=0
			if verbose:
				d=dur(now-hosts[h].last)
			else:
				d=hosts[h].getstate()
			if not hosts[h].up:
				d=hosts[h].getstate()
			elif now-hosts[h].last > hosts[h].interval+GRACE:
				d="overdue"
				if now-hosts[h].last > hosts[h].interval*5+GRACE:
					d="OVERDUE"
					if hosts[h].getstate() != "overdue":
						log("%s is overdue" % h)
						hosts[h].newstate("overdue")
				if now-hosts[h].last > hosts[h].interval*60+GRACE:
					attr=curses.A_BOLD
		if visual:
			win.addstr(hosts[h].num+1, 25, "%8s" %  d, attr)
			win.addstr(hosts[h].num+1, 53,  "%3s" % hosts[h].uppercent )
	if visual:
		win.refresh()
		stdscr.refresh()

#
#
#
def displaystatetime(h, refresh=1):
	win.addstr(hosts[h].num+1, 60, "%-17s" % time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(hosts[h].statetime)))
	if refresh:
		win.refresh()
#
#
#
def displayaddr(h, refresh=1):
	win.addstr(hosts[h].num+1, 35, "%-16s" % hosts[h].addr)
	if refresh:
		win.refresh()
#
#
#
def displaybody():
	for h in hosts:
		win.addstr(hosts[h].num+1, 1, "%-24s" %  (h))
		if hosts[h].addr != None:
			displayaddr(h, 0)
		if hosts[h].statetime != None:
			displaystatetime(h, 0)
	win.refresh()

#
#
#
def displaymsgs():
	global msgw, msgs
	y=0
	for m in msgs[len(msgs)-msgwHeight:]:
		msgw.addstr(y, 0, m)
		y+=1
	msgw.refresh()

#
#
#
def display():
	if visual:
		initwin()
	displaytime()
	if visual:
		displaybody()
		displaymsgs()

def log(m, service="heartbeat"):
	msg=time.strftime("%b %d %H:%M:%S")+": "+m+"\n"
	msgs.append(msg)
	m2="%d|%s|%s\n" % (now, service, m)
	logf.write(m2)
	logf.flush()
	if msgw != None:
		y,x=msgw.getyx()
#		if y >= msgwHeight-1:
#			msgw.scroll()
		msgw.addstr(msg)
		msgw.clrtoeol()
		msgw.refresh()

#
#
def fromaddr(name, addr, boot):
	global htab

	if not htab.has_key(addr):
		addhost(name, addr)
	host=hosts[htab[addr]]
	host.last=now
	if host.getstate() != "up":
		lasts=host.state
		d=host.newstate("up")
		log("%s, back after being %s for %s" % (host.name, lasts, dur(d)))
	host.upcount+=1

#
#
#
def readsock():
	global htab, win
	data, addr = sock.recvfrom(1024)
	pairs=string.split(data,';')
	boot=0
	shutdown=0
	name="unknown"
	msg=None
	interval=INTERVAL
	for pair in pairs:
		l=string.split(pair,"=")
		key=l[0]
		if len(l) != 2:
			val="0"
		else:
			val=l[1]
		if key == 'boot':
			boot+=1
		elif key == 'shutdown':
			shutdown+=1
		elif key == 'interval':
			interval=int(val)
		elif key == 'name':
			name=val
		elif key == 'msg':
			msg=val
		elif key == 'service':
			service=val
	if boot:
		log("%s booted" % name)
	if msg:
		log("%s msg: %s" % (name, msg),service=service)
	fromaddr(name, addr[0], boot)
	if shutdown:
		log("%s shutdown" % name)
		hosts[name].newstate("down")
	hosts[name].interval=interval


#
#
#
def dominute():
	global upcount
	upcount+=1
	
	for h in hosts:
		if upcount > 0:
			hosts[h].uppercent="%3.0f" % ((hosts[h].upcount*hosts[h].interval*100.0)/(upcount*INTERVAL))
	

def initcurses():
	global stdscr
	stdscr = curses.initscr()
	curses.noecho()
	curses.cbreak()
	stdscr.keypad(1)
	if DEBUG: sys.stderr.write("curses init done: %s\n" % stdscr)

def exitcurses():
	curses.nocbreak(); stdscr.keypad(0); curses.echo()
	curses.endwin()

#
# Main
#



initlog()

now=time.time()
startsec=int(now) % INTERVAL

htab={}
if visual:
	initcurses()
	display()
	stdscr.nodelay(1)

atexit.register(on_exit)
if DEBUG: log("Starting")

ilist=[]

sock=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("",PORT))

ilist.append(sock)

next=int(now)+1
sleep=next - now
while 1:
	if visual:
		c = stdscr.getch()
		if c == ord('c'): msgs=[]; display()
		elif c == ord('q'): break  # Exit the while()
		elif c == ord('d'): DEBUG=not DEBUG
		elif c == ord('v'): verbose=not verbose
#		elif c == ord('p'): PrintDocument()
#		elif c == ord('x'): x = y = 0

	try:
		sr=select.select(ilist,[],[],sleep)
		now=time.time()
	except KeyboardInterrupt:
		sys.exit(0)
	except select.error, value:
		if value[0] != 4:	# interrupted system call
			raise os.error, value
		if visual:
			exitcurses()
			initcurses()
			display()
		continue
	for fh in sr[0]:
		if fh == sock:
			readsock()
	if now >= next:
		next+=1
		if int(now) % INTERVAL == startsec:
			dominute()
		if visual:
#			stdscr.addstr(1 , 0,  "Now is %s, Next is %s" % (now, next))
			if DEBUG: 
				stdscr.addstr(1 , 0,  "len(htab) is %s, Next is %s" % (len(htab), next))
			else:
				stdscr.move(1 , 0)
				stdscr.clrtoeol()
		displaytime()
	sleep=next-now
	if DEBUG: sys.stderr.write("sleep=%s next=%s\n" % (sleep, next))

