#!/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-2007 Dag Wieers <dag@wieers.com>

#from __future__ import generators

VERSION = '0.6.7'

def inspath(path):
    if os.path.isdir(path) and path not in sys.path:
        sys.path.insert(1, path)

try:
    import sys, signal, os, re, time, string
    import types, signal, resource, getpass
    inspath('/usr/local/share/dstat/')
    inspath('/usr/share/dstat/')
    inspath(os.path.abspath(os.path.dirname(sys.argv[0])) + '/plugins/')    # binary path + /plugins/
    inspath(os.getcwd() + '/plugins/')                                      # current path + /plugins/
    inspath(os.getcwd())                                                    # current path
    inspath(os.path.expanduser('~/.dstat/'))                                # home + /.dstat/
except KeyboardInterrupt, e:
    pass

#if sys.version_info < (1, 5):
#   sys.exit('error: Python 1.5 or later required')

### Workaround for python <= 2.2.1
try:
    True, False
except NameError:
    True = 1
    False = 0

### Workaround for python < 2.3 (FIXME: check for sys.version_info existence)
if True:
    def enumerate(sequence):
        index = 0
        list = []
        for item in sequence:
            list.append((index, item))
            index = index + 1
        return list

### Workaround for python < 2.3
if True:
    def sum(sequence):
        ret = 0
        for i in sequence:
            ret = ret + i
        return ret

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

        ### Implicit if no terminal is used
        if not sys.stdout.isatty():
            self.color = False
            self.header = False
            self.update = False

        ### Temporary hardcoded for my own project
        self.diskset = {
            'local': ('sda', 'hd[a-d]'),
            'lores': ('sd[b-k]', 'sd[v-z]', 'sda[a-e]'),
            'hires': ('sd[l-u]', 'sda[f-o]'),
        }

        try:
            import getopt
            opts, args = getopt.getopt (args, 'acdfghilmno:pstTvyC:D:I:M:N:S:V',
                ['cpu', 'disk', 'epoch', 'int', 'ipc', 'load', 'lock', 'mem', 'net',
                'page', 'proc', 'raw', 'swap', 'sys', 'tcp', 'time', 'udp', 'unix', 
                'all', 'debug', 'full', 'help', 'integer', 'mods', 'modules', 'nocolor',
                'noheaders', 'noupdate', 'output=', 'version', 'vmstat'])
        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 ['-C']:
                self.cpulist = string.split(arg, ',')
            elif opt in ['-d', '--disk']:
                self.modlist.append('disk')
            elif opt in ['-D']:
                self.disklist = string.split(arg, ',')
            elif opt in ['--debug']:
                self.debug = self.debug + 1
            elif opt in ['-g', '--page']:
                self.modlist.append('page')
            elif opt in ['-i', '--int']:
                self.modlist.append('int')
            elif opt in ['-I']:
                self.intlist = string.split(arg, ',')
            elif opt in ['--ipc']:
                self.modlist.append('ipc')
            elif opt in ['-l', '--load']:
                self.modlist.append('load')
            elif opt in ['--lock']:
                self.modlist.append('lock')
            elif opt in ['-m', '--mem']:
                self.modlist.append('mem')
            elif opt in ['-M', '--mods', '--modules']:
                self.modlist = self.modlist + string.split(arg, ',')
            elif opt in ['-n', '--net']:
                self.modlist.append('net')
            elif opt in ['-N']:
                self.netlist = string.split(arg, ',')
            elif opt in ['-p', '--proc']:
                self.modlist.append('proc')
            elif opt in ['--raw']:
                self.modlist.append('raw')
            elif opt in ['-s', '--swap']:
                self.modlist.append('swap')
            elif opt in ['-S']:
                self.swaplist = string.split(arg, ',')
            elif opt in ['--tcp']:
                self.modlist.append('tcp')
            elif opt in ['-t', '--time']:
                self.modlist.append('time')
            elif opt in ['-T', '--epoch']:
                self.modlist.append('epoch')
            elif opt in ['--udp']:
                self.modlist.append('udp')
            elif opt in ['--unix']:
                self.modlist.append('unix')
            elif opt in ['-y', '--sys']:
                self.modlist.append('sys')

            elif opt in ['-a', '--all']:
                self.modlist = self.modlist + [ 'cpu', 'disk', 'net', 'page', 'sys' ]
            elif opt in ['-v', '--vmstat']:
                self.modlist = self.modlist + [ 'proc', 'mem', 'page', 'disk', 'sys', 'cpu' ]
            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 ['--noheaders']:
                self.header = False
            elif opt in ['--noupdate']:
                self.update = False
            elif opt in ['-o', '--output']:
                self.output = arg
            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', 'page', 'sys' ]

        try:
            if len(args) > 0: self.delay = int(args[0])
            if len(args) > 1: self.count = int(args[1])
        except:
            print 'dstat: incorrect argument, try dstat -h for the correct syntax'
            sys.exit(1)

        if self.delay <= 0:
            print 'dstat: delay must be an integer, greater than zero'
            sys.exit(1)

    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
        print

        color = ""
        if not gettermcolor(self.color):
            color = "no "
        print 'Terminal type: %s (%scolor support)' % (getenv('TERM'), color)
        rows, cols = gettermsize()
        print 'Terminal size: %d lines, %d columns' % (rows, cols)
        print
        print 'Processors: %d' % getcpunr()
        print 'Pagesize: %d' % resource.getpagesize()
        print 'Clock ticks per secs: %d' % os.sysconf('SC_CLK_TCK')
        print

        global op
        op = self
        listmodules()

    def usage(self):
        print 'Usage: dstat [-afv] [options..] [delay [count]]'

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

Dstat options:
  -c, --cpu              enable cpu stats
     -C 0,3,total           include cpu0, cpu3 and total
  -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
     -S swap1,total         include swap1 and total
  -t, --time             enable time/date output
  -T, --epoch            enable time counter (seconds since epoch)
  -y, --sys              enable system stats
  --ipc                  enable ipc stats
  --lock                 enable lock stats
  --raw                  enable raw stats
  --tcp                  enable tcp stats
  --udp                  enable udp stats
  --unix                 enable unix stats

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

  -a, --all              equals -cdngy (default)
  -f, --full             expand -C, -D, -I, -N and -S discovery lists
  -v, --vmstat           equals -pmgdsc -D total

  --integer              show integer values
  --nocolor              disable colors (implies --noupdate)
  --noheaders            disable repetitive headers
  --noupdate             disable intermediate updates
  --output file          write CSV output to file

  delay is the delay in seconds between each update
  count is the number of updates to display before exiting
  The default delay is 1 and count is unspecified (unlimited)
