#!/usr/bin/env python
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Library General Public License as published by
# the Free Software Foundation; version 2 only
#
# This program 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 Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# Copyright 2004 Dag Wieers <dag@wieers.com>

#from __future__ import generators
import fcntl, struct, termios
import os, sys, re, getopt, time
import ConfigParser, urlparse, signal, resource

VERSION = '0.5.5'

enable = ('yes', 'on', 'true', '1')
disable = ('no', 'off', 'false', '0')

class Options:
	def __init__(self, args):
		self.count = -1
		self.delay = 1
		self.disklist = None
		self.full = False
		self.integer = False
		self.intlist = None
		self.netlist = None
		self.color = True
		self.update = True
		self.header = True

		try:
			opts, args = getopt.getopt (args, 'acdfghilmnpstvyD:I:M:N:',
				['all', 'cpu', 'disk', 'help', 'int', 'load', 'mem', 'net', 'page',
				'proc', 'swap', 'sys', 'tcp', 'time', 'udp', 'version', 'vmstat',
				'full', 'integer', 'mods', 'modules', 'nocolor', 'noheader', 'noupdate'])
		except getopt.error, exc:
			print 'dstat: %s, try dstat -h for a list of all the options' % str(exc)
			sys.exit(1)

		self.modlist = []

		for opt, arg in opts:
			if opt in ['-c', '--cpu']:
				self.modlist.append('cpu')
			elif opt in ['-d', '--disk']:
				self.modlist.append('disk')
			elif opt in ['-D']:
				self.disklist = arg.split(',')
			elif opt in ['-i', '--int']:
				self.modlist.append('int')
			elif opt in ['-g', '--page']:
				self.modlist.append('page')
			elif opt in ['-I']:
				self.intlist = arg.split(',')
			elif opt in ['-m', '--mem']:
				self.modlist.append('mem')
			elif opt in ['-M', '--mods', '--modules']:
				self.modlist = self.modlist + arg.split(',')
			elif opt in ['-l', '--load']:
				self.modlist.append('load')
			elif opt in ['-n', '--net']:
				self.modlist.append('net')
			elif opt in ['-N']:
				self.netlist = arg.split(',')
			elif opt in ['-p', '--proc']:
				self.modlist.append('proc')
			elif opt in ['-s', '--swap']:
				self.modlist.append('swap')
			elif opt in ['--tcp']:
				self.modlist.append('tcp')
			elif opt in ['-t', '--time']:
				self.modlist.append('time')
			elif opt in ['--udp']:
				self.modlist.append('udp')
			elif opt in ['-y', '--sys']:
				self.modlist.append('sys')

			elif opt in ['-a', '--all']:
				self.modlist = self.modlist + [ 'cpu', 'disk', 'net', 'page', 'sys', 'load' ]
			elif opt in ['-v', '--vmstat']:
				self.modlist = self.modlist + [ 'proc', 'mem', 'page', 'disk', 'sys', 'cpu' ]
				self.disklist = ('total',)

			elif opt in ['-f', '--full']:
				self.full = True
			elif opt in ['--integer']:
				self.integer = True
			elif opt in ['--nocolor']:
				self.color = False
				self.update = False
			elif opt in ['--noheader']:
				self.header = False
			elif opt in ['--noupdate']:
				self.update = False

			elif opt in ['-h', '--help']:
				self.usage()
				self.help()
				sys.exit(0)
			elif opt in ['-v', '--version']:
				self.version()
				sys.exit(0)

		if not self.modlist:
			self.modlist = [ 'cpu', 'disk', 'net', 'sys', 'page' ]

		if len(args) > 0:
			self.delay = int(args[0])

		if len(args) > 1:
			self.count = int(args[1])

		if self.delay == 0:
				die(6, 'Delay must be an integer, greater than zero')

	def version(self):
		print 'Dstat %s' % VERSION
		print 'Written by Dag Wieers <dag@wieers.com>'
		print 'Homepage at http://dag.wieers.com/home-made/dstat/'
		print
		print 'Platform %s/%s' % (os.name, sys.platform)
		print 'Kernel %s' % os.uname()[2]
		print 'Python %s' % sys.version

	def usage(self):
		print 'usage: dstat [-afv] [-cdgilmnpsty] [-D..] [-I..] [-N..] [delay [count]]'

	def help(self):
		print '''Versatile tool for generating system resource statistics

Dstat options:
  -c, --cpu                enable cpu stats
  -d, --disk               enable disk stats
     -D total,hda             include hda and total
  -g, --page               enable page stats
  -i, --int                enable interrupt stats
     -I 5,eth2                include int5 and interrupt used by eth2
  -l, --load               enable load stats
  -m, --mem                enable memory stats
  -n, --net                enable network stats
     -N eth1,total            include eth1 and total
  -p, --proc               enable process stats
  -s, --swap               enable swap stats
  -t, --time               enable time counter
  -y, --sys                enable system stats

  -M stat1,stat2           enable specific stats
     --mods stat1,stat2

    Possible stats are:
      cpu, disk, page, int, load, mem, net, proc,
      swap, sys, tcp, time, udp

  -a, --all          equals -cdngyl
  -v, --vmstat       equals -pmgdsc -D total

  -f, --full         expand -D, -I and -N discovery lists
  --integer          show integer values
  --nocolor          disable colors (implies --noupdate)
  --noheader         disable repetitive headers
  --noupdate         disable intermediate updates when delay > 1
'''

