#!/usr/bin/env python # -*- coding: utf-8 -*- # top.py -- top command implemented use python. # get infomation from /proc system. # the main file for system info is: # /proc/stat /proc/<pid>/status /proc/<pid>/stam etc. from __future__ import print_function import sys import re import os from optparse import OptionParser from terminal import * import traceback import atexit import fcntl import time import signal try: import curses, _curses except ImportError: print("Curse is not available on this system. Exiting.", file=sys.stderr) sys.exit(0) NUMREGX = re.compile(r'^(-{0,1}|\+{0,1})[0-9]+(\.{0,1}[0-9]+)$') CONFIGURATION = { 'pause_refresh': False, 'refresh_interval': 3.0, 'help_mode': False, } def read_uptime(): try: f_uptime = open("/proc/uptime", "r") line = f_uptime.readline() return line.split(None) finally: f_uptime.close() def get_system_hz(): """Return system hz use SC_CLK_TCK.""" ticks = os.sysconf(os.sysconf_names['SC_CLK_TCK']) if ticks == -1: return 100 else: return ticks def parse_cmdline(argv): """Parses the command-line.""" # get arguments parser = OptionParser(description='Top command script for display system ' 'infomation.') parser.add_option('-d', '--delay', dest='delay', metavar='int', default=3, help='Delay time interval: -d ss.tt(second.tenths).') parser.add_option('-n', '--number', dest='number', metavar='int', default=0, help='Number of interations limit ad: -n number') parser.add_option('-u', '--user', dest='user', metavar="str", default="all", help='Monitor only processes with an ' 'effective UID or user name.') parser.add_option('-v', dest='verbose', action='store_true', default=False, help='Display version information.') (options, args) = parser.parse_args(args=argv[1:]) return (options, args) def getpagesize(): """ get the system pagesize from /proc/self/smaps """ SMAPS = "/proc/self/smaps" size = 4 unit = "kB" pattern = re.compile(r'KernelPageSize|MMUPageSize') if os.path.exists(SMAPS): try: f_smaps = open(SMAPS, "r") for line in f_smaps: m = pattern.match(line) if m: array = re.split("\s+", line) size = array[1] unit = array[2] break finally: f_smaps.close() if re.match("kB", unit): size = int(size) * 1024 elif re.match("mB", unit): size = int(size) * 1024 * 1024 elif re.match("gB", unit): size = int(size) * 1024 * 1024 * 1024 return size CPU_T = ( "u", "s", "n", "i", "w" ) def get_cpu_info(): """get cpus infomation from /proc/stat """ cpus = list() cpustat = "/proc/stat" try: f_stat = open(cpustat, "r") except IOError, e: return cpus f_stat.seek(0) for line in f_stat: if re.match("cpu", line): values = line.split(None) cpu = dict(zip(CPU_T, values[1:])) cpus.append(cpu) f_stat.close() return cpus PROC_T = ( "pid", "cmd", "state", "ppid", "pgrp", "session", "tty", "tpgid", "flags", "min_flt", "cmin_flt", "maj_flt", "cmaj_flt", "utime", "stime", "cutime", "cstime", "priority", "nice", "timeout", "it_real_value", "start_time", "vsize", "rss", "rss_rlim", "start_code", "end_code", "start_stack", "kstk_esp", "kstk_eip", "signal", "blocked", "sigignore", "sigcatch", "wchan", "nswap", "cnswap", "exit_signal", "processor", "rtprio", "sched", "ruid", "euid", "suid", "fuid", "rgid", "egid", "sgid", "fgid", "vm_size", "vm_lock", "vm_rss", "vm_data", "vm_stack", "vm_exe", "vm_lib" ) def get_process_stat(proc_id): """ get process stat from /proc/#/stat. """ import pwd import grp proc_t = {} if not str(proc_id).isdigit() or\ not os.path.exists("/proc/" + proc_id): return proc_t STAT = "/proc/" + proc_id + "/stat" try: f_stat = open(STAT, "r") stats = f_stat.readline().split(None) proc_t = dict(zip(PROC_T, stats[0:])) proc_t["cmd"] = re.sub('[()]', '', proc_t["cmd"]) if int(proc_t["tty"]) == 0: proc_t["tty"] = -1 if int(proc_t["priority"]) < 0: proc_t["priority"] = "RT" proc_t["pcpu"] = int(proc_t["utime"]) + int(proc_t["stime"]) proc_t["tics"] = int(proc_t["utime"]) + int(proc_t["stime"]) except IOError, e: return proc_t finally: f_stat.close() STATUS = "/proc/" + proc_id + "/status" try: f_status = open(STATUS, "r") for line in f_status: if re.match("uid:", line, re.I): (proc_t["ruid"],proc_t["euid"],proc_t["suid"],proc_t["fuid"])\ = line.split(None)[1:] elif re.match("Gid:", line, re.I): (proc_t["rgid"],proc_t["egid"],proc_t["sgid"],proc_t["fgid"])\ = line.split(None)[1:] elif re.match("VmSize:", line, re.I): (proc_t["vm_size"]) = line.split(None)[1] elif re.match("VmLck", line, re.I): (proc_t["vm_lock"]) = line.split(None)[1] elif re.match("VmRss", line, re.I): (proc_t["vm_rss"]) = line.split(None)[1] elif re.match("VmData:", line, re.I): (proc_t["vm_data"]) = line.split(None)[1] elif re.match("VmStk:", line, re.I): (proc_t["vm_stack"]) = line.split(None)[1] elif re.match("VmExe:", line, re.I): (proc_t["vm_exe"]) = line.split(None)[1] elif re.match("VmLib:", line, re.I): (proc_t["vm_lib"]) = line.split(None)[1] except IOError, e: return proc_t finally: f_status.close() proc_t["euser"] = pwd.getpwuid(int(proc_t["euid"]))[0] proc_t["ruser"] = pwd.getpwuid(int(proc_t["ruid"]))[0] proc_t["suser"] = pwd.getpwuid(int(proc_t["suid"]))[0] proc_t["egroup"] = grp.getgrgid(int(proc_t["egid"]))[0] proc_t["rgroup"] = grp.getgrgid(int(proc_t["rgid"]))[0] proc_t["fgroup"] = grp.getgrgid(int(proc_t["fgid"]))[0] if proc_t["state"] == "Z": proc_t["cmd"] += " <defunct>" STATM = "/proc/" + proc_id + "/statm" try: f_statm = open(STATM, "r") (proc_t["size"],proc_t["resident"],proc_t["share"],\ proc_t["trs"],proc_t["lrs"],proc_t["drs"],proc_t["dt"])\ = f_statm.readline().split(None) except IOError, e: return proc_t finally: f_statm.close() CMDLINE = "/proc/" + proc_id + "/cmdline" try: f_cmdline = open(CMDLINE, "r") proc_t["cmdline"] = f_cmdline.readline() except IOError, e: return proc_t finally: f_cmdline.close() ENVIRON = "/proc/" + proc_id + "/environ" try: f_environ = open(ENVIRON, "r") proc_t["environ"] = f_environ.readline() except IOError, e: return proc_t return proc_t def fmttime(seconds): """ format seconds to string like: '12days, 01:12' """ result="" if not NUMREGX.match(str(seconds)): return result day = float(seconds) / ((24 * 60 * 60)) hour = float(seconds) / (60 * 60) % 24 minuter = float(seconds) / 60 % 60 if day > 1: result += "%d days, " % day else: result += "%d day, " % day if hour > 0: result += "%02d:%02d" % (hour, minuter) else: result += "%02d min" % min return result def fmtshare(share, pagesize): """ format share size """ if ( not NUMREGX.match(str(seconds)) )\ or ( not NUMREGX.match(str(pagesize)) ): return "?" share = int(share) * (int(pagesize) >> 10) result = "" if (int(share) <= 9999): result = "%d" % share elif (int(share) <= 2 << 20): result = "%dm" % (int(share) >> 10) elif (int(share) <= 2 << 30): result = "%dg" % (int(share) >> 20) else: result = "?" return result def get_all_process(): """ get all process id from /proc. """ PROC = "/proc" process = list() try: process = os.listdir(PROC) process = [elem for elem in process if str(elem).isdigit() ] except: return list() return process def get_sys_loads(): """ get sys loads from /proc/loadavg. """ LOADAVG = "/proc/loadavg" loadavg = list() try: f_loadavg = open(LOADAVG, "r") except IOError, e: return loadavg f_loadavg.seek(0) loadavg = f_loadavg.readline().split(None)[0:3] f_loadavg.close() return loadavg def get_memswap_info(): """ get memory and swap infomation from /proc/meminfo. """ MEMINFO = "/proc/meminfo" meminfo = list() pattern = re.compile(r'MemTotal|MemFree|Buffers|Cached|SwapTotal|SwapFree') try: f_meminfo = open(MEMINFO, "r") except IOError, e: return meminfo f_meminfo.seek(0) for line in f_meminfo: m = pattern.match(line) if m: meminfo.append(line.split(None)[1]) f_meminfo.close() return meminfo def scale_num(num, width, pagesize): """ format number to fit width. """ num = int(num) * (int(pagesize) >> 10) result = "?" if (int(num) <= (10 ** int(width) - 1)): result = "%d" % num elif (int(num) <= 1024 * 1024): result = "%dm" % (int(num) / 1024) elif (int(num) <= 1024 * 1024 * 1024): result = "%dg" % (int(num) / (1024 * 1024)) return result def fmt_mem_percent(mem, memtotal, pagesize): """ format memory num to percent. """ result = "0" if NUMREGX.match(str(mem)) and NUMREGX.match(str(memtotal)): result = "%.1f" % (int(mem)*int(pagesize)/1024*100/int(memtotal)) return result def set_fd_nonblocking(fd): """ set fd nonblocking. """ if not fd: return -1 flags = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) def get_all_user(): """ get user of system """ import pwd all_user = {} for user in pwd.getpwall(): all_user[user[0]] = all_user[user[2]] = user return all_user def usage(): usage = """ Help for Interactive Commands - top.py version 1.0.0.0 Window 1:Def: Cumulative mode Off. System: Delay 3.0 secs; Secure mode Off. Z,B Global: 'Z' change color mappings; 'B' disable/enable bold l,t,m Toggle Summaries: 'l' load avg; 't' task/cpu stats; 'm' mem info 1,I Toggle SMP view: '1' single/separate states; 'I' Irix/Solaris mode f,o . Fields/Columns: 'f' add or remove; 'o' change display order F or O . Select sort field <,> . Move sort field: '<' next col left; '>' next col right R,H . Toggle: 'R' normal/reverse sort; 'H' show threads c,i,S . Toggle: 'c' cmd name/line; 'i' idle tasks; 'S' cumulative time x,y . Toggle highlights: 'x' sort field; 'y' running tasks z,b . Toggle: 'z' color/mono; 'b' bold/reverse (only if 'x' or 'y') u . Show specific user only n or # . Set maximum tasks displayed k,r Manipulate tasks: 'k' kill; 'r' renice d or s Set update interval W Write configuration file q Quit ( commands shown with '.' require a visible task display window ) Press 'h' or '?' for help with Windows, any other key to continue """ return usage def header(processes, memory): """ return string of top header. """ now = time.strftime("%H:%M:%S", time.localtime()) uptime = read_uptime()[0] #uptime = fmttime(read_uptime()[0]) (sysload1, sysload5, sysload15) = get_sys_loads() total = len(processes) running = 0; sleeping = 0; stoped = 0; zombie = 0; cpus = get_cpu_info() us = float(cpus[0].get("u")) sy = float(cpus[0].get("s")) ni = float(cpus[0].get("n")) idle = float(cpus[0].get("i")) wa = float(cpus[0].get("w")) summary = us + sy + ni + idle + wa scale = 100.0 / summary (memtotal, memfree, buf, cache, swaptotal, swapfree) = memory[0:] memused = int(memtotal) - int(memfree) swapused = int(swaptotal) - int(swapfree) head = "%5s - %8s up %5s, %2d users, load average: %3s, %3s, %3s\n" \ % ("top.py", now, fmttime(uptime), 3, sysload1, sysload5, sysload15) head = head + "Tasks: %3d total, %2d running, %3d sleeping, %3d stopped, %2d zombie\n" \ % (total, running, sleeping, stoped, zombie) head = head + "Cpu(s): %2.1f%%us, %2.1f%%sy, %2.1f%%ni, %2.1f%%id, %2.1f%%wa, 0.0%%hi, 0.0%%si, 0.0%%st\n" \ % (us*scale, sy*scale, ni*scale, idle*scale, wa*scale) head = head + "Mem: %8sk total, %8sk used, %8sk free, %8sk buffers\n" \ % (memtotal, memused, memfree, buf) head = head + "Swap: %8sk total, %8sk used, %8sk free, %8sk cached\n" \ % (swaptotal, swapused, swapfree, cache) return head def on_keyboard(scr, c): '''Handle keyborad shortcuts''' if c == ord('q'): raise KeyboardInterrupt() elif c == ord('p'): CONFIGURATION['pause_refresh'] = not CONFIGURATION['pause_refresh'] elif c == ord('h') or c == ord('?'): CONFIGURATION['pause_refresh'] = not CONFIGURATION['pause_refresh'] display(scr, usage(), False) return 1 return 1 def on_mouse(): '''Update selected line / sort''' _, x, y, z, bstate = curses.getmouse() # Left button click ? if bstate & curses.BUTTON1_CLICKED: # Is it title line ? if y == 0: # Determine sort column based on offset / col width x_max = 0 return 2 # Is it a cgroup line ? return 1 def on_resize(): '''Redraw screen, do not refresh''' return 2 def event_listener(scr, timeout): ''' Wait for curses events on screen ``scr`` at mot ``timeout`` ms return - 1 OK - 2 redraw - 0 error ''' try: scr.timeout(timeout) c = scr.getch() if c == -1: return 1 elif c == curses.KEY_MOUSE: return on_mouse() elif c == curses.KEY_RESIZE: return on_resize() else: return on_keyboard(scr, c) except _curses.error: return 0 def do_finish(): """ Stop top.py and reinit curses.""" curses.nocbreak() stdscr.keypad(0) curses.echo() curses.endwin() #The end... sys.exit(0) def signal_handler(): """Callback for CTRL-C.""" do_finish() class Timer(object): """The timer class. A simple chronometer.""" def __init__(self, duration): self.duration = duration self.start() def start(self): self.target = time() + self.duration def reset(self): self.start() def set(self, duration): self.duration = duration def finished(self): return time() > self.target def init_screen(): curses.start_color() # load colors curses.use_default_colors() curses.noecho() # do not echo text curses.cbreak() # do not wait for "enter" curses.mousemask(curses.ALL_MOUSE_EVENTS) # Hide cursor, if terminal AND curse supports it if hasattr(curses, 'curs_set'): try: curses.curs_set(0) except: pass def display(scr, info, head=False): # Get display informations height, width = scr.getmaxyx() list_height = height - 5 # title + status lines # Display statistics scr.clear() scr.addstr(0, 0, info) #scr.addstr(5, 0, "\n") if head: title = " PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND" scr.addstr(6, 0, ' '*width, curses.color_pair(2)) scr.addstr(6, 0, title, curses.color_pair(2)) scr.refresh() def main(argv): """ The main top entry point and loop.""" options, args = parse_cmdline(argv) CONFIGURATION['refresh_interval'] = float(options.delay) try: screen = curses.initscr() init_screen() atexit.register(curses.endwin) screen.keypad(1) # parse keypad control sequences # Curses colors curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_GREEN) # header curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_CYAN) # focused header / line curses.init_pair(3, curses.COLOR_WHITE, -1) # regular curses.init_pair(4, curses.COLOR_CYAN, -1) # tree #height,width = screen.getmaxyx() #signal.signal(signal.SIGINT, signal_handler) #screen.addstr(height - 1, 0, "position string", curses.A_BLINK) while True: #screen.timeout(0) processes = get_all_process() memory = get_memswap_info() display(screen, header(processes, memory), True) sleep_start = time.time() #while CONFIGURATION['pause_refresh'] or time.time() < sleep_start + CONFIGURATION['refresh_interval']: while CONFIGURATION['pause_refresh'] or time.time() < sleep_start + CONFIGURATION['refresh_interval']: if CONFIGURATION['pause_refresh']: to_sleep = -1 else: to_sleep = int((sleep_start + CONFIGURATION['refresh_interval'] - time.time())*1000) ret = event_listener(screen, to_sleep) if ret == 2: display(screen, header(processes, memory), True) except KeyboardInterrupt: pass finally: do_finish() if __name__ == '__main__': sys.exit(main(sys.argv))