'''

### START STATS DEFINITIONS ###
class dstat:
    ### Initialise default variables
    def init(self, vars=(), len=0):
        if vars:
            self.val = {}; self.cn1 = {}; self.cn2 = {}
            for name in vars:
                if len <= 1:
                    self.val[name] = self.cn1[name] = self.cn2[name] = 0
                else:
                    self.val[name] = self.cn1[name] = self.cn2[name] = range(len)
                    for i in range(len):
                        self.val[name][i] = self.cn1[name][i] = self.cn2[name][i] = 0

    def open(self, *files):
        "Open stat file descriptor"
        self.file = []
        self.fd = []
        for file in files:
            fd = dopen(file)
            if fd:
                self.file.append(file)
                self.fd.append(fd)
            else:
                raise Exception, 'Cannot open file %s.' % file

    def readlines(self):
        ret = []
        for fd in self.fd:
            fd.seek(0)
            ret = ret + fd.readlines()
        return ret

    def statwidth(self):
        "Return complete stat width"
        return len(self.vars) * self.width() + len(self.vars) - 1

    def width(self):
        "Return column width"
        if isinstance(self.name, types.StringType):
            return self.format[1]
        else:
            return len(self.nick) * self.format[1] + len(self.nick) - 1

    def title(self, nr):
        if nr == 1:
            return self.title1()
        else:
            return self.title2()

    def title1(self):
        ret = ansi['darkblue']
        if isinstance(self.name, types.StringType):
            max = self.statwidth()
            return ret + string.replace(string.center(self.name[0:max], max), ' ', '-') + ansi['default']
        for i, name in enumerate(self.name):
            max = self.width()
            ret = ret + string.replace(string.center(name[0:max], max), ' ', '-')
            if i + 1 != len(self.name):
                if op.color:
                    ret = ret + ansi['blue'] + char['dash'] + ansi['darkblue']
                else:
                    ret = ret + char['space']
        return ret

    def title2(self):
        if isinstance(self.name, types.StringType):
            ret = ''
            for i, nick in enumerate(self.nick):
                ret = ret + ansi['blue'] + ansi['underline'] + string.center(nick, self.format[1]) + ansi['default']
                if i + 1 != len(self.nick): ret = ret + char['space']
            return ret
        else:
            ret = ''
            for i, name in enumerate(self.name):
                for j, nick in enumerate(self.nick):
                    ret = ret + ansi['blue'] + ansi['underline'] + string.center(nick, self.format[1]) + ansi['default']
                    if j + 1 != len(self.nick): ret = ret + char['space']
                if i + 1 != len(self.name): ret = ret + ansi['gray'] + char['colon']
            return ret

    def titlecsv(self, nr):
        if nr == 1:
            return self.titlecsv1()
        else:
            return self.titlecsv2()

    def titlecsv1(self):
        if isinstance(self.name, types.StringType):
            return '"' + self.name + '"' + ',' * (len(self.nick) - 1)
        else:
            ret = ''
            for i, name in enumerate(self.name):
                ret = ret + '"' + name + '"' + ',' * (len(self.nick) - 1)
                if i + 1 != len(self.name): ret = ret + ','
            return ret

    def titlecsv2(self):
        if isinstance(self.name, types.StringType):
            ret = ''
            for i, nick in enumerate(self.nick):
                ret = ret + '"' + nick + '"'
                if i + 1 != len(self.nick): ret = ret + ','
            return ret
        else:
            ret = ''
            for i, name in enumerate(self.name):
                for j, nick in enumerate(self.nick):
                    ret = ret + '"' + nick + '"'
                    if j + 1 != len(self.nick): ret = ret + ','
                if i + 1 != len(self.name): ret = ret + ','
            return ret

    def check(self):
        "Check if stat is applicable"
#       if hasattr(self, 'fd') and not self.fd:
#           raise Exception, 'File %s does not exist' % self.file
        if not self.vars:
            raise Exception, 'No objects found, no stats available'
        if not self.discover:
            raise Exception, 'No objects discovered, no stats available'
        if self.width():
            return True
        raise Exception, 'Unknown problem, please report'

    def discover(self):
        return True

    def show(self):
        "Display stat results"
        line = ''
        for i, name in enumerate(self.vars):
            if isinstance(self.val[name], types.TupleType) or isinstance(self.val[name], types.ListType):
                line = line + cprintlist(self.val[name], self.format)
                sep = ansi['gray'] + char['colon']
            else:
                line = line + cprint(self.val[name], self.format)
                sep = char['space']
            if i + 1 != len(self.vars):
                line = line + sep
        return line

    def showend(self, totlist, vislist):
        if self is not vislist[-1]:
            return ansi['gray'] + char['pipe']
        elif totlist != vislist:
            return ansi['gray'] + char['gt']
        return ''

    def showcsv(self):
        def printcsv(var):
            if var != round(var):
                return '%.3f' % var
            return '%s' % round(var)

        line = ''
        for i, name in enumerate(self.vars):
            if isinstance(self.val[name], types.ListType) or isinstance(self.val[name], types.TupleType):
                for j, val in enumerate(self.val[name]):
                    line = line + printcsv(val)
                    if j + 1 != len(self.val[name]):
                        line = line + ','
            elif isinstance(self.val[name], types.StringType):
                line = line + self.val[name]
            else:
                line = line + printcsv(self.val[name])
            if i + 1 != len(self.vars):
                line = line + ','
        return line

    def showcsvend(self, totlist, vislist):
        if self is not vislist[-1]:
            return ','
        elif self is not totlist[-1]:
            return ','
        return ''

class dstat_cpu(dstat):
    def __init__(self):
        self.format = ('p', 3, 34)
        self.open('/proc/stat')
        self.nick = ( 'usr', 'sys', 'idl', 'wai', 'hiq', 'siq' )
        self.discover = self.discover()
        self.vars = self.vars()
        self.name = self.name()
        self.init(self.vars + ['total',], 6)

    def discover(self, *list):
        ret = []
        for line in self.readlines():
            l = string.split(line)
            if len(l) < 8 or l[0][0:3] != 'cpu': continue
            ret.append(l[0][3:])
        ret.sort()
        for item in list: ret.append(item)
        return ret

    def name(self):
        ret = []
        for name in self.vars:
            if name == 'total':
                ret.append('total cpu usage')
            else:
                ret.append('cpu' + name + ' usage')
        return ret

    def vars(self):
        ret = []
        if op.cpulist:
            list = op.cpulist
        elif not op.full:
            list = ('total',)
        else:
            list = []
            cpu = 0
            while cpu < cpunr:
                list.append(str(cpu))
                cpu = cpu + 1
#           if len(list) > 2: list = list[0:2]
        for name in list:
            if name in self.discover + ['total']:
                ret.append(name)
        return ret

    def extract(self):
        for line in self.readlines():
            l = string.split(line)
            if len(l) < 8: continue
            for name in self.vars:
                if l[0] == 'cpu' + name or ( l[0] == 'cpu' and name == 'total' ):
                    self.cn2[name] = ( long(l[1]) + long(l[2]), long(l[3]), long(l[4]), long(l[5]), long(l[6]), long(l[7]) )
        for name in self.vars:
            for i in range(6):
                self.val[name][i] = 100.0 * (self.cn2[name][i] - self.cn1[name][i]) / (sum(self.cn2[name]) - sum(self.cn1[name]))
        if step == op.delay:
            self.cn1.update(self.cn2)

class dstat_cpu24(dstat):
    def __init__(self):
        self.format = ('p', 3, 34)
        self.open('/proc/stat')
        self.nick = ( 'usr', 'sys', 'idl')
        self.discover = self.discover()
        self.vars = self.vars()
        self.name = self.name()
        self.init(self.vars + ['total',], 3)

    def name(self):
        ret = []
        for name in self.vars:
            if name:
                ret.append('cpu' + name)
            else:
                ret.append('cpu total')
        return ret

    def discover(self, *list):
        ret = []
        for line in self.readlines():
            l = string.split(line)
            if len(l) != 5 or l[0][0:3] != 'cpu': continue
            ret.append(l[0][3:])
        ret.sort()
        for item in list: ret.append(item)
        return ret

    def vars(self):
        ret = []
        if op.cpulist:
            list = op.cpulist
        elif not op.full:
            list = ('total',)
        else:
            list = []
            cpu = 0
            while cpu < cpunr:
                list.append(str(cpu))
                cpu = cpu + 1
#           if len(list) > 2: list = list[0:2]
        for name in list:
            if name in self.discover + ['total']:
                ret.append(name)
        return ret

    def extract(self):
        for line in self.readlines():
            l = string.split(line)
            for name in self.vars:
                if l[0] == 'cpu' + name or ( l[0] == 'cpu' and name == 'total' ):
                    self.cn2[name] = ( long(l[1]) + long(l[2]), long(l[3]), long(l[4]) )
        for name in self.vars:
            for i in range(3):
                self.val[name][i] = 100.0 * (self.cn2[name][i] - self.cn1[name][i]) / (sum(self.cn2[name]) - sum(self.cn1[name]))
        if step == op.delay:
            self.cn1.update(self.cn2)

class dstat_disk(dstat):
    def __init__(self):
        self.format = ('f', 5, 1024)
        self.open('/proc/diskstats')
        self.nick = ('read', 'writ')
        self.discover = self.discover()
        self.vars = self.vars()
        self.name = []
        for name in self.vars: self.name.append('dsk/' + name)
        self.init(self.vars + ['total',], 2)

    def discover(self, *list):
        ret = []
        for line in self.readlines():
            l = string.split(line)
            if len(l) < 13: continue
            if l[3:] == ['0',] * 11: continue
            name = l[2]
            ret.append(name)
        for item in list: ret.append(item)
        return ret

    def vars(self):
        ret = []
        if op.disklist:
            list = op.disklist
        elif not op.full:
            list = ('total',)
        else:
            list = []
            for name in self.discover:
                if not re.match('(md[0-9]+|dm-[0-9]+)', name):
                    list.append(name)
#           if len(list) > 2: list = list[0:2]
            list.sort()
        for name in list:
            if name in self.discover + ['total'] + op.diskset.keys():
                ret.append(name)
        return ret

    def extract(self):
        for name in self.vars: self.cn2[name] = (0, 0)
        for line in self.readlines():
            l = string.split(line)
            if len(l) < 13: continue
            if l[5] == '0' and l[9] == '0': continue
            name = l[2]
            if l[3:] == ['0',] * 11: continue
            if not re.match('(md[0-9]+|dm-[0-9]+)', name):
                self.cn2['total'] = ( self.cn2['total'][0] + long(l[5]), self.cn2['total'][1] + long(l[9]) )
            if name in self.vars and name != 'total':
                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 op.diskset.keys():
                    for disk in op.diskset[set]:
                        if re.match('^'+disk+'$', name):
                            self.cn2[set] = ( self.cn2[set][0] + long(l[5]), self.cn2[set][1] + long(l[9]) )
        for name in self.cn2.keys():
            self.val[name] = ( 
                (self.cn2[name][0] - self.cn1[name][0]) * 512.0 / tick,
                (self.cn2[name][1] - self.cn1[name][1]) * 512.0 / tick,
            )
        if step == op.delay:
            self.cn1.update(self.cn2)

class dstat_disk24(dstat):
    def __init__(self):
        self.format = ('f', 5, 1024)
        self.open('/proc/partitions')
        self.nick = ('read', 'writ')
        self.discover = self.discover()
        self.vars = self.vars()
        if self.fd and not self.discover:
            raise Exception, 'Kernel is not compiled with CONFIG_BLK_STATS'
        self.name = []
        for name in self.vars: self.name.append('dsk/' + sysfs_dev(name))
        self.init(self.vars + ['total',], 2)

    def discover(self, *list):
        ret = []
        for line in self.readlines():
            l = string.split(line)
            if len(l) < 15 or l[0] == 'major' or int(l[1]) % 16 != 0: continue
            name = l[3]
            ret.append(name)
        for item in list: ret.append(item)
        return ret

    def vars(self):
        ret = []
        if op.disklist:
            list = op.disklist
        elif not op.full:
            list = ('total',)
        else:
            list = []
            for name in self.discover:
                if not re.match('(md[0-9]+|dm-[0-9]+)', name):
                    list.append(name)
#           if len(list) > 2: list = list[0:2]
            list.sort()
        for name in list:
            if name in self.discover + ['total'] + op.diskset.keys():
                ret.append(name)
        return ret

    def extract(self):
        for name in self.vars: self.cn2[name] = (0, 0)
        for line in self.readlines():
            l = string.split(line)
            if len(l) < 15 or l[0] == 'major' or int(l[1]) % 16 != 0: continue
            name = l[3]
            if not re.match('(md[0-9]+|dm-[0-9]+)', name):
                self.cn2['total'] = ( self.cn2['total'][0] + long(l[6]), self.cn2['total'][1] + long(l[10]) )
            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 op.diskset.keys():
                    for disk in op.diskset[set]:
                        if re.match('^'+disk+'$', name):
                            self.cn2[set] = ( self.cn2[set][0] + long(l[6]), self.cn2[set][1] + long(l[10]) )
        for name in self.cn2.keys():
            self.val[name] = ( 
                (self.cn2[name][0] - self.cn1[name][0]) * 512.0 / tick,
                (self.cn2[name][1] - self.cn1[name][1]) * 512.0 / tick,
            )
        if step == op.delay:
            self.cn1.update(self.cn2)

### FIXME: Needs rework, does anyone care ?
class dstat_disk24old(dstat):
    def __init__(self):
        self.format = ('f', 5, 1024)
        self.open('/proc/stat')
        self.nick = ('read', 'writ')
        self.regexp = re.compile('^\((\d+),(\d+)\):\(\d+,\d+,(\d+),\d+,(\d+)\)$')
        self.discover = self.discover()
        self.vars = self.vars()
        self.name = []
        for name in self.vars: self.name.append('dsk/' + name)
        self.init(self.vars + ['total',], 2)

    def discover(self, *list):
        ret = []
        for line in self.readlines():
            l = string.split(line, ':')
            if len(l) < 3: continue
            name = l[0]
            if name != 'disk_io': continue
            for pair in string.split(line)[1:]:
                m = self.regexp.match(pair)
                if not m: continue
                l = m.groups()
                if len(l) < 4: continue
                name = dev(int(l[0]), int(l[1]))
                ret.append(name)
            break
        for item in list: ret.append(item)
        return ret

    def vars(self):
        ret = []
        if op.disklist:
            list = op.disklist
        elif not op.full:
            list = ('total',)
        else:
            list = []
            for name in self.discover:
                if not re.match('(md[0-9]+|dm-[0-9]+)', name):
                    list.append(name)
#           if len(list) > 2: list = list[0:2]
            list.sort()
        for name in list:
            if name in self.discover + ['total'] + op.diskset.keys():
                ret.append(name)
        return ret

    def extract(self):
        for name in self.vars: self.cn2[name] = (0, 0)
        for line in self.readlines():
            l = string.split(line, ':')
            if len(l) < 3: continue
            name = l[0]
            if name != 'disk_io': continue
            for pair in string.split(line)[1:]:
                m = self.regexp.match(pair)
                if not m: continue
                l = m.groups()
                if len(l) < 4: continue
                name = dev(int(l[0]), int(l[1]))
                if not re.match('(md[0-9]+)', name):
                    self.cn2['total'] = ( self.cn2['total'][0] + long(l[2]), self.cn2['total'][1] + long(l[3]) )
                if name in self.vars and name != 'total':
                    self.cn2[name] = ( self.cn2[name][0] + long(l[2]), self.cn2[name][1] + long(l[3]) )
                for set in self.vars:
                    if set in op.diskset.keys():
                        for disk in op.diskset[set]:
                            if re.match('^'+disk+'$', name):
                                self.cn2[set] = ( self.cn2[set][0] + long(l[2]), self.cn2[set][1] + long(l[3]) )
            break
        for name in self.cn2.keys():
            self.val[name] = (
                (self.cn2[name][0] - self.cn1[name][0]) * 512.0 / tick,
                (self.cn2[name][1] - self.cn1[name][1]) * 512.0 / tick,
            )
        if step == op.delay:
            self.cn1.update(self.cn2)

class dstat_epoch(dstat):
    def __init__(self):
        self.name = 'epoch'
        self.format = ('t', 10, 0)
        if op.debug:
            self.format = ('t', 14, 0)
        self.nick = ('epoch',)
        self.vars = self.nick
        self.init(self.vars, 1)

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

#   def show(self):
#       return ansi['reset'] + ( '%10.2f' % self.val['epoch'] )

### FIXME: Make total work as well
class dstat_int(dstat):
    def __init__(self):
        self.name = 'interrupts'
        self.format = ('d', 5, 1000)
        self.open('/proc/stat')
        self.discover = self.discover()
        self.intmap = self.intmap()
        self.vars = self.vars()
        self.nick = self.vars
        self.init(self.vars + ['total'], 1)

    def intmap(self):
        ret = {}
        for line in dopen('/proc/interrupts').readlines():
            l = string.split(line)
            if len(l) <= cpunr: continue
            l1 = string.split(l[0], ':')[0]
            l2 = string.split(string.join(l[cpunr+2:], ' '), ',')
            ret[l1] = l1
            for name in l2:
                name = string.lower(string.strip(name))
                ret[name] = l1
        return ret

    def discover(self):
        ret = []
        for line in self.readlines():
            l = string.split(line)
            if l[0] != 'intr': continue
            for name, i in enumerate(l[2:]):
                if long(i) > 10: ret.append(str(name))
        return ret

#   def check(self):
#       if self.fd[0] and self.vars:
#           self.fd[0].seek(0)
#           for line in self.fd[0].readlines():
#               l = string.split(line)
#               if l[0] != 'intr': continue
#               return True
#       return False

    def vars(self):
        ret = []
        if op.intlist:
            list = op.intlist
        else:
            list = self.discover + ['total']
            for name in list:
                if name in ('0', '1', '2', '8', 'NMI', 'LOC', 'MIS', 'CPU0'):
                    list.remove(name)
            if not op.full and len(list) > 3: list = list[-3:]
        for name in list:
            if name in self.discover:
                ret.append(name)
            elif string.lower(name) in self.intmap.keys():
                ret.append(self.intmap[string.lower(name)])
        return ret

    def extract(self):
        for line in self.readlines():
            l = string.split(line)
            if not l or l[0] != 'intr': continue
            for name in self.vars:
                self.cn2[name] = long(l[int(name) + 2])
            self.cn2['total'] = self.cn2['total'] + long(l[int(name) + 2])
        for name in self.vars + ['total']:
            self.val[name] = (self.cn2[name] - self.cn1[name]) * 1.0 / tick
        if step == op.delay:
            self.cn1.update(self.cn2)

class dstat_int24(dstat):
    def __init__(self):
        self.name = 'interrupts'
        self.format = ('d', 5, 1000)
        self.open('/proc/interrupts')
        self.discover = self.discover()
        self.vars = self.vars()
        self.nick = self.vars
        self.init(self.vars, 1)

    def intmap(self):
        ret = {}
        for line in self.readlines():
            l = string.split(line)
            if len(l) <= cpunr: continue
            l1 = string.split(l[0], ':')[0]
            l2 = string.split(string.join(l[cpunr+2:], ' '), ',')
            ret[l1] = l1
            for name in l2:
                name = string.lower(string.strip(name))
                ret[name] = l1
        return ret

    def discover(self):
        ret = []
        for line in self.readlines():
            l = string.split(line)
            if len(l) < cpunr+1: continue
            name = string.split(l[0], ':')[0]
            if long(l[1]) > 10:
                ret.append(name)
        return ret

#   def check(self):
#       if self.fd and self.discover:
#           self.fd[0].seek(0)
#           for line in self.fd[0].readlines():
#               l = string.split(line)
#               if l[0] != 'intr' or len(l) > 2: continue
#               return True
#       return False

    def vars(self):
        ret = []
        if op.intlist:
            list = op.intlist
        else:
            list = self.discover
            for name in list:
                if name in ('0', '1', '2', '8', 'CPU0', 'ERR', 'LOC', 'MIS', 'NMI'):
                    list.remove(name)
            if not op.full and len(list) > 3: list = list[-3:]
        for name in list:
            if name in self.discover:
                ret.append(name)
            elif string.lower(name) in self.intmap.keys():
                ret.append(self.intmap[string.lower(name)])
        return ret

    def extract(self):
        for line in self.readlines():
            l = string.split(line)
            if len(l) < cpunr+1: continue
            name = string.split(l[0], ':')[0]
            if name in self.vars:
                self.cn2[name] = 0
                for i in l[1:1+cpunr]:
                    self.cn2[name] = self.cn2[name] + long(i)
#           elif len(l) > 2 + cpunr:
#               for hw in self.vars:
#                   for mod in l[2+cpunr:]:
#                       self.cn2[mod] = long(l[1])
        for name in self.cn2.keys():
            self.val[name] = (self.cn2[name] - self.cn1[name]) * 1.0 / tick
        if step == op.delay:
            self.cn1.update(self.cn2)

class dstat_ipc(dstat):
    def __init__(self):
        self.name = 'sysv ipc'
        self.format = ('d', 3, 10)
        self.vars = ('msg', 'sem', 'shm')
        self.nick = self.vars
        self.init(self.vars, 1)

    def extract(self):
        for name in self.vars:
            self.val[name] = len(dopen('/proc/sysvipc/'+name).readlines()) - 1

class dstat_load(dstat):
    def __init__(self):
        self.name = 'load avg'
        self.format = ('f', 4, 10)
        self.open('/proc/loadavg')
        self.nick = ('1m', '5m', '15m')
        self.vars = ('load1', 'load5', 'load15')
        self.init(self.vars, 1)

    def extract(self):
        for line in self.readlines():
            l = string.split(line)
            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_lock(dstat):
    def __init__(self):
        self.name = 'file locks'
        self.format = ('f', 3, 10)
        self.open('/proc/locks')
        self.nick = ('pos', 'lck', 'rea', 'wri')
        self.vars = ('posix', 'flock', 'read', 'write')
        self.init(self.vars, 1)

    def extract(self):
        for name in self.vars: self.val[name] = 0
        for line in self.readlines():
            l = string.split(line)
            if len(l) < 4: continue
            if l[1] == 'POSIX':
                self.val['posix'] = self.val['posix'] + 1
            elif l[1] == 'FLOCK':
                self.val['flock'] = self.val['flock'] + 1
            if l[3] == 'READ':
                self.val['read'] = self.val['read'] + 1
            elif l[3] == 'WRITE':
                self.val['write'] = self.val['write'] + 1

class dstat_mem(dstat):
    def __init__(self):
        self.name = 'memory usage'
        self.format = ('f', 5, 1024)
        self.open('/proc/meminfo')
        self.nick = ('used', 'buff', 'cach', 'free')
        self.vars = ('MemUsed', 'Buffers', 'Cached', 'MemFree')
        self.init(self.vars, 1)

    def extract(self):
        for line in self.readlines():
            l = string.split(line)
            if len(l) < 2: continue
            name = string.split(l[0], ':')[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.format = ('f', 5, 1024)
        self.open('/proc/net/dev')
        self.nick = ('recv', 'send')
        self.discover = self.discover()
        self.vars = self.vars()
        self.name = []
        for name in self.vars: self.name.append('net/' + name)
        self.init(self.vars + ['total',], 2)

    def discover(self, *list):
        ret = []
        for line in self.readlines():
            l = string.split(string.replace(line, ':', ' '))
            if len(l) < 17: continue
            if l[2] == '0' and l[10] == '0': continue
            name = l[0]
            if name not in ('lo', 'face'):
                ret.append(name)
        ret.sort()
        for item in list: ret.append(item)
        return ret

    def vars(self):
        ret = []
        if op.netlist:
            list = op.netlist
        elif not op.full:
            list = ('total',)
        else:
            list = self.discover
#           if len(list) > 2: list = list[0:2]
            list.sort()
        for name in list:
            if name in self.discover + ['total', 'lo']:
                ret.append(name)
        return ret

    def extract(self):
        self.cn2['total'] = [0, 0]
        for line in self.readlines():
            l = string.split(string.replace(line, ':', ' '))
            if len(l) < 17: continue
            if l[2] == '0' and l[10] == '0': continue
            name = l[0]
            if name in self.vars :
                self.cn2[name] = ( long(l[1]), long(l[9]) )
            if name not in ('lo','face'):
                self.cn2['total'] = ( self.cn2['total'][0] + long(l[1]), 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]) * 1.0 / tick,
                    (self.cn2[name][1] - self.cn1[name][1]) * 1.0 / tick,
                 )
        if step == op.delay:
            self.cn1.update(self.cn2)

class dstat_page(dstat):
    def __init__(self):
        self.name = 'paging'
        self.format = ('f', 5, 1024)
        self.open('/proc/vmstat')
        self.nick = ('in', 'out')
        self.vars = ('pswpin', 'pswpout')
        self.init(self.vars, 1)

    def extract(self):
        for line in self.readlines():
            l = string.split(line)
            if len(l) < 2: continue
            name = l[0]
            if name in self.vars:
                self.cn2[name] = long(l[1])
        for name in self.vars:
            self.val[name] = (self.cn2[name] - self.cn1[name]) * pagesize * 1.0 / tick
        if step == op.delay:
            self.cn1.update(self.cn2)

class dstat_page24(dstat):
    def __init__(self):
        self.name = 'paging'
        self.format = ('f', 5, 1024)
        self.open('/proc/stat')
        self.nick = ('in', 'out')
        self.vars = ('pswpin', 'pswpout')
        self.init(self.vars, 1)

    def extract(self):
        for line in self.readlines():
            l = string.split(line)
            if len(l) < 3: continue
            name = l[0]
            if name != 'swap': continue
            self.cn2['pswpin'] = long(l[1])
            self.cn2['pswpout'] = long(l[2])
            break
        for name in self.vars:
            self.val[name] = (self.cn2[name] - self.cn1[name]) * pagesize * 1.0 / tick
        if step == op.delay:
            self.cn1.update(self.cn2)

class dstat_proc(dstat):
    def __init__(self):
        self.name = 'procs'
        self.format = ('f', 3, 10)
        self.open('/proc/stat')
        self.nick = ('run', 'blk', 'new')
        self.vars = ('procs_running', 'procs_blocked', 'processes')
        self.init(self.vars, 1)

    def extract(self):
        for line in self.readlines():
            l = string.split(line)
            if len(l) < 2: continue
            name = l[0]
            if name == 'processes':
                self.val['processes'] = 0
                self.cn2[name] = long(l[1])
            elif name == 'procs_running':
                self.cn2[name] = self.cn2[name] + long(l[1]) - 1
            elif name == 'procs_blocked':
                self.cn2[name] = self.cn2[name] + long(l[1])
        self.val['processes'] = (self.cn2['processes'] - self.cn1['processes']) * 1.0 / tick
        for name in ('procs_running', 'procs_blocked'):
            self.val[name] = self.cn2[name] * 1.0 / tick
        if step == op.delay:
            self.cn1.update(self.cn2)
            for name in ('procs_running', 'procs_blocked'):
                self.cn2[name] = 0

class dstat_raw(dstat):
    def __init__(self):
        self.name = 'raw'
        self.format = ('f', 3, 10)
        self.open('/proc/net/raw')
        self.nick = ('soc',)
        self.vars = ('sockets',)
        self.init(self.vars, 1)

    def extract(self):
        self.val['sockets'] = len(self.readlines()) - 1

class dstat_swap(dstat):
    def __init__(self):
        self.name = 'swap'
        self.format = ('f', 5, 1024)
        self.open('/proc/swaps')
        self.nick = ('used', 'free')
        self.discover = self.discover()
        self.vars = self.vars()
        self.name = []
        for name in self.vars: self.name.append('swp/'+improve(name))
        self.init(self.vars + ['total',], 2)

    def discover(self, *list):
        ret = []
        for line in self.readlines():
            l = string.split(line)
            if len(l) < 5: continue
            if l[0] == 'Filename': continue
#           ret.append(improve(l[0]))
            ret.append(l[0])
        ret.sort()
        for item in list: ret.append(item)
        return ret

    def vars(self):
        ret = []
        if op.swaplist:
            list = op.swaplist
        elif not op.full:
            list = ('total',)
        else:
            list = self.discover
#           if len(list) > 2: list = list[0:2]
        list.sort()
        for name in list:
            if name in self.discover + ['total']:
                ret.append(name)
        return ret

    def extract(self):
        self.val['total'] = [0, 0]
        for line in self.readlines():
            l = string.split(line)
            if len(l) < 5 or l[0] == 'Filename': continue
            name = l[0]
            self.val[name] = ( long(l[3]) * 1024.0, (long(l[2]) - long(l[3])) * 1024.0 )
            self.val['total'] = ( self.val['total'][0] + self.val[name][0], self.val['total'][1] + self.val[name][1])

class dstat_swapold(dstat):
    def __init__(self):
        self.name = 'swap'
        self.format = ('f', 5, 1024)
        self.open('/proc/meminfo')
        self.nick = ('used', 'free')
        self.vars = ('SwapUsed', 'SwapFree')
        self.init(self.vars, 1)

    def extract(self):
        for line in self.readlines():
            l = string.split(line)
            if len(l) < 2: continue
            name = string.split(l[0], ':')[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 = ('d', 5, 1000)
        self.open('/proc/stat')
        self.nick = ('int', 'csw')
        self.vars = ('intr', 'ctxt')
        self.init(self.vars, 1)

    def extract(self):
        for line in self.readlines():
            l = string.split(line)
            if len(l) < 2: continue
            name = l[0]
            if name in self.vars:
                self.cn2[name] = long(l[1])
        for name in self.vars:
            self.val[name] = (self.cn2[name] - self.cn1[name]) * 1.0 / tick
        if step == op.delay:
            self.cn1.update(self.cn2)

class dstat_tcp(dstat):
    def __init__(self):
        self.name = 'tcp sockets'
        self.format = ('f', 3, 100)
        self.open('/proc/net/tcp', '/proc/net/tcp6')
        self.nick = ('lis', 'act', 'syn', 'tim', 'clo')
        self.vars = ('listen', 'established', 'syn', 'wait', 'close')
        self.init(self.vars, 1)

    def extract(self):
        for name in self.vars: self.val[name] = 0
        for line in self.readlines():
            l = string.split(line)
            if len(l) < 12: continue
            ### 01: established, 02: syn_sent,  03: syn_recv, 04: fin_wait1,
            ### 05: fin_wait2,   06: time_wait, 07: close,    08: close_wait,
            ### 09: last_ack,    0A: listen,    0B: closing
            if l[3] in ('0A',): self.val['listen'] = self.val['listen'] + 1
            elif l[3] in ('01',): self.val['established'] = self.val['established'] + 1
            elif l[3] in ('02', '03', '09',): self.val['syn'] = self.val['syn'] + 1
            elif l[3] in ('06',): self.val['wait'] = self.val['wait'] + 1
            elif l[3] in ('04', '05', '07', '08', '0B',): self.val['close'] = self.val['close'] + 1

class dstat_time(dstat):
    def __init__(self):
        self.name = 'time'
        self.format = ('s', 14, 0)
        self.nick = ('date/time',)
        self.vars = ('time',)
        self.init(self.vars, 1)

    def extract(self):
        ### FIXME: Add milliseconds when using --debug (see dstat_epoch)
        self.val['time'] = time.strftime('%d-%m %H:%M:%S', time.localtime())

    def show(self):
        if step == op.delay:
            color = 'silver'
        else:
            color = 'gray'
        return ansi[color] + self.val['time']

class dstat_udp(dstat):
    def __init__(self):
        self.name = 'udp'
        self.format = ('f', 3, 100)
        self.open('/proc/net/udp', '/proc/net/udp6')
        self.nick = ('lis', 'act')
        self.vars = ('listen', 'established')
        self.init(self.vars, 1)

    def extract(self):
        for name in self.vars: self.val[name] = 0
        for line in self.readlines():
            l = string.split(line)
            if l[3] == '07': self.val['listen'] = self.val['listen'] + 1
            elif l[3] == '01': self.val['established'] = self.val['established'] + 1

class dstat_unix(dstat):
    def __init__(self):
        self.name = 'unix sockets'
        self.format = ('d', 4, 100)
        self.open('/proc/net/unix')
        self.nick = ('dgrm', 'strm', 'lis', 'act')
        self.vars = ('datagram', 'stream', 'listen', 'established')
        self.init(self.vars, 1)

    def extract(self):
        for name in self.vars: self.val[name] = 0
        for line in self.readlines():
            l = string.split(line)
            if l[4] == '0002':
                self.val['datagram'] = self.val['datagram'] + 1
            elif l[4] == '0001':
                self.val['stream'] = self.val['stream'] + 1
                if l[5] == '01':
                    self.val['listen'] = self.val['listen'] + 1
                elif l[5] == '03':
                    self.val['established'] = self.val['established'] + 1

### END STATS DEFINITIONS ###

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',
    'clearline': '\033[2K',
#   'save': '\033[s',
#   'restore': '\033[u',
    'save': '\0337',
    'restore': '\0338',

    'up': '\033[1A',
    'down': '\033[1B',
    'right': '\033[1C',
    'left': '\033[1D',

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

char = {
    'pipe': '|',
    'colon': ':',
    'gt': '>',
    'space': ' ',
    'dash': '-',
}

def ticks():
    "Return the number of 'ticks' since bootup"
    try:
        for line in open('/proc/uptime', 'r', 0).readlines():
            l = string.split(line)
            if len(l) < 2: continue
            return float(l[0])
    except:
        for line in dopen('/proc/stat').readlines():
            l = string.split(line)
            if len(l) < 2: continue
            if l[0] == 'btime':
                return time.time() - long(l[1])

def improve(str):
    "Improve a device name"
    if str.startswith('/dev/mapper/'):
        str = string.split(str, '/')[3]
    elif str.startswith('/dev/'):
        str = string.split(str, '/')[2]
    return str

def dopen(file):
    "Open a file for reuse, if already opened, return file descriptor"
    global fds
    if not os.path.exists(file): return None
    if 'fds' not in globals().keys(): fds = {}
    if file not in fds.keys():
        fds[file] = open(file, 'r', 0)
    else:
        fds[file].seek(0)
    return fds[file]

def dclose(file):
    "Close an open file and remove file descriptor from list"
    global fds
    if not 'fds' in globals(): fds = {}
    if file in fds:
        fds[file].close()
        del(fds[file])

def dpopen(cmd):
    "Open a pipe for reuse, if already opened, return pipes"
    global pipes
    if 'pipes' not in globals().keys(): pipes = {}
    if cmd not in pipes.keys():
        pipes[cmd] = os.popen3(cmd, 't', 0)
    return pipes[cmd]

def readpipe(file, tmout = 0.001):
    "Read available data from pipe in a non-blocking fashion"
    ret = ''
    while not select.select([file.fileno()], [], [], tmout)[0]:
        pass
    while select.select([file.fileno()], [], [], tmout)[0]:
        ret = ret + file.read(1)
    return string.split(ret, '\n')

def greppipe(file, str, tmout = 0.001):
    "Grep available data from pipe in a non-blocking fashion"
    ret = ''
    while not select.select([file.fileno()], [], [], tmout)[0]:
        pass
    while select.select([file.fileno()], [], [], tmout)[0]:
        char = file.read(1)
        if char != '\n':
            ret = ret + char
        elif ret.startswith(str):
            return ret
        else:
            ret = ''
    return None

def matchpipe(file, string, tmout = 0.001):
    "Match available data from pipe in a non-blocking fashion"
    ret = ''
    regexp = re.compile(string)
    while not select.select([file.fileno()], [], [], tmout)[0]:
        pass
    while select.select([file.fileno()], [], [], tmout)[0]:
        char = file.read(1)
        if char != '\n':
            ret = ret + char
        elif regexp.match(ret):
            return ret
        else:
            ret = ''
    return None

def dchg(var, max, base):
    "Convert decimal to string given base and length"
    c = 0
    while True:
        ret = str(int(round(var)))
        if len(ret) <= max:
            break
        var = var / base
        c = c + 1
    else:
        c = -1
    return ret, c

def fchg(var, max, base):
    "Convert float to string given base and length"
    c = 0
    while True:
        if var == 0:
            ret = str('0')
            break
#       ret = repr(round(var))
#       ret = repr(long(round(var,max)))
        ret = str(long(round(var,max)))
        if len(ret) <= max:
            i = max - len(ret)
            while i > 0:
                ret = ('%.'+str(i)+'f') % var
                if len(ret) < max and ret != repr(round(var)):
                    break
                i = i - 1
            else:
                ret = str(int(round(var)))
            break
        var = var / base
        c = c + 1
    else:
        c = -1
    return ret, c

def cprintlist(list, format):
    ret = sep = ''
    for var in list:
        ret = ret + sep + cprint(var, format)
        sep = ' '
    return ret

def cprint(var, format = ('f', 4, 1000)):
    c = -1
    type = format[0]
    max = format[1]
    mp = format[2]

    base = 1000
    if mp == 1024:
        base = 1024

    unit = False
    if mp in (1000, 1024) and max >= len(str(base)):
        unit = True
        max = max - 1

    if var < 0:
        if unit:
            return ansi['default'] + string.rjust('-', max) + ' '
        else:
            return ansi['default'] + string.rjust('-', max)

    if base == 1024:
        units = ('B', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
    else:
        units = (' ', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')

    if step == op.delay:
        colors = ('red', 'yellow', 'green', 'blue', 'magenta', 'cyan', 'white', 'darkred', 'darkgreen')
    else:
        colors = ('darkred', 'darkyellow', 'darkgreen', 'darkblue', 'darkmagenta', 'darkcyan', 'silver', 'red', 'green')

    if op.integer and type in ('d', 'p', 'f'):
        ret, c = dchg(var, max, base)
    elif type in ('d', 'p'):
        ret, c = dchg(var, max, base)
    elif type in ('f'):
        ret, c = fchg(var, max, base)
    elif type in ('t'):
        ret, c = fchg(var, max+1, base)
    else:
        ret = str(var)

    if ret == '0':
        color = 'default'
    elif type in ('d', 'p'):
        color = colors[int(var/mp)%len(colors)]
    elif type in ('f'):
        color = colors[c%len(colors)]
    else:
        color = 'default'

    if type in ('s',):
        ret = ansi['default'] + string.ljust(ret, max)
    else:
        ret = ansi[color] + string.rjust(ret, max)

    if unit:
        if c != -1 and round(var) != 0:
            ret = ret + units[c]
        else:
            ret = ret + ' '

    return ret

def showtitle(nr, totlist, vislist, midchar, endchar):
    line = ''
    for o in vislist:
        line = line + o.title(nr)
        if o is not vislist[-1]:
            line = line + midchar
        elif totlist != vislist:
            line = line + endchar
    sys.stdout.write(line + '\n')

def showcsvtitle(nr, totlist):
    line = ''
    for o in totlist:
        line = line + o.titlecsv(nr)
        if o is not totlist[-1]:
            line = line + ','
    outputfile.write(line + '\n')

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)
    exit(ret)

def getenv(key):
    if os.environ.has_key(key):
        return os.environ[key]
    return None

def initterm():
    "Initialise terminal"
    global termsize

    termsize = None, None

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

    try:
        global fcntl, struct, termios
        import fcntl, struct, termios
        termios.TIOCGWINSZ
    except:
        try:
            curses.setupterm()
            curses.tigetnum('lines'), curses.tigetnum('cols')
        except:
            pass
        else:
            termsize = None, 2
    else:
        termsize = None, 1

def gettermsize():
    "Return the dynamic terminal geometry"
    global termsize

    if not termsize[0]:
        try:
            if termsize[1] == 1:
                s = struct.pack('HHHH', 0, 0, 0, 0)
                x = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, s)
                return struct.unpack('HHHH', x)[:2]
            elif termsize[1] == 2:
                curses.setupterm()
                return curses.tigetnum('lines'), curses.tigetnum('cols')
            else:
                termsize = (int(getenv('LINES')), int(getenv('COLUMNS')))
        except:
            termsize = 25, 80
    return termsize

def gettermcolor(color=True):
    if color and sys.stdout.isatty():
        try:
            import curses
            curses.setupterm()
            if curses.tigetnum('colors') < 0:
                return False
        except:
            info(1, 'Color support is disabled, python-curses is not installed.')
            return False
    return color

def getcpunr():
    "Return the number of CPUs in the system"
    cpunr = -1
    for line in dopen('/proc/stat').readlines():
        if line[0:3] == 'cpu':
            cpunr = cpunr + 1
    if cpunr < 0:
        raise "Problem finding number of CPUs in system."
    return cpunr

### FIXME: Add scsi support too and improve
def sysfs_dev(device):
    "Convert sysfs device names into device names"
    m = re.match('ide/host([0-9])/bus([0-9])/target([0-9])/lun([0-9])/disc', device)
    if m:
        l = m.groups()
        # ide/host0/bus0/target0/lun0/disc -> 0 -> hda
        # ide/host0/bus1/target0/lun0/disc -> 2 -> hdc
        nr = int(l[1]) * 2 + int(l[3])
        return 'hd' + chr(ord('a') + nr)
    m = re.match('placeholder', device)
    if m:
        return 'sdX'
    return device

def dev(maj, min):
    "Convert major/minor pairs into device names"
    ram = [1, ]
    ide = [3, 22, 33, 34, 56, 57, 88, 89, 90, 91]
    loop = [7, ]
    scsi = [8, 65, 66, 67, 68, 69, 70, 71, 128, 129, 130, 131, 132, 133, 134, 135]
    md = [9, ]
    ida = [72, 73, 74, 75, 76, 77, 78, 79]
    ubd = [98,]
    cciss = [104,]
    dm =  [253,]
    if maj in scsi:
        disc = chr(ord('a') + scsi.index(maj) * 16 + min / 16)
        part = min % 16
        if not part: return 'sd%s' % disc
        return 'sd%s%d' % (disc, part)
    elif maj in ide:
        disc = chr(ord('a') + ide.index(maj) * 2 + min / 64)
        part = min % 64
        if not part: return 'hd%s' % disc
        return 'hd%s%d' % (disc, part)
    elif maj in dm:
        return 'dm-%d' % min
    elif maj in md:
        return 'md%d' % min
    elif maj in loop:
        return 'loop%d' % min
    elif maj in ram:
        return 'ram%d' % min
    elif maj in cciss:
        disc = cciss.index(maj) * 16 + min / 16
        part = min % 16
        if not part: return 'c0d%d' % disc
        return 'c0d%dp%d' % (disc, part)
    elif maj in ida:
        cont = ida.index(maj)
        disc = min / 16
        part = min % 16
        if not part: return 'ida%d-%d' % (cont, disc)
        return 'ida%d-%d-%d' % (cont, disc, part)
    elif maj in ubd:
        disc = ubd.index(maj) * 16 + min / 16
        part = min % 16
        if not part: return 'ubd%d' % disc
        return 'ubd%d-%d' % (disc, part)
    else:
        return 'dev%d-%d' % (maj, min)

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

def readfile(file):
    ret = ''
    for line in open(file,'r').readlines():
        ret = ret + line
    return ret

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

def exit(ret):
    signal.signal(signal.SIGALRM, signal.SIG_IGN)
    sys.stdout.write(ansi['reset'])
    sys.exit(ret)

def listmodules():
    import glob
    rows, cols = gettermsize()
    remod = re.compile('.+/dstat_(.+).py$')
    for path in sys.path:
        list = []
        for file in glob.glob(path + '/dstat_*.py'):
            list.append(remod.match(file).groups()[0])
        if not list: continue
        list.sort()
        cols2 = cols - 8
        print '%s:\n\t' % os.path.abspath(path),
        for mod in list:
            cols2 = cols2 - len(mod) - 2
            if cols2 <= 0:
                print '\n\t',
                cols2 = cols - len(mod) - 10
            print mod + ',',
        print

def main():
    global update, loop, step, pagesize, cpunr, ansi, interval, outputfile, tick, cols

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

    user = getpass.getuser()
    hostname = string.split(os.uname()[1], '.')[0]

    rows, cols = gettermsize()

    ### Write term-title
    if sys.stdout.isatty():
        shell = getenv('XTERM_SHELL')
        term = getenv('TERM')
        if shell == '/bin/bash' and term and re.compile('(screen*|xterm*)').match(term):
            sys.stdout.write('\033]0;(%s@%s) %s %s\007' % (user, hostname, os.path.basename(sys.argv[0]), string.join(op.args, ' ')))

    ### Check terminal capabilities
    op.color = gettermcolor(op.color)

    if op.output:
        if os.path.exists(op.output):
            outputfile = open(op.output, 'a', 0)
            outputfile.write('\n\n')
        else:
            outputfile = open(op.output, 'w', 0)
            outputfile.write('"Dstat %s CSV output"\n' % VERSION)
            outputfile.write('"Author:","Dag Wieers <dag@wieers.com>",,,,"URL:","http://dag.wieers.com/home-made/dstat/"\n')

        outputfile.write('"Host:","%s",,,,"User:","%s"\n' % (hostname, user))
        outputfile.write('"Cmdline:","dstat %s",,,,"Date:","%s"\n\n' % (' '.join(op.args), time.strftime('%d %b %Y %H:%M:%S %Z', time.localtime())))

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

    if not op.update:
        interval = op.delay

    ### Build list of requested modules
    linewidth = 0
    oldvislist = []
    totlist = []
    for module in op.modlist:
        if module in ('list', 'help'):
            listmodules()
            exit(0)
        elif module == 'cpu':  mods = ( 'cpu', 'cpu24' )
        elif module == 'disk': mods = ( 'disk', 'disk24', 'disk24old' )
        elif module == 'int':  mods = ( 'int', 'int24' )
        elif module == 'page': mods = ( 'page', 'page24' )
        elif module == 'swap': mods = ( 'swap', 'swapold' )
        else: mods = ( module, )

        for mod in mods:
            try:
                if 'dstat_'+mod not in globals().keys():
                    import imp
                    file, pathname, description = imp.find_module('dstat_'+mod)

                    ### Try loading python plugin
                    if description[0] == '.py':
#                           exec compile(readfile(pathname), pathname, 'exec')
                            execfile(pathname)
                            exec 'o = dstat_%s()' % mod

                    ### Try loading C plugin
                    elif description[0] == '.so':
                        exec 'import dstat_%s' % mod
                        exec 'o = dstat_%s.new()' % mod
#                        exec 'o = dstat_%s.init(dstat)' % mod
#                        print dir(o)
#                        print o.__module__
#                        print o.name
                    else:
                        info(1, 'Module is of unknown type.')

                    ### Remove defect stat objects and calculate line length
                    if not o.check():
                        raise Exception, 'Unknown problem, please report'
#                except Exception, e:
#                    if mod == mods[-1]:
#                        info(1, 'Module %s has problems. (%s)' % (mod, e))
#                        if op.debug:
#                            raise
#                    continue
                else:
                    exec 'o = dstat_%s()' % mod
#                print o.__module__
            except Exception, e:
                if mod == mods[-1]:
                    info(1, 'Module %s failed to load. (%s)' % (mod, e))
                elif op.debug:
                    info(1, 'Module %s failed to load, trying another. (%s)' % (mod, e))
                if op.debug:
                    raise
#                tb = sys.exc_info()[2]
                continue

            linewidth = linewidth + o.statwidth() + 1
            totlist.append(o)
            break

    if not totlist:
        die(8, 'None of the stats you selected are available.')

    if op.debug:
        for o in totlist:
            print 'Module', string.split(str(o.__class__), '.')[1],
            if hasattr(o, 'file'): print 'requires', o.file,
            print

    if op.output:
        showcsvtitle(1, totlist)
        showcsvtitle(2, totlist)

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

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

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

    tt = 0

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

        if op.debug:
            if step == 1: tt = 0
            t1 = time.time()
            curwidth = 8
        else:
            curwidth = 0

        ### Trim object list to what is visible on screen
        if sys.stdout.isatty():
            rows, cols = gettermsize()
            vislist = []
            for o in totlist:
                newwidth = curwidth + o.statwidth() + 1
                if newwidth <= cols or ( vislist == totlist[:-1] and newwidth < cols ):
                    vislist.append(o)
                    curwidth = newwidth
    
            ### Check when to display the header
            if op.header and rows >= 6:
                if oldvislist != vislist:
                    showheader = True
                elif step == 1 and loop % (rows - 1) == 0:
                    showheader = True

            oldvislist = vislist
        else:
            vislist = totlist

        if showheader:
            if loop == 0 and totlist != vislist:
                info(1, 'Terminal width too small, trimming output.')
            showheader = False
            showtitle(1, totlist, vislist, ansi['darkblue'] + char['space'], ansi['darkblue'] + char['gt'])
            showtitle(2, totlist, vislist, ansi['gray'] + char['pipe'], ansi['darkblue'] + char['gt'])

        ### 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']
        line = ansi['default']

        ### Calculate all objects (visible, invisible)
        oline = ''
        for o in totlist:
            o.extract()
            if o in vislist:
                line = line + o.show() + o.showend(totlist, vislist)
            if op.output and step == op.delay:
                oline = oline + o.showcsv() + o.showcsvend(totlist, vislist)

        ### Print stats
        sys.stdout.write(line + ansi['default'])
        if op.output and step == op.delay:
            outputfile.write(oline + '\n')

        ### Print debugging output
        if op.debug:
            t2 = time.time();
            tt = tt + (t2 - t1) * 1000.0
            if loop == 0: tt = tt * step
            if op.debug == 1:
                sys.stdout.write('%s%6.2fms%s' % (ansi['darkblue'], tt / step, ansi['default']))
            elif op.debug > 1:
                sys.stdout.write('%s%6.2f %s%d:%d:%d%s' % (ansi['darkblue'], tt / step, ansi['gray'], loop, step, update, ansi['default']))

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

        if not op.update:
            sys.stdout.write('\n')

        ### 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 its own line
        if op.update:
            if step == op.delay:
                sys.stdout.write('\n' + ansi['reset'] + ansi['clearline'] + ansi['save'])
            else:
                sys.stdout.write(ansi['restore'])

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

    signal.signal(signal.SIGALRM, signal.SIG_IGN)

### Main entrance
if __name__ == '__main__':
    try:
        initterm()
        op = Options(sys.argv[1:])
        main()
    except KeyboardInterrupt, e:
        print ansi['default']
#   except IOError, e:
#       if e.errno != 32:               ## [Errno 32] Broken pipe
#           print
#           print 'IOError: %s' % e
#           exit(7)
#   except OSError, e:
#       print
#       print 'OSError: %s' % e
#       exit(7)
    exit(0)
else:
    op = Options('')
    step = 1
    tick = ticks()

# vim:ts=4:sw=4:et