class Config:
	def __init__(self):
		self.configfile = op.configfile
		self.cfg = ConfigParser.ConfigParser()

		(s,b,p,q,f,o) = urlparse.urlparse(self.configfile)
		if s in ('http', 'ftp', 'file'):
			configfh = urllib.urlopen(self.configfile)
			try:
				self.cfg.readfp(configfh)
			except ConfigParser.MissingSectionHeaderError, e:
				die(6, 'Error accessing URL: %s' % self.configfile)
		else:
			if os.access(self.configfile, os.R_OK):
				try:
					self.cfg.read(self.configfile)
				except:
					die(7, 'Syntax error reading file: %s' % self.configfile)
			else:
				die(6, 'Error accessing file: %s' % self.configfile)

	def getoption(self, section, option, var):
		"Get an option from a section from configfile"
		try:
			var = self.cfg.get(section, option)
			info(3, 'Setting option %s in section [%s] to: %s' % (option, section, var))
		except ConfigParser.NoSectionError, e:
			info(4, 'Failed to find section [%s] in %s' % (section, op.configfile))
		except ConfigParser.NoOptionError, e:
#			info(4, 'Failed to find option %s in [%s], set to default: %s' % (option, section, var))
			info(4, 'Setting option %s in section [%s] to: %s (default)' % (option, section, var))
		return var

class dstat:
	def init(self):
#		if not self.check(): return

		self.title1 = self.title1()
		self.title2 = self.title2()

		### Initialise default variables
		self.val = {}
		self.cn1 = {}
		self.cn2 = {}
		for i in self.vars: self.val[i] = self.cn1[i] = self.cn2[i] = 0

	def width(self):
		return len(self.vars) * self.len + len(self.vars) - 1

	def title1(self):
		max = self.width()
		return ansi['darkblue'] + self.name[0:max-2].center(max).replace(' ', '-') + ansi['default']

	def title2(self):
		ret = ''
		for str in self.nick:
			ret = ret + str[0:self.len].center(self.len).replace(' ', '_')
			if str != self.nick[-1]: ret = ret + ' '
		return ansi['blue'] + ret + ansi['default']

	def check(self):
		return True

	def stats(self):
		pass

	def show(self):
		sep = ' '
		for name in self.vars:
			if self.format == '%s':
					sys.stdout.write(self.format % conv(self.len, self.val[name]))
			elif self.format in ('%s %s', '%s:%s', '%s-%s'):
					sys.stdout.write(self.format % convlist(self.len, self.val[name]))
					sep = '|'
			else:
					sys.stdout.write(self.format % self.val[name])
			if name != self.vars[-1]:
				sys.stdout.write(sep)

class dstat_cpu(dstat):
	def __init__(self):
		self.name = 'cpu usage'
		self.vars = ('user', 'sys', 'idle', 'wait')
		self.len = 3
		self.format = '%3d'
		self.nick = ( 'usr', 'sys', 'idl', 'wai' )
		self.init()
		self.cn1['total'] = 0

	def check(self):
		if os.path.exists('/proc/stat'):
			l = open('/proc/stat', 'r').readline().split()
			if len(l) > 5:
				return True
		return False

	def stats(self):
		for line in open('/proc/stat', 'r').readlines():
			l = line.split()
			if len(l) <= 5 or l[0] != 'cpu': continue
			self.cn2['user'] = long(l[1]) + long(l[2])
			self.cn2['sys'] = long(l[3])
			self.cn2['idle'] = long(l[4])
			self.cn2['wait'] = long(l[5])
			self.cn2['total'] = self.cn2['user'] + self.cn2['sys'] + self.cn2['idle'] + self.cn2['wait']
		for name in self.vars:
			self.val[name] = (100.0 * (self.cn2[name] - self.cn1[name]) + (self.cn2['total'] - self.cn1['total']) / 2) / (self.cn2['total'] - self.cn1['total'])
		self.cn1.update(self.cn2)

