‰PNG  IHDR @ @ ªiqÞ pHYs   šœ —tEXtComment #!/usr/bin/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 from __future__ import generators try: import sys, os, time, sched, re, getopt import types, resource, getpass, glob, linecache except KeyboardInterrupt: pass VERSION = '0.7.2' theme = { 'default': '' } if sys.version_info < (2, 2): sys.exit('error: Python 2.2 or later required') ### Workaround for python <= 2.2.1 try: True, False except NameError: True = 1 False = 0 ### Workaround for python < 2.3 #if 'enumerate' not in __builtins__.__dict__.keys(): if sys.version_info >= (2, 2) and sys.version_info < (2, 3): def enumerate(sequence): index = 0 for item in sequence: yield index, item index = index + 1 elif sys.version_info < (2, 2): def enumerate(sequence): index = 0 seqlist = [] for item in sequence: seqlist.append((index, item)) index = index + 1 return seqlist ### Workaround for python < 2.3 #if 'sum' not in __builtins__.__dict__.keys(): if sys.version_info < (2, 3): def sum(sequence): ret = 0 for i in sequence: ret = ret + i return ret pluginpath = [ os.path.expanduser('~/.dstat/'), # home + /.dstat/ os.path.abspath(os.path.dirname(sys.argv[0])) + '/plugins/', # binary path + /plugins/ '/usr/share/dstat/', '/usr/local/share/dstat/', ] class Options: def __init__(self, args): self.args = args self.bits = False self.blackonwhite = False self.count = -1 self.cpulist = None self.debug = 0 self.delay = 1 self.disklist = None self.full = False self.float = 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 self.pidfile = False self.profile = '' ### List of available plugins allplugins = listplugins() ### List of plugins to show self.plugins = [] ### 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: opts, args = getopt.getopt(args, 'acdfghilmno:prstTvyC:D:I:M:N:S:V', ['all', 'all-plugins', 'bits', 'bw', 'blackonwhite', 'debug', 'filesystem', 'float', 'full', 'help', 'integer', 'list', 'mods', 'modules', 'nocolor', 'noheaders', 'noupdate', 'output=', 'pidfile=', 'profile', 'version', 'vmstat'] + allplugins) except getopt.error, exc: print 'dstat: %s, try dstat -h for a list of all the options' % str(exc) sys.exit(1) for opt, arg in opts: if opt in ['-c']: self.plugins.append('cpu') elif opt in ['-C']: self.cpulist = arg.split(',') elif opt in ['-d']: self.plugins.append('disk') elif opt in ['-D']: self.disklist = self.get_disklist(arg) elif opt in ['--filesystem']: self.plugins.append('fs') elif opt in ['-g']: self.plugins.append('page') elif opt in ['-i']: self.plugins.append('int') elif opt in ['-I']: self.intlist = arg.split(',') elif opt in ['-l']: self.plugins.append('load') elif opt in ['-m']: self.plugins.append('mem') elif opt in ['-M', '--mods', '--modules']: print >>sys.stderr, 'WARNING: Option %s is deprecated, please use --%s instead' % (opt, ' --'.join(arg.split(','))) self.plugins += arg.split(',') elif opt in ['-n']: self.plugins.append('net') elif opt in ['-N']: self.netlist = arg.split(',') elif opt in ['-p']: self.plugins.append('proc') elif opt in ['-r']: self.plugins.append('io') elif opt in ['-s']: self.plugins.append('swap') elif opt in ['-S']: self.swaplist = arg.split(',') elif opt in ['-t']: self.plugins.append('time') elif opt in ['-T']: self.plugins.append('epoch') elif opt in ['-y']: self.plugins.append('sys') elif opt in ['-a', '--all']: self.plugins += [ 'cpu', 'disk', 'net', 'page', 'sys' ] elif opt in ['-v', '--vmstat']: self.plugins += [ 'proc', 'mem', 'page', 'disk', 'sys', 'cpu' ] elif opt in ['-f', '--full']: self.full = True elif opt in ['--all-plugins']: ### Make list unique in a fancy fast way plugins = {}.fromkeys(allplugins).keys() plugins.sort() self.plugins += plugins elif opt in ['--bits']: self.bits = True elif opt in ['--bw', '--black-on-white']: self.blackonwhite = True elif opt in ['--debug']: self.debug = self.debug + 1 elif opt in ['--float']: self.float = True elif opt in ['--integer']: self.integer = True elif opt in ['--list']: showplugins() sys.exit(0) 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 ['--pidfile']: self.pidfile = arg elif opt in ['--profile']: self.profile = 'dstat_profile.log' elif opt in ['-h', '--help']: self.usage() self.help() sys.exit(0) elif opt in ['-V', '--version']: self.version() sys.exit(0) elif opt.startswith('--'): self.plugins.append(opt[2:]) else: print 'dstat: option %s unknown to getopt, try dstat -h for a list of all the options' % opt sys.exit(1) if self.float and self.integer: print 'dstat: option --float and --integer are mutual exlusive, you can only force one' sys.exit(1) if not self.plugins: print 'You did not select any stats, using -cdngy by default.' self.plugins = [ '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 get_disklist(self, disks): disklist = disks.split(',') ret = [] for disk in disklist: # e.g. /dev/sda1 if disk[:5] == '/dev/': # file or symlink if os.path.exists(disk): # e.g. /dev/disk/by-uuid/15e40cc5-85de-40ea-b8fb-cb3a2eaf872 if os.path.islink(disk): target = os.readlink(disk) # convert relative pathname to absolute if target[0] != '/': target = os.path.join(os.path.dirname(disk), target) target = os.path.normpath(target) print 'dstat: symlink %s -> %s' % (disk, target) disk = target # trim leading /dev/ disk = disk[5:] ret.append(disk) else: print 'dstat: %s does not exist' % disk else: ret.append(disk) return ret def version(self): print 'Dstat %s' % VERSION print 'Written by Dag Wieers ' 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)' % (os.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 showplugins() 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 -r, --io enable io stats (I/O requests completed) -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 --aio enable aio stats --fs, --filesystem enable fs stats --ipc enable ipc stats --lock enable lock stats --raw enable raw stats --socket enable socket stats --tcp enable tcp stats --udp enable udp stats --unix enable unix stats --vm enable vm stats --plugin-name enable plugins by plugin name (see manual) --list list all available plugins -a, --all equals -cdngy (default) -f, --full automatically expand -C, -D, -I, -N and -S lists -v, --vmstat equals -pmgdsc -D total --bits force bits for values expressed in bytes --float force float values on screen --integer force integer values on screen --bw, --blackonwhite change colors for white background terminal --nocolor disable colors (implies --noupdate) --noheaders disable repetitive headers --noupdate disable intermediate updates --output file write CSV output to file --profile show profiling statistics when exiting dstat delay is the delay in seconds between each update (default: 1) count is the number of updates to display before exiting (default: unlimited) ''' ### START STATS DEFINITIONS ### class dstat: vars = None name = None nick = None type = 'f' width = 5 scale = 1024 cols = 0 # val = {} # set1 = {} # set2 = {} def prepare(self): if callable(self.discover): self.discover = self.discover() if callable(self.vars): self.vars = self.vars() if not self.vars: raise Exception, 'No counter objects to monitor' if callable(self.name): self.name = self.name() if callable(self.nick): self.nick = self.nick() if not self.nick: self.nick = self.vars self.val = {}; self.set1 = {}; self.set2 = {} if self.cols <= 0: for name in self.vars: self.val[name] = self.set1[name] = self.set2[name] = 0 else: for name in self.vars + [ 'total', ]: self.val[name] = range(self.cols) self.set1[name] = range(self.cols) self.set2[name] = range(self.cols) for i in range(self.cols): self.val[name][i] = self.set1[name][i] = self.set2[name][i] = 0 # print self.val def open(self, *filenames): "Open stat file descriptor" self.file = [] self.fd = [] for filename in filenames: try: fd = dopen(filename) if fd: self.file.append(filename) self.fd.append(fd) except: pass if not self.fd: raise Exception, 'Cannot open file %s' % filename def readlines(self): "Return lines from any file descriptor" for fd in self.fd: fd.seek(0) for line in fd.readlines(): yield line ### Implemented linecache (for top-plugins) but slows down normal plugins # for fd in self.fd: # i = 1 # while True: # line = linecache.getline(fd.name, i); # if not line: break # yield line # i += 1 def splitline(self, sep=None): for fd in self.fd: fd.seek(0) return fd.read().split(sep) def splitlines(self, sep=None, replace=None): "Return split lines from any file descriptor" for fd in self.fd: fd.seek(0) for line in fd.readlines(): if replace and sep: yield line.replace(replace, sep).split(sep) elif replace: yield line.replace(replace, ' ').split() else: yield line.split(sep) # ### Implemented linecache (for top-plugins) but slows down normal plugins # for fd in self.fd: # if replace and sep: # yield line.replace(replace, sep).split(sep) # elif replace: # yield line.replace(replace, ' ').split() # else: # yield line.split(sep) # i += 1 def statwidth(self): "Return complete stat width" if self.cols: return len(self.vars) * self.colwidth() + len(self.vars) - 1 else: return len(self.nick) * self.colwidth() + len(self.nick) - 1 def colwidth(self): "Return column width" if isinstance(self.name, types.StringType): return self.width else: return len(self.nick) * self.width + len(self.nick) - 1 def title(self): ret = theme['title'] if isinstance(self.name, types.StringType): width = self.statwidth() return ret + self.name[0:width].center(width, char['space']).replace(' ', '-') + theme['default'] for i, name in enumerate(self.name): width = self.colwidth() ret = ret + name[0:width].center(width, char['space']).replace(' ', '-') if i + 1 != len(self.name): if op.color: ret = ret + theme['frame'] + char['dash'] + theme['title'] else: ret = ret + char['space'] return ret def subtitle(self): ret = '' if isinstance(self.name, types.StringType): for i, nick in enumerate(self.nick): ret = ret + theme['subtitle'] + nick[0:self.width].center(self.width, char['space']) + theme['default'] if i + 1 != len(self.nick): ret = ret + char['space'] return ret else: for i, name in enumerate(self.name): for j, nick in enumerate(self.nick): ret = ret + theme['subtitle'] + nick[0:self.width].center(self.width, char['space']) + theme['default'] if j + 1 != len(self.nick): ret = ret + char['space'] if i + 1 != len(self.name): ret = ret + theme['frame'] + char['colon'] return ret def csvtitle(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 csvsubtitle(self): ret = '' if isinstance(self.name, types.StringType): for i, nick in enumerate(self.nick): ret = ret + '"' + nick + '"' if i + 1 != len(self.nick): ret = ret + ',' return ret else: 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.fd 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.colwidth(): return True raise Exception, 'Unknown problem, please report' def discover(self, *objlist): return True def show(self): "Display stat results" line = '' if hasattr(self, 'output'): return cprint(self.output, self.type, self.width, self.scale) 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.type, self.width, self.scale) sep = theme['frame'] + char['colon'] else: line = line + cprint(self.val[name], self.type, self.width, self.scale) 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 theme['frame'] + char['pipe'] elif totlist != vislist: return theme['frame'] + 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_aio(dstat): def __init__(self): self.name = 'async' self.nick = ('#aio',) self.vars = ('aio',) self.type = 'd' self.width = 5; self.open('/proc/sys/fs/aio-nr') def extract(self): for l in self.splitlines(): if len(l) < 1: continue self.val['aio'] = long(l[0]) class dstat_cpu(dstat): def __init__(self): self.nick = ( 'usr', 'sys', 'idl', 'wai', 'hiq', 'siq' ) self.type = 'p' self.width = 3 self.scale = 34 self.open('/proc/stat') self.cols = 6 def discover(self, *objlist): ret = [] for l in self.splitlines(): if len(l) < 8 or l[0][0:3] != 'cpu': continue ret.append(l[0][3:]) ret.sort() for item in objlist: ret.append(item) return ret def vars(self): ret = [] if op.cpulist: varlist = op.cpulist elif not op.full: varlist = ('total',) else: varlist = [] cpu = 0 while cpu < cpunr: varlist.append(str(cpu)) cpu = cpu + 1 # if len(varlist) > 2: varlist = varlist[0:2] for name in varlist: if name in self.discover + ['total']: ret.append(name) 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 extract(self): for l in self.splitlines(): if len(l) < 8: continue for name in self.vars: if l[0] == 'cpu' + name or ( l[0] == 'cpu' and name == 'total' ): self.set2[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): if sum(self.set2[name]) > sum(self.set1[name]): self.val[name][i] = 100.0 * (self.set2[name][i] - self.set1[name][i]) / (sum(self.set2[name]) - sum(self.set1[name])) else: self.val[name][i] = 0 # print >>sys.stderr, "Error: tick problem detected, this should never happen !" if step == op.delay: self.set1.update(self.set2) class dstat_cpu24(dstat): def __init__(self): self.nick = ( 'usr', 'sys', 'idl') self.type = 'p' self.width = 3 self.scale = 34 self.open('/proc/stat') self.cols = 3 def discover(self, *objlist): ret = [] for l in self.splitlines(): if len(l) != 5 or l[0][0:3] != 'cpu': continue ret.append(l[0][3:]) ret.sort() for item in objlist: ret.append(item) return ret def vars(self): ret = [] if op.cpulist: varlist = op.cpulist elif not op.full: varlist = ('total',) else: varlist = [] cpu = 0 while cpu < cpunr: varlist.append(str(cpu)) cpu = cpu + 1 # if len(varlist) > 2: varlist = varlist[0:2] for name in varlist: if name in self.discover + ['total']: ret.append(name) return ret def name(self): ret = [] for name in self.vars: if name == 'total': ret.append('cpu usage') else: ret.append('cpu' + name) return ret def extract(self): for l in self.splitlines(): for name in self.vars: if l[0] == 'cpu' + name or ( l[0] == 'cpu' and name == 'total' ): self.set2[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.set2[name][i] - self.set1[name][i]) / (sum(self.set2[name]) - sum(self.set1[name])) if step == op.delay: self.set1.update(self.set2) class dstat_disk(dstat): def __init__(self): self.nick = ('read', 'writ') self.type = 'b' self.diskfilter = re.compile('^(dm-\d+|md\d+|[hsv]d[a-z]+\d+)$') self.open('/proc/diskstats') self.cols = 2 def discover(self, *objlist): ret = [] for l in self.splitlines(): if len(l) < 13: continue if l[3:] == ['0',] * 11: continue name = l[2] ret.append(name) for item in objlist: ret.append(item) if not ret: raise Exception, "No suitable block devices found to monitor" return ret def vars(self): ret = [] if op.disklist: varlist = op.disklist elif not op.full: varlist = ('total',) else: varlist = [] for name in self.discover: if self.diskfilter.match(name): continue if name not in blockdevices(): continue varlist.append(name) # if len(varlist) > 2: varlist = varlist[0:2] varlist.sort() for name in varlist: if name in self.discover + ['total'] + op.diskset.keys(): ret.append(name) return ret def name(self): return ['dsk/'+sysfs_dev(name) for name in self.vars] def extract(self): for name in self.vars: self.set2[name] = (0, 0) for l in self.splitlines(): 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 self.diskfilter.match(name): self.set2['total'] = ( self.set2['total'][0] + long(l[5]), self.set2['total'][1] + long(l[9]) ) if name in self.vars and name != 'total': self.set2[name] = ( self.set2[name][0] + long(l[5]), self.set2[name][1] + long(l[9]) ) for diskset in self.vars: if diskset in op.diskset.keys(): for disk in op.diskset[diskset]: if re.match('^'+disk+'$', name): self.set2[diskset] = ( self.set2[diskset][0] + long(l[5]), self.set2[diskset][1] + long(l[9]) ) for name in self.set2.keys(): self.val[name] = ( (self.set2[name][0] - self.set1[name][0]) * 512.0 / elapsed, (self.set2[name][1] - self.set1[name][1]) * 512.0 / elapsed, ) if step == op.delay: self.set1.update(self.set2) class dstat_disk24(dstat): def __init__(self): self.nick = ('read', 'writ') self.type = 'b' self.diskfilter = re.compile('(dm-\d+|md\d+|[hsv]d[a-z]+\d+)') self.open('/proc/partitions') if self.fd and not self.discover: raise Exception, 'Kernel is not compiled with CONFIG_BLK_STATS' self.cols = 2 def discover(self, *objlist): ret = [] for l in self.splitlines(): if len(l) < 15 or l[0] == 'major' or int(l[1]) % 16 != 0: continue name = l[3] ret.append(name) for item in objlist: ret.append(item) if not ret: raise Exception, "No suitable block devices found to monitor" return ret def vars(self): ret = [] if op.disklist: varlist = op.disklist elif not op.full: varlist = ('total',) else: varlist = [] for name in self.discover: if self.diskfilter.match(name): continue varlist.append(name) # if len(varlist) > 2: varlist = varlist[0:2] varlist.sort() for name in varlist: if name in self.discover + ['total'] + op.diskset.keys(): ret.append(name) return ret def name(self): return ['dsk/'+sysfs_dev(name) for name in self.vars] def extract(self): for name in self.vars: self.set2[name] = (0, 0) for l in self.splitlines(): if len(l) < 15 or l[0] == 'major' or int(l[1]) % 16 != 0: continue name = l[3] if not self.diskfilter.match(name): self.set2['total'] = ( self.set2['total'][0] + long(l[6]), self.set2['total'][1] + long(l[10]) ) if name in self.vars: self.set2[name] = ( self.set2[name][0] + long(l[6]), self.set2[name][1] + long(l[10]) ) for diskset in self.vars: if diskset in op.diskset.keys(): for disk in op.diskset[diskset]: if re.match('^'+disk+'$', name): self.set2[diskset] = ( self.set2[diskset][0] + long(l[6]), self.set2[diskset][1] + long(l[10]) ) for name in self.set2.keys(): self.val[name] = ( (self.set2[name][0] - self.set1[name][0]) * 512.0 / elapsed, (self.set2[name][1] - self.set1[name][1]) * 512.0 / elapsed, ) if step == op.delay: self.set1.update(self.set2) ### FIXME: Needs rework, does anyone care ? class dstat_disk24old(dstat): def __init__(self): self.nick = ('read', 'writ') self.type = 'b' self.diskfilter = re.compile('(dm-\d+|md\d+|[hsv]d[a-z]+\d+)') self.regexp = re.compile('^\((\d+),(\d+)\):\(\d+,\d+,(\d+),\d+,(\d+)\)$') self.open('/proc/stat') self.cols = 2 def discover(self, *objlist): ret = [] for l in self.splitlines(':'): if len(l) < 3: continue name = l[0] if name != 'disk_io': continue for pair in line.split()[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 objlist: ret.append(item) if not ret: raise Exception, "No suitable block devices found to monitor" return ret def vars(self): ret = [] if op.disklist: varlist = op.disklist elif not op.full: varlist = ('total',) else: varlist = [] for name in self.discover: if self.diskfilter.match(name): continue varlist.append(name) # if len(varlist) > 2: varlist = varlist[0:2] varlist.sort() for name in varlist: if name in self.discover + ['total'] + op.diskset.keys(): ret.append(name) return ret def name(self): return ['dsk/'+name for name in self.vars] def extract(self): for name in self.vars: self.set2[name] = (0, 0) for line in self.splitlines(':'): if len(l) < 3: continue name = l[0] if name != 'disk_io': continue for pair in line.split()[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 self.diskfilter.match(name): self.set2['total'] = ( self.set2['total'][0] + long(l[2]), self.set2['total'][1] + long(l[3]) ) if name in self.vars and name != 'total': self.set2[name] = ( self.set2[name][0] + long(l[2]), self.set2[name][1] + long(l[3]) ) for diskset in self.vars: if diskset in op.diskset.keys(): for disk in op.diskset[diskset]: if re.match('^'+disk+'$', name): self.set2[diskset] = ( self.set2[diskset][0] + long(l[2]), self.set2[diskset][1] + long(l[3]) ) break for name in self.set2.keys(): self.val[name] = ( (self.set2[name][0] - self.set1[name][0]) * 512.0 / elapsed, (self.set2[name][1] - self.set1[name][1]) * 512.0 / elapsed, ) if step == op.delay: self.set1.update(self.set2) class dstat_epoch(dstat): def __init__(self): self.name = 'epoch' self.vars = ('epoch',) self.width = 10 if op.debug: self.width = 13 self.scale = 0 ### We are now using the starttime instead of the execution time of this plugin def extract(self): # self.val['epoch'] = time.time() self.val['epoch'] = starttime class dstat_fs(dstat): def __init__(self): self.name = 'filesystem' self.vars = ('files', 'inodes') self.type = 'd' self.width = 6 self.scale = 1000 def extract(self): for line in dopen('/proc/sys/fs/file-nr'): l = line.split() if len(l) < 1: continue self.val['files'] = long(l[0]) for line in dopen('/proc/sys/fs/inode-nr'): l = line.split() if len(l) < 2: continue self.val['inodes'] = long(l[0]) - long(l[1]) class dstat_int(dstat): def __init__(self): self.name = 'interrupts' self.type = 'd' self.width = 5 self.scale = 1000 self.open('/proc/stat') self.intmap = self.intmap() def intmap(self): ret = {} for line in dopen('/proc/interrupts'): l = line.split() if len(l) <= cpunr: continue l1 = l[0].split(':')[0] l2 = ' '.join(l[cpunr+2:]).split(',') ret[l1] = l1 for name in l2: ret[name.strip().lower()] = l1 return ret def discover(self, *objlist): ret = [] for l in self.splitlines(): 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 l in self.fd[0].splitlines(): # if l[0] != 'intr': continue # return True # return False def vars(self): ret = [] if op.intlist: varlist = op.intlist else: varlist = self.discover for name in varlist: if name in ('0', '1', '2', '8', 'NMI', 'LOC', 'MIS', 'CPU0'): varlist.remove(name) if not op.full and len(varlist) > 3: varlist = varlist[-3:] for name in varlist: if name in self.discover + ['total',]: ret.append(name) elif name.lower() in self.intmap.keys(): ret.append(self.intmap[name.lower()]) return ret def extract(self): for l in self.splitlines(): if not l or l[0] != 'intr': continue for name in self.vars: if name != 'total': self.set2[name] = long(l[int(name) + 2]) self.set2['total'] = long(l[1]) for name in self.vars: self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed if step == op.delay: self.set1.update(self.set2) class dstat_int24(dstat): def __init__(self): self.name = 'interrupts' self.type = 'd' self.width = 5 self.scale = 1000 self.open('/proc/interrupts') def intmap(self): ret = {} for l in self.splitlines(): if len(l) <= cpunr: continue l1 = l[0].split(':')[0] l2 = ' '.join(l[cpunr+2:]).split(',') ret[l1] = l1 for name in l2: ret[name.strip().lower()] = l1 return ret def discover(self, *objlist): ret = [] for l in self.splitlines(): if len(l) < cpunr+1: continue name = l[0].split(':')[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 l in self.fd[0].splitlines(): # if l[0] != 'intr' or len(l) > 2: continue # return True # return False def vars(self): ret = [] if op.intlist: varlist = op.intlist else: varlist = self.discover for name in varlist: if name in ('0', '1', '2', '8', 'CPU0', 'ERR', 'LOC', 'MIS', 'NMI'): varlist.remove(name) if not op.full and len(varlist) > 3: varlist = varlist[-3:] for name in varlist: if name in self.discover: ret.append(name) elif name.lower() in self.intmap.keys(): ret.append(self.intmap[name.lower()]) return ret def extract(self): for l in self.splitlines(): if len(l) < cpunr+1: continue name = l[0].split(':')[0] if name in self.vars: self.set2[name] = 0 for i in l[1:1+cpunr]: self.set2[name] = self.set2[name] + long(i) # elif len(l) > 2 + cpunr: # for hw in self.vars: # for mod in l[2+cpunr:]: # self.set2[mod] = long(l[1]) for name in self.set2.keys(): self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed if step == op.delay: self.set1.update(self.set2) class dstat_io(dstat): def __init__(self): self.nick = ('read', 'writ') self.type = 'f' self.width = 5 self.scale = 1000 self.diskfilter = re.compile('(dm-\d+|md\d+|[hsv]d[a-z]+\d+)') self.open('/proc/diskstats') self.cols = 3 def discover(self, *objlist): ret = [] for l in self.splitlines(): if len(l) < 13: continue if l[3:] == ['0',] * 11: continue name = l[2] ret.append(name) for item in objlist: ret.append(item) if not ret: raise Exception, "No suitable block devices found to monitor" return ret def vars(self): ret = [] if op.disklist: varlist = op.disklist elif not op.full: varlist = ('total',) else: varlist = [] for name in self.discover: if self.diskfilter.match(name): continue if name not in blockdevices(): continue varlist.append(name) # if len(varlist) > 2: varlist = varlist[0:2] varlist.sort() for name in varlist: if name in self.discover + ['total'] + op.diskset.keys(): ret.append(name) return ret def name(self): return ['io/'+name for name in self.vars] def extract(self): for name in self.vars: self.set2[name] = (0, 0) for l in self.splitlines(): if len(l) < 13: continue if l[3] == '0' and l[7] == '0': continue name = l[2] if l[3:] == ['0',] * 11: continue if not self.diskfilter.match(name): self.set2['total'] = ( self.set2['total'][0] + long(l[3]), self.set2['total'][1] + long(l[7]) ) if name in self.vars and name != 'total': self.set2[name] = ( self.set2[name][0] + long(l[3]), self.set2[name][1] + long(l[7]) ) for diskset in self.vars: if diskset in op.diskset.keys(): for disk in op.diskset[diskset]: if re.match('^'+disk+'$', name): self.set2[diskset] = ( self.set2[diskset][0] + long(l[3]), self.set2[diskset][1] + long(l[7]) ) for name in self.set2.keys(): self.val[name] = ( (self.set2[name][0] - self.set1[name][0]) * 1.0 / elapsed, (self.set2[name][1] - self.set1[name][1]) * 1.0 / elapsed, ) if step == op.delay: self.set1.update(self.set2) class dstat_ipc(dstat): def __init__(self): self.name = 'sysv ipc' self.vars = ('msg', 'sem', 'shm') self.type = 'd' self.width = 3 self.scale = 10 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.nick = ('1m', '5m', '15m') self.vars = ('load1', 'load5', 'load15') self.type = 'f' self.width = 4 self.scale = 0.5 self.open('/proc/loadavg') def extract(self): for l in self.splitlines(): 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.nick = ('pos', 'lck', 'rea', 'wri') self.vars = ('posix', 'flock', 'read', 'write') self.type = 'f' self.width = 3 self.scale = 10 self.open('/proc/locks') def extract(self): for name in self.vars: self.val[name] = 0 for l in self.splitlines(): if len(l) < 4: continue if l[1] == 'POSIX': self.val['posix'] += 1 elif l[1] == 'FLOCK': self.val['flock'] += 1 if l[3] == 'READ': self.val['read'] += 1 elif l[3] == 'WRITE': self.val['write'] += 1 class dstat_mem(dstat): def __init__(self): self.name = 'memory usage' self.nick = ('used', 'buff', 'cach', 'free') self.vars = ('MemUsed', 'Buffers', 'Cached', 'MemFree') self.open('/proc/meminfo') def extract(self): for l in self.splitlines(): 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.nick = ('recv', 'send') self.type = 'b' self.totalfilter = re.compile('^(lo|bond\d+|face|.+\.\d+)$') self.open('/proc/net/dev') self.cols = 2 def discover(self, *objlist): ret = [] for l in self.splitlines(replace=':'): 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 objlist: ret.append(item) return ret def vars(self): ret = [] if op.netlist: varlist = op.netlist elif not op.full: varlist = ('total',) else: varlist = self.discover # if len(varlist) > 2: varlist = varlist[0:2] varlist.sort() for name in varlist: if name in self.discover + ['total', 'lo']: ret.append(name) if not ret: raise Exception, "No suitable network interfaces found to monitor" return ret def name(self): return ['net/'+name for name in self.vars] def extract(self): self.set2['total'] = [0, 0] for l in self.splitlines(replace=':'): if len(l) < 17: continue if l[2] == '0' and l[10] == '0': continue name = l[0] if name in self.vars : self.set2[name] = ( long(l[1]), long(l[9]) ) if not self.totalfilter.match(name): self.set2['total'] = ( self.set2['total'][0] + long(l[1]), self.set2['total'][1] + long(l[9])) if update: for name in self.set2.keys(): self.val[name] = [ (self.set2[name][0] - self.set1[name][0]) * 1.0 / elapsed, (self.set2[name][1] - self.set1[name][1]) * 1.0 / elapsed, ] if self.val[name][0] < 0: self.val[name][0] += maxint + 1 if self.val[name][1] < 0: self.val[name][1] += maxint + 1 if step == op.delay: self.set1.update(self.set2) class dstat_page(dstat): def __init__(self): self.name = 'paging' self.nick = ('in', 'out') self.vars = ('pswpin', 'pswpout') self.type = 'd' self.open('/proc/vmstat') def extract(self): for l in self.splitlines(): if len(l) < 2: continue name = l[0] if name in self.vars: self.set2[name] = long(l[1]) for name in self.vars: self.val[name] = (self.set2[name] - self.set1[name]) * pagesize * 1.0 / elapsed if step == op.delay: self.set1.update(self.set2) class dstat_page24(dstat): def __init__(self): self.name = 'paging' self.nick = ('in', 'out') self.vars = ('pswpin', 'pswpout') self.type = 'd' self.open('/proc/stat') def extract(self): for l in self.splitlines(): if len(l) < 3: continue name = l[0] if name != 'swap': continue self.set2['pswpin'] = long(l[1]) self.set2['pswpout'] = long(l[2]) break for name in self.vars: self.val[name] = (self.set2[name] - self.set1[name]) * pagesize * 1.0 / elapsed if step == op.delay: self.set1.update(self.set2) class dstat_proc(dstat): def __init__(self): self.name = 'procs' self.nick = ('run', 'blk', 'new') self.vars = ('procs_running', 'procs_blocked', 'processes') self.type = 'f' self.width = 3 self.scale = 10 self.open('/proc/stat') def extract(self): for l in self.splitlines(): if len(l) < 2: continue name = l[0] if name == 'processes': self.val['processes'] = 0 self.set2[name] = long(l[1]) elif name == 'procs_running': self.set2[name] = self.set2[name] + long(l[1]) - 1 elif name == 'procs_blocked': self.set2[name] = self.set2[name] + long(l[1]) self.val['processes'] = (self.set2['processes'] - self.set1['processes']) * 1.0 / elapsed for name in ('procs_running', 'procs_blocked'): self.val[name] = self.set2[name] * 1.0 / elapsed if step == op.delay: self.set1.update(self.set2) for name in ('procs_running', 'procs_blocked'): self.set2[name] = 0 class dstat_raw(dstat): def __init__(self): self.name = 'raw' self.nick = ('raw',) self.vars = ('sockets',) self.type = 'd' self.width = 3 self.scale = 100 self.open('/proc/net/raw') def extract(self): lines = -1 for line in self.readlines(): lines += 1 self.val['sockets'] = lines ### Cannot use len() on generator # self.val['sockets'] = len(self.readlines()) - 1 class dstat_socket(dstat): def __init__(self): self.name = 'sockets' self.type = 'd' self.width = 3 self.scale = 100 self.open('/proc/net/sockstat') self.nick = ('tot', 'tcp', 'udp', 'raw', 'frg') self.vars = ('sockets:', 'TCP:', 'UDP:', 'RAW:', 'FRAG:') def extract(self): for l in self.splitlines(): if len(l) < 3: continue self.val[l[0]] = long(l[2]) self.val['other'] = self.val['sockets:'] - self.val['TCP:'] - self.val['UDP:'] - self.val['RAW:'] - self.val['FRAG:'] class dstat_swap(dstat): def __init__(self): self.name = 'swap' self.nick = ('used', 'free') self.type = 'd' self.open('/proc/swaps') def discover(self, *objlist): ret = [] for l in self.splitlines(): if len(l) < 5: continue if l[0] == 'Filename': continue try: int(l[2]) int(l[3]) except: continue # ret.append(improve(l[0])) ret.append(l[0]) ret.sort() for item in objlist: ret.append(item) return ret def vars(self): ret = [] if op.swaplist: varlist = op.swaplist elif not op.full: varlist = ('total',) else: varlist = self.discover # if len(varlist) > 2: varlist = varlist[0:2] varlist.sort() for name in varlist: if name in self.discover + ['total']: ret.append(name) if not ret: raise Exception, "No suitable swap devices found to monitor" return ret def name(self): return ['swp/'+improve(name) for name in self.vars] def extract(self): self.val['total'] = [0, 0] for l in self.splitlines(): 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.nick = ('used', 'free') self.vars = ('SwapUsed', 'SwapFree') self.type = 'd' self.open('/proc/meminfo') def extract(self): for l in self.splitlines(): 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.nick = ('int', 'csw') self.vars = ('intr', 'ctxt') self.type = 'd' self.width = 5 self.scale = 1000 self.open('/proc/stat') def extract(self): for l in self.splitlines(): if len(l) < 2: continue name = l[0] if name in self.vars: self.set2[name] = long(l[1]) for name in self.vars: self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed if step == op.delay: self.set1.update(self.set2) class dstat_tcp(dstat): def __init__(self): self.name = 'tcp sockets' self.nick = ('lis', 'act', 'syn', 'tim', 'clo') self.vars = ('listen', 'established', 'syn', 'wait', 'close') self.type = 'd' self.width = 3 self.scale = 100 self.open('/proc/net/tcp', '/proc/net/tcp6') def extract(self): for name in self.vars: self.val[name] = 0 for l in self.splitlines(): 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'] += 1 elif l[3] in ('01',): self.val['established'] += 1 elif l[3] in ('02', '03', '09',): self.val['syn'] += 1 elif l[3] in ('06',): self.val['wait'] += 1 elif l[3] in ('04', '05', '07', '08', '0B',): self.val['close'] += 1 class dstat_time(dstat): def __init__(self): self.name = 'system' self.timefmt = os.getenv('DSTAT_TIMEFMT') or '%d-%m %H:%M:%S' self.type = 's' if op.debug: self.width = len(time.strftime(self.timefmt, time.localtime())) + 4 else: self.width = len(time.strftime(self.timefmt, time.localtime())) self.scale = 0 self.vars = ('time',) ### We are now using the starttime for this plugin, not the execution time of this plugin def extract(self): if op.debug: self.val['time'] = time.strftime(self.timefmt, time.localtime(starttime)) + ".%03d" % (round(starttime * 1000 % 1000 )) else: self.val['time'] = time.strftime(self.timefmt, time.localtime(starttime)) class dstat_udp(dstat): def __init__(self): self.name = 'udp' self.nick = ('lis', 'act') self.vars = ('listen', 'established') self.type = 'd' self.width = 3 self.scale = 100 self.open('/proc/net/udp', '/proc/net/udp6') def extract(self): for name in self.vars: self.val[name] = 0 for l in self.splitlines(): if l[3] == '07': self.val['listen'] += 1 elif l[3] == '01': self.val['established'] += 1 class dstat_unix(dstat): def __init__(self): self.name = 'unix sockets' self.nick = ('dgm', 'str', 'lis', 'act') self.vars = ('datagram', 'stream', 'listen', 'established') self.type = 'd' self.width = 3 self.scale = 100 self.open('/proc/net/unix') def extract(self): for name in self.vars: self.val[name] = 0 for l in self.splitlines(): if l[4] == '0002': self.val['datagram'] += 1 elif l[4] == '0001': self.val['stream'] += 1 if l[5] == '01': self.val['listen'] += 1 elif l[5] == '03': self.val['established'] += 1 class dstat_vm(dstat): def __init__(self): self.name = 'virtual memory' self.nick = ('majpf', 'minpf', 'alloc', 'free') self.vars = ('pgmajfault', 'pgfault', 'pgalloc', 'pgfree') self.type = 'd' self.width = 5 self.scale = 1000 self.open('/proc/vmstat') ### Page allocations should include all page zones, not just ZONE_NORMAL, ### but also ZONE_DMA, ZONE_HIGHMEM, ZONE_DMA32 (depending on architecture) def extract(self): self.set2['pgalloc'] = 0 for l in self.splitlines(): if len(l) < 2: continue if l[0].startswith('pgalloc_'): self.set2['pgalloc'] += long(l[1]) elif l[0] in self.vars: self.set2[l[0]] = long(l[1]) for name in self.vars: self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed if step == op.delay: self.set1.update(self.set2) ### 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', 'gray': '\033[0;37m', 'darkgray': '\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', 'linewrap': '\033[7h', 'nolinewrap': '\033[7l', 'up': '\033[1A', 'down': '\033[1B', 'right': '\033[1C', 'left': '\033[1D', 'default': '\033[0;0m', } char = { 'pipe': '|', 'colon': ':', 'gt': '>', 'space': ' ', 'dash': '-', 'plus': '+', 'underscore': '_', } def set_theme(): "Provide a set of colors to use" if op.blackonwhite: theme = { 'title': ansi['darkblue'], 'subtitle': ansi['darkcyan'] + ansi['underline'], 'frame': ansi['darkblue'], 'default': ansi['default'], 'error': ansi['white'] + ansi['redbg'], 'roundtrip': ansi['darkblue'], 'debug': ansi['darkred'], 'input': ansi['darkgray'], 'text_lo': ansi['black'], 'text_hi': ansi['darkgray'], 'unit_lo': ansi['black'], 'unit_hi': ansi['darkgray'], 'colors_lo': (ansi['darkred'], ansi['darkmagenta'], ansi['darkgreen'], ansi['darkblue'], ansi['darkcyan'], ansi['gray'], ansi['red'], ansi['green']), 'colors_hi': (ansi['red'], ansi['magenta'], ansi['green'], ansi['blue'], ansi['cyan'], ansi['white'], ansi['darkred'], ansi['darkgreen']), } else: theme = { 'title': ansi['darkblue'], 'subtitle': ansi['blue'] + ansi['underline'], 'frame': ansi['darkblue'], 'default': ansi['default'], 'error': ansi['white'] + ansi['redbg'], 'roundtrip': ansi['darkblue'], 'debug': ansi['darkred'], 'input': ansi['darkgray'], 'text_lo': ansi['gray'], 'text_hi': ansi['darkgray'], 'unit_lo': ansi['darkgray'], 'unit_hi': ansi['darkgray'], 'colors_lo': (ansi['red'], ansi['yellow'], ansi['green'], ansi['blue'], ansi['cyan'], ansi['white'], ansi['darkred'], ansi['darkgreen']), 'colors_hi': (ansi['darkred'], ansi['darkyellow'], ansi['darkgreen'], ansi['darkblue'], ansi['darkcyan'], ansi['gray'], ansi['red'], ansi['green']), } return theme def ticks(): "Return the number of 'ticks' since bootup" try: for line in open('/proc/uptime', 'r', 0).readlines(): l = line.split() if len(l) < 2: continue return float(l[0]) except: for line in dopen('/proc/stat').readlines(): l = line.split() if len(l) < 2: continue if l[0] == 'btime': return time.time() - long(l[1]) def improve(devname): "Improve a device name" if devname.startswith('/dev/mapper/'): devname = devname.split('/')[3] elif devname.startswith('/dev/'): devname = devname.split('/')[2] return devname def dopen(filename): "Open a file for reuse, if already opened, return file descriptor" global fds if not os.path.exists(filename): raise Exception, 'File %s does not exist' % filename # return None if 'fds' not in globals().keys(): fds = {} if file not in fds.keys(): fds[filename] = open(filename, 'r', 0) else: fds[filename].seek(0) return fds[filename] def dclose(filename): "Close an open file and remove file descriptor from list" global fds if not 'fds' in globals().keys(): fds = {} if filename in fds: fds[filename].close() del(fds[filename]) def dpopen(cmd): "Open a pipe for reuse, if already opened, return pipes" global pipes, select import select 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(fileobj, tmout = 0.001): "Read available data from pipe in a non-blocking fashion" ret = '' while not select.select([fileobj.fileno()], [], [], tmout)[0]: pass while select.select([fileobj.fileno()], [], [], tmout)[0]: ret = ret + fileobj.read(1) return ret.split('\n') def greppipe(fileobj, str, tmout = 0.001): "Grep available data from pipe in a non-blocking fashion" ret = '' while not select.select([fileobj.fileno()], [], [], tmout)[0]: pass while select.select([fileobj.fileno()], [], [], tmout)[0]: character = fileobj.read(1) if character != '\n': ret = ret + character elif ret.startswith(str): return ret else: ret = '' if op.debug: raise Exception, 'Nothing found during greppipe data collection' return None def matchpipe(fileobj, string, tmout = 0.001): "Match available data from pipe in a non-blocking fashion" ret = '' regexp = re.compile(string) while not select.select([fileobj.fileno()], [], [], tmout)[0]: pass while select.select([fileobj.fileno()], [], [], tmout)[0]: character = fileobj.read(1) if character != '\n': ret = ret + character elif regexp.match(ret): return ret else: ret = '' if op.debug: raise Exception, 'Nothing found during matchpipe data collection' return None def cmd_test(cmd): pipes = os.popen3(cmd, 't', 0) for line in pipes[2].readlines(): raise Exception, line.strip() def cmd_readlines(cmd): pipes = os.popen3(cmd, 't', 0) for line in pipes[1].readlines(): yield line def cmd_splitlines(cmd, sep=None): pipes = os.popen3(cmd, 't', 0) for line in pipes[1].readlines(): yield line.split(sep) def proc_readlines(filename): "Return the lines of a file, one by one" # for line in open(filename).readlines(): # yield line ### Implemented linecache (for top-plugins) i = 1 while True: line = linecache.getline(filename, i); if not line: break yield line i += 1 def proc_splitlines(filename, sep=None): "Return the splitted lines of a file, one by one" # for line in open(filename).readlines(): # yield line.split(sep) ### Implemented linecache (for top-plugins) i = 1 while True: line = linecache.getline(filename, i); if not line: break yield line.split(sep) i += 1 def proc_readline(filename): "Return the first line of a file" # return open(filename).read() return linecache.getline(filename, 1) def proc_splitline(filename, sep=None): "Return the first line of a file splitted" # return open(filename).read().split(sep) return linecache.getline(filename, 1).split(sep) ### FIXME: Should we cache this within every step ? def proc_pidlist(): "Return a list of process IDs" dstat_pid = str(os.getpid()) for pid in os.listdir('/proc/'): try: ### Is it a pid ? int(pid) ### Filter out dstat if pid == dstat_pid: continue yield pid except ValueError: continue def dchg(var, width, base): "Convert decimal to string given base and length" c = 0 while True: ret = str(long(round(var))) if len(ret) <= width: break var = var / base c = c + 1 else: c = -1 return ret, c def fchg(var, width, base): "Convert float to string given scale and length" c = 0 while True: if var == 0: ret = str('0') break # ret = repr(round(var)) # ret = repr(long(round(var, maxlen))) ret = str(long(round(var, width))) if len(ret) <= width: i = width - len(ret) - 1 while i > 0: ret = ('%%.%df' % i) % var if len(ret) <= width and ret != str(long(round(var, width))): break i = i - 1 else: ret = str(long(round(var))) break var = var / base c = c + 1 else: c = -1 return ret, c def tchg(var, width): "Convert time string to given length" ret = '%2dh%02d' % (var / 60, var % 60) if len(ret) > width: ret = '%2dh' % (var / 60) if len(ret) > width: ret = '%2dd' % (var / 60 / 24) if len(ret) > width: ret = '%2dw' % (var / 60 / 24 / 7) return ret def cprintlist(varlist, type, width, scale): "Return all columns color printed" ret = sep = '' for var in varlist: ret = ret + sep + cprint(var, type, width, scale) sep = char['space'] return ret def cprint(var, type = 'f', width = 4, scale = 1000): "Color print one column" base = 1000 if scale == 1024: base = 1024 ### Use units when base is exact 1000 or 1024 unit = False if scale in (1000, 1024) and width >= len(str(base)): unit = True width = width - 1 ### If this is a negative value, return a dash if var < 0: if unit: return theme['error'] + '-'.rjust(width, char['space']) + char['space'] + theme['default'] else: return theme['error'] + '-'.rjust(width, char['space']) + theme['default'] if base != 1024: units = (char['space'], 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') elif op.bits and type in ('b', ): units = ('b', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') base = scale = 1000 var = var * 8.0 else: units = ('B', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') if step == op.delay: colors = theme['colors_lo'] ctext = theme['text_lo'] cunit = theme['unit_lo'] else: colors = theme['colors_hi'] ctext = theme['text_hi'] cunit = theme['unit_hi'] ### Convert value to string given base and field-length if op.integer and type in ('b', 'd', 'p', 'f'): ret, c = dchg(var, width, base) elif op.float and type in ('b', 'd', 'p', 'f'): ret, c = fchg(var, width, base) elif type in ('b', 'd', 'p'): ret, c = dchg(var, width, base) elif type in ('f'): ret, c = fchg(var, width, base) elif type in ('s'): ret, c = str(var), ctext elif type in ('t'): ret, c = tchg(var, width), ctext else: raise Exception, 'Type %s not known to dstat.' % type ### Set the counter color if ret == '0': color = cunit elif scale <= 0: color = ctext elif scale not in (1000, 1024): color = colors[int(var/scale)%len(colors)] elif type in ('p'): color = colors[int(round(var)/scale)%len(colors)] elif type in ('b', 'd', 'f'): color = colors[c%len(colors)] else: color = ctext ### Justify value to left if string if type in ('s',): ret = color + ret.ljust(width, char['space']) else: ret = color + ret.rjust(width, char['space']) ### Add unit to output if unit: if c != -1 and round(var) != 0: ret += cunit + units[c] else: ret += char['space'] return ret def header(totlist, vislist): "Return the header for a set of module counters" line = '' ### Process title for o in vislist: line += o.title() if o is not vislist[-1]: line += theme['frame'] + char['space'] elif totlist != vislist: line += theme['title'] + char['gt'] line += '\n' ### Process subtitle for o in vislist: line += o.subtitle() if o is not vislist[-1]: line += theme['frame'] + char['pipe'] elif totlist != vislist: line += theme['title'] + char['gt'] return line + '\n' def csvheader(totlist): "Return the CVS header for a set of module counters" line = '' ### Process title for o in totlist: line = line + o.csvtitle() if o is not totlist[-1]: line = line + ',' line += '\n' ### Process subtitle for o in totlist: line = line + o.csvsubtitle() if o is not totlist[-1]: line = line + ',' return line + '\n' def info(level, str): "Output info message" # if level <= op.verbose: print >>sys.stderr, str def die(ret, str): "Print error and exit with errorcode" print >>sys.stderr, str exit(ret) def initterm(): "Initialise terminal" global termsize ### 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] and not termsize[1]: 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(os.environ['LINES']), int(os.environ['COLUMNS'])) except: termsize = 25, 80 return termsize def gettermcolor(color=True): "Return whether the system can use colors or not" if color and sys.stdout.isatty(): try: import curses curses.setupterm() if curses.tigetnum('colors') < 0: return False except: print >>sys.stderr, 'Color support is disabled, python-curses is not installed.' return False return color ### We only want to filter out paths, not ksoftirqd/1 def basename(name): "Perform basename on paths only" if name[0] in ('/', '.'): return os.path.basename(name) return name def getnamebypid(pid, name): "Return the name of a process by taking best guesses and exclusion" ret = None try: # cmdline = open('/proc/%s/cmdline' % pid).read().split('\0') cmdline = linecache.getline('/proc/%s/cmdline' % pid, 1).split('\0') ret = basename(cmdline[0]) if ret in ('bash', 'csh', 'ksh', 'perl', 'python', 'ruby', 'sh'): ret = basename(cmdline[1]) if ret.startswith('-'): ret = basename(cmdline[-2]) if ret.startswith('-'): raise if not ret: raise except: ret = basename(name) return ret 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 Exception, "Problem finding number of CPUs in system." return cpunr def blockdevices(): ### We have to replace '!' by '/' to support cciss!c0d0 type devices :-/ return [os.path.basename(filename).replace('!', '/') for filename in glob.glob('/sys/block/*')] ### FIXME: Add scsi support too and improve def sysfs_dev(device): "Convert sysfs device names into device names" m = re.match('ide/host(\d)/bus(\d)/target(\d)/lun(\d)/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('cciss/(c\dd\d)', device) if m: l = m.groups() return l[0] 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: # devlist = entry.split() # if dev == devlist[0]: # return devlist[1] #def readfile(file): # ret = '' # for line in open(file,'r').readlines(): # ret = ret + line # return ret #cdef extern from "sched.h": # struct sched_param: # int sched_priority # int sched_setscheduler(int pid, int policy,sched_param *p) # #SCHED_FIFO = 1 # #def switchRTCPriority(nb): # cdef sched_param sp # sp.sched_priority = nb # sched_setscheduler (0,SCHED_FIFO , &sp); def listplugins(): plugins = [] remod = re.compile('dstat_(.+)$') for filename in globals(): if filename.startswith('dstat_'): plugins.append(remod.match(filename).groups()[0]) remod = re.compile('.+/dstat_(.+).py$') for path in pluginpath: for filename in glob.glob(path + '/dstat_*.py'): plugins.append(remod.match(filename).groups()[0].replace('_', '-')) plugins.sort() return plugins def showplugins(): rows, cols = gettermsize() print 'internal:\n\t', remod = re.compile('dstat_(.+)$') plugins = [] for filename in globals(): if filename.startswith('dstat_'): plugins.append(remod.match(filename).groups()[0].replace('_', '-')) plugins.sort() cols2 = cols - 8 for mod in plugins: cols2 = cols2 - len(mod) - 2 if cols2 <= 0: print '\n\t', cols2 = cols - len(mod) - 10 if mod != plugins[-1]: print mod+',', print mod remod = re.compile('.+/dstat_(.+).py$') for path in pluginpath: plugins = [] for filename in glob.glob(path + '/dstat_*.py'): plugins.append(remod.match(filename).groups()[0].replace('_', '-')) if not plugins: continue plugins.sort() cols2 = cols - 8 print '%s:\n\t' % os.path.abspath(path), for mod in plugins: cols2 = cols2 - len(mod) - 2 if cols2 <= 0: print '\n\t', cols2 = cols - len(mod) - 10 if mod != plugins[-1]: print mod+',', print mod def exit(ret): sys.stdout.write(ansi['reset']) sys.stdout.flush() if op.pidfile and os.path.exists(op.pidfile): os.remove(op.pidfile) if op.profile and os.path.exists(op.profile): rows, cols = gettermsize() import pstats p = pstats.Stats(op.profile) # p.sort_stats('name') # p.print_stats() p.sort_stats('cumulative').print_stats(rows - 13) # p.sort_stats('time').print_stats(rows - 13) # p.sort_stats('file').print_stats('__init__') # p.sort_stats('time', 'cum').print_stats(.5, 'init') # p.print_callees() elif op.profile: print >>sys.stderr, "No profiling data was found, maybe profiler was interrupted ?" sys.exit(ret) def main(): "Initialization of the program, terminal, internal structures" global cpunr, hz, maxint, ownpid, pagesize global ansi, theme, outputfile global totlist, inittime global update, missed cpunr = getcpunr() hz = os.sysconf('SC_CLK_TCK') maxint = (sys.maxint + 1) * 2 ownpid = str(os.getpid()) pagesize = resource.getpagesize() interval = 1 user = getpass.getuser() hostname = os.uname()[1] ### Disable line-wrapping (does not work ?) sys.stdout.write('\033[7l') ### Write term-title if sys.stdout.isatty(): shell = os.getenv('XTERM_SHELL') term = os.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]), ' '.join(op.args))) ### Check background color (rxvt) ### COLORFGBG="15;default;0" # if os.environ['COLORFGBG'] and len(os.environ['COLORFGBG'].split(';')) >= 3: # l = os.environ['COLORFGBG'].split(';') # bg = int(l[2]) # if bg < 7: # print 'Background is dark' # else: # print 'Background is light' # else: # print 'Background is unknown, assuming dark.' ### Check terminal capabilities op.color = gettermcolor(op.color) ### Prepare CSV output file 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 ",,,,"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()))) ### Create pidfile if op.pidfile: try: pidfile = open(op.pidfile, 'w', 0) pidfile.write(str(os.getpid())) pidfile.close() except Exception, e: print >>sys.stderr, 'Failed to create pidfile %s' % op.pidfile, e op.pidfile = False ### Empty ansi and theme database if no colors are requested if not op.color: op.update = False for key in ansi.keys(): ansi[key] = '' for key in theme.keys(): theme[key] = '' theme['colors_hi'] = (ansi['default'],) theme['colors_lo'] = (ansi['default'],) # print ansi['blackbg'] if not op.update: interval = op.delay ### Build list of requested plugins linewidth = 0 totlist = [] for plugin in op.plugins: ### Set up fallback lists if plugin == 'cpu': mods = ( 'cpu', 'cpu24' ) elif plugin == 'disk': mods = ( 'disk', 'disk24', 'disk24old' ) elif plugin == 'int': mods = ( 'int', 'int24' ) elif plugin == 'page': mods = ( 'page', 'page24' ) elif plugin == 'swap': mods = ( 'swap', 'swapold' ) else: mods = ( plugin, ) for mod in mods: pluginfile = 'dstat_' + mod.replace('-', '_') try: if pluginfile not in globals().keys(): import imp fp, pathname, description = imp.find_module(pluginfile, pluginpath) fp.close() ### TODO: Would using .pyc help with anything ? ### Try loading python plugin if description[0] in ('.py', ): execfile(pathname) exec 'o = dstat_plugin(); del(dstat_plugin)' o.filename = pluginfile o.check() o.prepare() ### Try loading C plugin (not functional yet) elif description[0] == '.so': exec 'import %s' % pluginfile exec 'o = %s.new()' % pluginfile o.check() o.prepare() # print dir(o) # print o.__module__ # print o.name else: print >>sys.stderr, 'Module %s is of unknown type.' % pluginfile else: exec 'o = %s()' % pluginfile o.check() o.prepare() # print o.__module__ except Exception, e: if mod == mods[-1]: print >>sys.stderr, 'Module %s failed to load. (%s)' % (pluginfile, e) elif op.debug: print >>sys.stderr, 'Module %s failed to load, trying another. (%s)' % (pluginfile, e) if op.debug >= 3: raise # tb = sys.exc_info()[2] continue except: print >>sys.stderr, 'Module %s caused unknown exception' % pluginfile linewidth = linewidth + o.statwidth() + 1 totlist.append(o) if op.debug: print 'Module', pluginfile, if hasattr(o, 'file'): print 'requires', o.file, print break if not totlist: die(8, 'None of the stats you selected are available.') if op.output: outputfile.write(csvheader(totlist)) scheduler = sched.scheduler(time.time, time.sleep) inittime = time.time() update = 0 missed = 0 ### Let the games begin while update <= op.delay * op.count or op.count == -1: scheduler.enterabs(inittime + update, 1, perform, (update,)) # scheduler.enter(1, 1, perform, (update,)) scheduler.run() sys.stdout.flush() update = update + interval linecache.clearcache() if op.update: sys.stdout.write('\n') def perform(update): "Inner loop that calculates counters and constructs output" global totlist, oldvislist, vislist, showheader, rows, cols global elapsed, totaltime, starttime global loop, step, missed starttime = time.time() loop = (update - 1 + op.delay) / op.delay step = ((update - 1) % op.delay) + 1 ### Get current time (may be different from schedule) for debugging if not op.debug: curwidth = 0 else: if step == 1 or loop == 0: totaltime = 0 curwidth = 8 ### FIXME: This is temporary functionality, we should do this better ### If it takes longer than 500ms, than warn ! if loop != 0 and starttime - inittime - update > 1: missed = missed + 1 return 0 ### Initialise certain variables if loop == 0: elapsed = ticks() rows, cols = 0, 0 vislist = [] oldvislist = [] showheader = True else: elapsed = step ### FIXME: Make this part smarter if sys.stdout.isatty(): oldcols = cols rows, cols = gettermsize() ### Trim object list to what is visible on screen if oldcols != cols: 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 ### Prepare the colors for intermediate updates, last step in a loop is definitive if step == op.delay: theme['default'] = ansi['reset'] else: theme['default'] = theme['text_lo'] ### The first step is to show the definitive line if necessary newline = '' if op.update: if step == 1 and update != 0: newline = '\n' + ansi['reset'] + ansi['clearline'] + ansi['save'] elif loop != 0: newline = ansi['restore'] ### Display header if showheader: if loop == 0 and totlist != vislist: print >>sys.stderr, 'Terminal width too small, trimming output.' showheader = False sys.stdout.write(newline) newline = header(totlist, vislist) ### Calculate all objects (visible, invisible) line = newline 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 + theme['input']) if op.output and step == op.delay: outputfile.write(oline + '\n') ### Print debugging output if op.debug: totaltime = totaltime + (time.time() - starttime) * 1000.0 if loop == 0: totaltime = totaltime * step if op.debug == 1: sys.stdout.write('%s%6.2fms%s' % (theme['roundtrip'], totaltime / step, theme['input'])) elif op.debug == 2: sys.stdout.write('%s%6.2f %s%d:%d%s' % (theme['roundtrip'], totaltime / step, theme['debug'], loop, step, theme['input'])) elif op.debug > 2: sys.stdout.write('%s%6.2f %s%d:%d:%d%s' % (theme['roundtrip'], totaltime / step, theme['debug'], loop, step, update, theme['input'])) if missed > 0: # sys.stdout.write(' '+theme['error']+'= warn =') sys.stdout.write(' ' + theme['error'] + 'missed ' + str(missed+1) + ' ticks' + theme['input']) missed = 0 ### Finish the line if not op.update: sys.stdout.write('\n') ### Main entrance if __name__ == '__main__': try: initterm() op = Options(sys.argv[1:]) theme = set_theme() if op.profile: import profile if os.path.exists(op.profile): os.remove(op.profile) profile.run('main()', op.profile) else: main() except KeyboardInterrupt, e: if op.update: sys.stdout.write('\n') exit(0) else: op = Options('') step = 1 # vim:ts=4:sw=4:et