class dstat_cpu24(dstat):
	def __init__(self):
		self.name = 'cpu usage'
		self.vars = ('user', 'sys', 'idle')
		self.len = 3
		self.format = '%3d'
		self.nick = ( 'usr', 'sys', 'idl')
		self.init()
		self.cn1['total'] = 0

	def check(self):
		if os.path.exists('/proc/stat'):
			l = open('/proc/stat', 'r').readline().split()
			if len(l) == 5:
				return True
		return False

	def stats(self):
		for line in open('/proc/stat', 'r').readlines():
			l = line.split()
			if len(l) < 4 or l[0] != 'cpu': continue
			self.cn2['user'] = long(l[1]) + long(l[2])
			self.cn2['sys'] = long(l[3])
			self.cn2['idle'] = long(l[4])
			self.cn2['total'] = self.cn2['user'] + self.cn2['sys'] + self.cn2['idle']
		for name in self.vars:
			self.val[name] = (100.0 * (self.cn2[name] - self.cn1[name]) + (self.cn2['total'] - self.cn1['total']) / 2) / (self.cn2['total'] - self.cn1['total'])
		self.cn1.update(self.cn2)

class dstat_disk(dstat):
	def __init__(self):
		if not self.check(): return
		self.format = '%s %s'
		self.len = 11
		self.name = 'disk i/o'
		if op.disklist:
			self.vars = op.disklist
		else:
			self.vars = self.discover()
		### Temporary hardcoded for my own project
		self.diskset = {
			'local': ('sda', 'hda', 'hdc'),
			'lores': ('sdb', 'sdc', 'sdd', 'sde', 'sdf', 'sdg', 'sdh', 'sdi', 'sdj', 'sdk', 'sdl', 'sdm', 'sdn', 'sdo', 'sdp', 'sdq', 'sdr', 'sds', 'sdt'),
			'hires': ('sdu', 'sdv', 'sdw', 'sdx', 'sdy', 'sdz', 'sdaa', 'sdab', 'sdac', 'sdad' ),
		}
		self.nick = self.vars
		self.init()

		for name in self.vars: self.cn1[name] = (0, 0); self.cn1['total'] = (0, 0)
		for name in self.vars: self.cn2[name] = (0, 0); self.cn2['total'] = (0, 0)
		for name in self.vars: self.val[name] = (0, 0); self.val['total'] = (0, 0)

	def discover(self):
		retlist = []
		for line in open('/proc/diskstats', 'r').readlines():
			l = line.split()
			if len(l) < 13 or l[3] == '0': continue
			name = l[2]
			if not re.match('^(ram\d+|loop\d+)$', name):
				retlist.append(name)
		if not op.full and len(retlist) > 2: retlist = retlist[0:2]
		retlist.sort()
		return retlist

	def check(self):
		if os.path.exists('/proc/diskstats'):
			return True
		return False

	def stats(self):
		for name in self.vars: self.cn2[name] = (0, 0)
		for line in open('/proc/diskstats', 'r').readlines():
			l = line.split()
			if len(l) < 13: continue
			name = l[2]
			if name in self.vars:
				self.cn2[name] = ( self.cn2[name][0] + long(l[5]), self.cn2[name][1] + long(l[9]) )
			for set in self.vars:
				if set in self.diskset.keys() and name in self.diskset[set]:
					self.cn2[set] = ( self.cn2[set][0] + long(l[5]), self.cn2[set][1] + long(l[9]) )
			self.cn2['total'] = ( self.cn2['total'][0] + long(l[5]), self.cn2['total'][1] + long(l[9]) )
		if update:
			for name in self.cn2.keys():
				self.val[name] = ( 
					(self.cn2[name][0]-self.cn1[name][0]) * 512.0 / step,
					(self.cn2[name][1]-self.cn1[name][1]) * 512.0 / step,
				)
		if step == op.delay:
			self.cn1.update(self.cn2)

class dstat_disk24(dstat_disk):
	def discover(self):
		retlist = []
		for line in open('/proc/partitions', 'r').readlines():
			l = line.split()
			if len(l) < 15 or l[0] == 'major' or int(l[1]) % 16 != 0: continue
			name = l[3]
			if not re.match('^(ram\d+|loop\d+|name)$', name):
				retlist.append(name)
		if not op.full and len(retlist) > 2: retlist = retlist[0:2]
		retlist.sort()
		return retlist

	def check(self):
		if os.path.exists('/proc/partitions') and not os.path.exists('/proc/diskstats'):
			l = open('/proc/partitions', 'r').readline().split()
			if len(l) > 4:
				return True
		return False

	def stats(self):
		for name in self.vars: self.cn2[name] = (0, 0)
		for line in open('/proc/partitions', 'r').readlines():
			l = line.split()
			if len(l) < 15: continue
			name = l[3]
			if name == 'name': continue
			if name in self.vars:
				self.cn2[name] = ( self.cn2[name][0] + long(l[6]), self.cn2[name][1] + long(l[10]) )
			for set in self.vars:
				if set in self.diskset.keys() and name in self.diskset[set]:
					self.cn2[set] = ( self.cn2[set][0] + long(l[6]), self.cn2[set][1] + long(l[10]) )
			self.cn2['total'] = ( self.cn2['total'][0] + long(l[6]), self.cn2['total'][1] + long(l[10]))
		if update:
			for name in self.cn2.keys():
				self.val[name] = ( 
					(self.cn2[name][0]-self.cn1[name][0]) * 512.0 / step,
					(self.cn2[name][1]-self.cn1[name][1]) * 512.0 / step,
				)
		if step == op.delay:
			self.cn1.update(self.cn2)

class dstat_disk24old(dstat_disk24):
	def discover(self):
		retlist = []
		for line in open('/proc/partitions', 'r').readlines():
			l = line.split()
			if len(l) != 4 or l[0] == 'major' or int(l[1]) % 16 != 0: continue
			name = l[3]
			if not re.match('^(ram\d+|loop\d+|name)$', name):
				retlist.append(name)
		if not op.full and len(retlist) > 2: retlist = retlist[0:2]
		retlist.sort()
		return retlist

	def check(self):
		if os.path.exists('/proc/stat') and \
			not os.path.exists('/proc/diskstats'):
			if os.path.exists('/proc/partitions'):
				l = open('/proc/partitions', 'r').readline().split()
				if len(l) <= 4:
					return True
			else:
				return True
		return False

	def stats(self):
		for name in self.vars: self.cn2[name] = (0, 0)
		for line in open('/proc/stat', 'r').readlines():
			l = line.split(':')
			if len(l) < 3: continue
			name = l[0]
			if name == 'disk_io':
				for pair in line.split()[1:]:
					m = re.match('^\((\d+),(\d+)\):\(\d+,\d+,(\d+),\d+,(\d+)\)$', pair)
					if m:
						l = m.groups()
						name = dev(int(l[0]), int(l[1]))
						self.cn2[name] = ( long(l[2]), long(l[3]) )
						for set in self.vars:
							if set in self.diskset.keys() and name in self.diskset[set]:
								self.cn2[set] = ( self.cn2[set][0] + long(l[2]), self.cn2[set][1] + long(l[3]) )
						self.cn2['total'] = ( self.cn2['total'][0] + long(l[2]), self.cn2['total'][1] + long(l[3]) )
		if update:
			for name in self.cn2.keys():
				self.val[name] = (
					(self.cn2[name][0]-self.cn1[name][0]) * 512.0 / step,
					(self.cn2[name][1]-self.cn1[name][1]) * 512.0 / step,
				)
		if step == op.delay:
			self.cn1.update(self.cn2)

class dstat_int(dstat):
	def __init__(self):
		self.len = 5
		self.format = '%5d'
		self.name = 'interrupts'
		if op.intlist:
			self.vars = op.intlist
		else:
			self.vars = self.discover()
		self.nick = self.vars
		self.init()

	def discover(self):
		retlist = []
		for line in open('/proc/interrupts', 'r').readlines():
			l = line.split()
			if len(l) < 1: continue
			name = l[0].split(':')[0]
			if name in ('0', '1', '2', '8', 'NMI', 'LOC', 'MIS', 'CPU0'): continue
			if len(l) > 1 and l[1] != '0':
				retlist.append(name)
		if not op.full and len(retlist) > 3: retlist = retlist[-4:-1]
		return retlist

	def check(self):
		if os.path.exists('/proc/interrupts'):
			return True
		return False

	def stats(self):
		for line in open('/proc/interrupts', 'r').readlines():
			l = line.split()
			if len(l) < 2: continue
			name = l[0].split(':')[0]
			if name in self.vars:
				self.cn2[name] = long(l[1])
			elif len(l) > 2 + procs:
				for hw in self.vars:
					for mod in l[2+procs:]:
						self.cn2[mod] = long(l[1])
		if update:
			for name in self.cn2.keys():
				self.val[name] = (self.cn2[name]-self.cn1[name]) * 1.0 / step
		if step == op.delay:
			self.cn1.update(self.cn2)

class dstat_load(dstat):
	def __init__(self):
		self.len = 5
		self.format = '%5.2f'
		self.name = 'load avg'
		self.nick = ('1m', '5m', '15m')
		self.vars = ('load1', 'load5', 'load15')
		self.init()

	def check(self):
		if os.path.exists('/proc/loadavg'):
			return True
		return False

	def stats(self):
		for line in open('/proc/loadavg', 'r').readlines():
			l = line.split()
			if len(l) < 3: continue
			self.val['load1'] = float(l[0])
			self.val['load5'] = float(l[1])
			self.val['load15'] = float(l[2])

class dstat_mem(dstat):
	def __init__(self):
		self.name = 'memory usage'
		self.len = 5
		self.format = '%s'
		self.nick = ('used', 'free', 'buff', 'cach')
		self.vars = ('MemUsed', 'MemFree', 'Buffers', 'Cached')
		self.init()

	def check(self):
		if os.path.exists('/proc/meminfo'):
			return True
		return False

	def stats(self):
		for line in open('/proc/meminfo', 'r').readlines():
			l = line.split()
			if len(l) < 2: continue
			name = l[0].split(':')[0]
			if name in self.vars + ('MemTotal',):
				self.val[name] = long(l[1]) * 1024.0
		self.val['MemUsed'] = self.val['MemTotal'] - self.val['MemFree'] - self.val['Buffers'] - self.val['Cached']

class dstat_net(dstat):
	def __init__(self):
		self.name = 'network'
		self.len = 11
		self.format = '%s %s'
		if op.netlist:
			self.vars = op.netlist
		else:
			self.vars = self.discover()
		self.nick = self.vars
		self.init()

		for name in self.vars: self.cn1[name] = (0, 0); self.cn1['total'] = (0, 0)
		for name in self.vars: self.cn2[name] = (0, 0); self.cn2['total'] = (0, 0)
		for name in self.vars: self.val[name] = (0, 0); self.val['total'] = (0, 0)

	def discover(self):
		retlist = []
		for line in open('/proc/net/dev', 'r').readlines():
			l = line.split()
			if len(l) < 2: continue
			name = l[0].split(':')[0]
			if l[1] == '0': continue
			if not re.match('^(Inter-\||face|lo)$', name):
				retlist.append(name)
		if not op.full and len(retlist) > 2: retlist = retlist[0:2]
		retlist.sort()
		return retlist

	def check(self):
		if os.path.exists('/proc/net/dev'):
			return True
		return False

	def stats(self):
		self.cn2['total'] = (0, 0)
		for line in open('/proc/net/dev', 'r').readlines():
			l = line.split()
			if len(l) < 1: continue
			l2 = l[0].split(':')
			if len(l2) < 1: continue
			name = l2[0]
			if len(l2) > 1 and l2[1].strip():
				l = l2[1:] + l[1:]
			else:
				l = l[1:]
			if name in (self.vars) :
				self.cn2[name] = ( long(l[0]), long(l[8]) )
			if not re.match('^(Inter-\||face|lo)$', name):
				self.cn2['total'] = ( self.cn2['total'][0] + long(l[0]), self.cn2['total'][1] + long(l[8]))
		if update:
			for name in self.cn2.keys():
				self.val[name] = ( 
					(self.cn2[name][0]-self.cn1[name][0]) * 1.0 / step,
					(self.cn2[name][1]-self.cn1[name][1]) * 1.0 / step,
				 )
		if step == op.delay:
			self.cn1.update(self.cn2)

class dstat_page(dstat):
	def __init__(self):
		self.name = 'paging'
		self.format = '%s'
		self.len = 5
		self.vars = ('pswpin', 'pswpout')
		self.nick = ('in', 'out')
		self.init()

	def check(self):
		if os.path.exists('/proc/vmstat'):
			return True
		return False

	def stats(self):
		for line in open('/proc/vmstat', 'r').readlines():
			l = line.split()
			if len(l) < 2: continue
			name = l[0]
			if name in self.vars:
				self.cn2[name] = long(l[1])
		if update:
			for name in self.vars:
				self.val[name] = (self.cn2[name]-self.cn1[name]) * pagesize * 1.0 / step
		if step == op.delay:
			self.cn1.update(self.cn2)

class dstat_page24(dstat_page):
	def check(self):
		if os.path.exists('/proc/stat') and not os.path.exists('/proc/vmstat'):
			return True
		return False

	def stats(self):
		for line in open('/proc/stat', 'r').readlines():
			l = line.split()
			if len(l) < 3: continue
			name = l[0]
			if name == 'swap':
				self.cn2['pswpin'] = long(l[1])
				self.cn2['pswpout'] = long(l[2])
		if update:
			for name in self.vars:
				self.val[name] = (self.cn2[name]-self.cn1[name]) * pagesize * 1.0 / step
		if step == op.delay:
			self.cn1.update(self.cn2)

class dstat_proc(dstat):
	def __init__(self):
#		self.name = 'procs'
		self.name = 'procs'
		self.format = '%2d'
		self.len = 2
		self.vars = ('procs_running', 'procs_blocked', 'processes')
#		self.vars = ('procs_running', 'procs_blocked')
		self.nick = ('ru', 'bl', 'nw')
#		self.nick = ('ru', 'bl')
		self.init()

	def check(self):
		if os.path.exists('/proc/stat'):
			return True
		return False

	def stats(self):
		for line in open('/proc/stat', 'r').readlines():
			l = line.split()
			if len(l) < 2: continue
			name = l[0]
			if name in self.vars:
				self.val[name] = long(l[1])
			if name in ('processes',):
				self.val['processes'] = 0
				self.cn2[name] = long(l[1])
		if self.val['procs_running'] > 0:
			self.val['procs_running'] = self.val['procs_running'] - 1
		if update:
			self.val['processes'] = (self.cn2['processes'] - self.cn1['processes']) / step
		if step == op.delay:
			self.cn1.update(self.cn2)

class dstat_swap(dstat):
	def __init__(self):
		self.name = 'swap'
		self.len = 5
		self.format = '%s'
		self.nick = ('used', 'free')
		self.vars = ('SwapUsed', 'SwapFree')
		self.init()

	def check(self):
		if os.path.exists('/proc/meminfo'):
			return True
		return False

	def stats(self):
		for line in open('/proc/meminfo', 'r').readlines():
			l = line.split()
			if len(l) < 2: continue
			name = l[0].split(':')[0]
			if name in self.vars + ('SwapTotal',):
				self.val[name] = long(l[1]) * 1024.0
		self.val['SwapUsed'] = self.val['SwapTotal'] - self.val['SwapFree']

class dstat_sys(dstat):
	def __init__(self):
		self.name = 'system'
		self.format = '%5d'
		self.len = 5
		self.nick = ('int', 'csw')
		self.vars = ('intr', 'ctxt')
		self.names = { 'intr': 'int', 'ctxt': 'csw' }
		self.init()

	def check(self):
		if os.path.exists('/proc/stat'):
			return True
		return False

	def stats(self):
		for line in open('/proc/stat', 'r').readlines():
			l = line.split()
			if len(l) < 2: continue
			name = l[0]
			if name in self.vars:
				self.cn2[name] = long(l[1])
		if update:
			for name in self.vars:
				self.val[name] = (self.cn2[name]-self.cn1[name]) * 1.0 / step
		if step == op.delay:
			self.cn1.update(self.cn2)

	def show(self):
		for name in self.vars:
			sys.stdout.write(self.format % self.val[name])
			if name != self.vars[-1]:
				sys.stdout.write(' ')

class dstat_tcp(dstat):
	def __init__(self):
		self.name = 'tcp'
		self.format = '%3d'
		self.len = 3
		self.nick = ('lis', 'act', 'syn', 'tim')
		self.vars = ('listen', 'established', 'syn_sent', 'time_wait')
		self.init()

	def check(self):
		if os.path.exists('/proc/net/tcp'):
			return True
		return False

	def stats(self):
		self.val['listen'] = self.val['established'] = self.val['syn_sent'] = self.val['time_wait'] = 0
		for line in open('/proc/net/tcp', 'r').readlines():
			l = line.split()
			if len(l) < 12: continue
			if l[3] == '0A': self.val['listen'] = self.val['listen'] + 1
			elif l[3] == '01': self.val['established'] = self.val['established'] + 1
			elif l[3] == '02': self.val['syn_sent'] = self.val['syn_sent'] + 1
			elif l[3] == '06': self.val['time_wait'] = self.val['time_wait'] + 1

	def show(self):
		for name in self.vars:
			sys.stdout.write(self.format % self.val[name])
			if name != self.vars[-1]:
				sys.stdout.write(' ')

class dstat_time(dstat):
	def __init__(self):
		self.name = 'time'
		self.format = '%10d'
		self.len = 10
		### Nice for debugging timer
#		self.format = '%13.3f'
#		self.len = 14
		self.nick = ('epoch',)
		self.vars = ('epoch',)
		self.init()

	def stats(self):
		self.val['epoch'] = time.time()

	def show(self):
		sys.stdout.write(self.format % self.val['epoch'])

class dstat_udp(dstat):
	def __init__(self):
		self.name = 'udp'
		self.format = '%3d'
		self.len = 3
		self.nick = ('con', )
		self.vars = ('connections', )
		self.init()

	def check(self):
		if os.path.exists('/proc/net/udp'):
			return True
		return False

	def stats(self):
		self.val['connections'] = 0
		for line in open('/proc/net/udp', 'r').readlines():
			l = line.split()
			if l[3] == '07': self.val['connections'] = self.val['connections'] + 1

	def show(self):
		for name in self.vars:
			sys.stdout.write(self.format % self.val[name])
			if name != self.vars[-1]:
				sys.stdout.write(' ')

ansi = {
	'black': '\033[0;30m',
	'darkred': '\033[0;31m',
	'darkgreen': '\033[0;32m',
	'darkyellow': '\033[0;33m',
	'darkblue': '\033[0;34m',
	'darkmagenta': '\033[0;35m',
	'darkcyan': '\033[0;36m',
	'silver': '\033[0;37m',

	'gray': '\033[1;30m',
	'red': '\033[1;31m',
	'green': '\033[1;32m',
	'yellow': '\033[1;33m',
	'blue': '\033[1;34m',
	'magenta': '\033[1;35m',
	'cyan': '\033[1;36m',
	'white': '\033[1;37m',

	'blackbg': '\033[40m',
	'redbg': '\033[41m',
	'greenbg': '\033[42m',
	'yellowbg': '\033[43m',
	'bluebg': '\033[44m',
	'magentabg': '\033[45m',
	'cyanbg': '\033[46m',
	'whitebg': '\033[47m',

	'reset': '\033[0;0m',
	'bold': '\033[1m',
	'reverse': '\033[2m',
	'underline': '\033[4m',

	'clear': '\033[2J',
	'clearline': '\033[K',
	'save': '\033[s',
	'restore': '\033[u',

	'up': '\033[1A',

	'default': '\033[0;0m',
}

def convlist(max, list):
	max = max / len(list)
	retlist = ()
	for var in list:
		retlist = retlist + (conv(max, var), )
	return retlist

#def convlist(max, list):
#	return map(conv, list)

def conv(max, var, base = 1024):
	### lowercase (b) is better for the eyes than uppercase
	unit = ('B', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
	if step == op.delay:
		color = ('red', 'yellow', 'green', 'blue', 'magenta', 'cyan', 'white', 'darkred', 'darkgreen')
	else:
		color = ('darkred', 'darkyellow', 'darkgreen', 'darkblue', 'darkmagenta', 'darkcyan', 'silver', 'red', 'geen')

	if var < 0:
		return ' ' * (max-2) + '- '

	c = 0
	while True:
		repr = str(long(round(var)))
		if len(repr) < max:
			if not op.integer and len('%1.2f' % var) < max:
				ret = '%1.2f' % var
			elif not op.integer and len('%1.1f' % var) < max:
				ret = '%1.1f' % var
			else:
				ret = repr
			break
		var = var / base
		c = c + 1
	if var != 0:
	 	return ansi[color[c]] + ret.rjust(max-1) + unit[c] + ansi['default']
	else:
		return ' ' * (max-2) + '0 '

def info(level, str):
	"Output info message"
#	if level <= op.verbose:
	print str

def die(ret, str):
	"Print error and exit with errorcode"
	info(0, str)
	sys.exit(ret)

def getwinsize():
	try:
		s = struct.pack("HHHH", 0, 0, 0, 0)
		x = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, s)
		return struct.unpack("HHHH", x)[:2]
	except:
		try:
			return (int(os.environ['LINES']), int(os.environ['COLUMNS']))
		except:
			return (25, 80)

def getcpunr():
	procs = 0
	for line in open('/proc/stat', 'r').readlines():
		name = line.split()[0]
		if len(name) == 4 and name[0:3] == 'cpu':
			procs = procs + 1
	return procs

def scsidev(nr):
	if nr < 26:
		return 'sd' + chr(ord('a') + nr)
	else:
		return 'sd' + chr(ord('a') - 1 + nr / 26) + chr(ord('a') + nr % 26)

def dev(maj, min):
	scsi = [8, 65, 66, 67, 68, 69, 70, 71, 128, 129, 130, 131, 132, 133, 134, 135]
	ide = [3, 22, 33, 34, 56, 57, 88, 89, 90, 91]
	if maj in scsi:
		nr = scsi.index(maj) * 16 + min
#		nr = scsi.index(maj) * 16 + min / 16
		return scsidev(nr)
	elif maj in ide:
		nr = ide.index(maj) * 2 + min / 64
		return 'hd' + chr(ord('a') + nr)

#def mountpoint(dev):
#	"Return the mountpoint of a mounted device/file"
#	for entry in open('/etc/mtab', 'r').readlines():
#		if entry:
#			list = entry.split()
#			if dev == list[0]:
#				return list[1]

def signaler(signum, frame):
	signal.alarm(interval)

def main():
	global update, loop, step, pagesize, procs, ansi, interval

	loop = update = 0
	step = op.delay
	pagesize = resource.getpagesize()
	procs = getcpunr()
#	hz = os.sysconf('SC_CLK_TCK')
	interval = 1

	if not op.update:
		interval = op.delay

	### Empty ansi database if no colors are requested
	if not op.color:
		op.update = False
		for key in ansi.keys():
			ansi[key] = ''

	### Build list of requested modules
	olist = []
	for mod in op.modlist:
		if mod == 'cpu':
			olist.append(dstat_cpu())
			olist.append(dstat_cpu24())
		elif mod == 'disk':
			olist.append(dstat_disk())
			olist.append(dstat_disk24())
			olist.append(dstat_disk24old())
		elif mod == 'int':	olist.append(dstat_int())
		elif mod == 'load':	olist.append(dstat_load())
		elif mod == 'mem':	olist.append(dstat_mem())
		elif mod == 'net':	olist.append(dstat_net())
		elif mod == 'page':
			olist.append(dstat_page())
			olist.append(dstat_page24())
		elif mod == 'proc':	olist.append(dstat_proc())
		elif mod == 'swap':	olist.append(dstat_swap())
		elif mod == 'sys':	olist.append(dstat_sys())
		elif mod == 'tcp':	olist.append(dstat_tcp())
		elif mod == 'time':	olist.append(dstat_time())
		elif mod == 'udp':	olist.append(dstat_udp())
		else:
			info(1, 'Module \'%s\' does not exist or failed to load.' % mod)

	(rows, cols) = getwinsize()

	### Remove defect objects and calculate line length
	linewidth = 0
	for o in olist + []:
		if not o.check():
			olist.remove(o)
		else:
			linewidth = linewidth + o.width() + 1

	if linewidth > cols:
		print 'Screen width too small, trimming output.'

	oldplist = []

	### Increase precision if we're root (does not have effect)
#	if os.geteuid() == 0:
#		os.nice(-20)
#	sys.setcheckinterval(op.delay / 10000)

	signal.signal(signal.SIGALRM, signaler)
	signal.alarm(interval)

	### Always show header the first time
	showheader = True

	### Let the games begin
	while update <= op.delay * op.count or op.count == -1:

		### Trim object list to what is visible on screen
		(rows, cols) = getwinsize()
		plist = []
		curwidth = 0
		for o in olist:
			if curwidth + o.width() + 1 <= cols:
				curwidth = curwidth + o.width() + 1
				plist.append(o)

		### Check when to display the header
		if op.header:
			if oldplist != plist:
				showheader = True
			elif step == 1 and loop % (rows - 1) == 0:
				showheader = True

		if showheader:
			showheader = False
			for o in plist:
				sys.stdout.write(o.title1)
				if o != plist[-1]:
					sys.stdout.write(ansi['darkblue'] + ' ' + ansi['default'])
				elif olist != plist:
					sys.stdout.write(ansi['darkblue'] + '>' + ansi['default'])
			sys.stdout.write('\n')

			for o in plist:
				sys.stdout.write(o.title2)
				if o != plist[-1]:
					sys.stdout.write(ansi['silver'] + '|' + ansi['default'])
				elif olist != plist:
					sys.stdout.write(ansi['darkblue'] + '>' + ansi['default'])
			sys.stdout.write('\n')

		oldplist = plist

		### Prepare the colors for intermediate updates, last step in a loop is definitive
		if step == op.delay:
			ansi['default'] = ansi['reset']
		else:
			ansi['default'] = ansi['gray']
		sys.stdout.write(ansi['default'])

		### Debugging info
#		sys.stdout.write('[%d:%d:%d]' % (loop, step, update))

		### Show the stats, calculate all objects (visible, invisible)
		for o in olist:
			o.stats()
			if o in plist:
				o.show()
				if o != plist[-1]:
					sys.stdout.write(ansi['default'] + '|' + ansi['default'])
				elif o != olist[-1]:
					sys.stdout.write(ansi['default'] + '>' + ansi['default'])

		### If intermediate results, update increases with 1 sec (=interval)
		update = update + interval

		### Do not pause when this is the final loop
		if update <= op.delay * op.count or op.count == -1:
			signal.pause()

		### The last step in a loop is to show the definitive line on a new line
		if step == op.delay:
			sys.stdout.write('\n' + ansi['clearline'] + ansi['save'])
		else:
			sys.stdout.write(ansi['restore'] + ansi['clearline'])

		loop = (update + op.delay - 1) / op.delay
		step = ((update - 1) % op.delay) + 1

### Unbuffered sys.stdout
sys.stdout = os.fdopen(1, 'w', 0)

### Workaround for python <= 2.2.1
try:
     True, False
except NameError:
     True = 1
     False = 0
Yes = yes = On = on = True
No = no = Off = off = False

### Workaround for python < 2.3
#def enumerate(sequence):
#    index = 0
#    for item in sequence:
#        yield index, item
#        index = index + 1

### Main entrance
if __name__ == '__main__':
	op=Options(sys.argv[1:])
#	cf=Config()
	try:
		main()
	except KeyboardInterrupt, e:
		signal.signal(signal.SIGALRM, signal.SIG_DFL)
		print ansi['reset']
	except OSError, e:
		signal.signal(signal.SIGALRM, signal.SIG_DFL)
#		print e.errno
		print
		print ansi['reset'] + 'OSError: %s' %e
		sys.exit(7)

sys.exit(0)


# vim:ts=4:sw=4
