blob: 25395870c06d1095e2b9c905097a6bba2beb2a52 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A clone of top / htop.
Author: Giampaolo Rodola' <g.rodola@gmail.com>
$ python3 scripts/top.py
CPU0 [|||| ] 10.9%
CPU1 [||||| ] 13.1%
CPU2 [||||| ] 12.8%
CPU3 [|||| ] 11.5%
Mem [||||||||||||||||||||||||||||| ] 73.0% 11017M / 15936M
Swap [ ] 1.3% 276M / 20467M
Processes: 347 (sleeping=273, running=1, idle=73)
Load average: 1.10 1.28 1.34 Uptime: 8 days, 21:15:40
PID USER NI VIRT RES CPU% MEM% TIME+ NAME
5368 giampaol 0 7.2G 4.3G 41.8 27.7 56:34.18 VirtualBox
24976 giampaol 0 2.1G 487.2M 18.7 3.1 22:05.16 Web Content
22731 giampaol 0 3.2G 596.2M 11.6 3.7 35:04.90 firefox
1202 root 0 807.4M 288.5M 10.6 1.8 12:22.12 Xorg
22811 giampaol 0 2.8G 741.8M 9.0 4.7 2:26.61 Web Content
2590 giampaol 0 2.3G 579.4M 5.5 3.6 28:02.70 compiz
22990 giampaol 0 3.0G 1.2G 4.2 7.6 4:30.32 Web Content
18412 giampaol 0 90.1M 14.5M 3.5 0.1 0:00.26 python3
26971 netdata 0 20.8M 3.9M 2.9 0.0 3:17.14 apps.plugin
2421 giampaol 0 3.3G 36.9M 2.3 0.2 57:14.21 pulseaudio
...
"""
import datetime
import sys
import time
try:
import curses
except ImportError:
sys.exit('platform not supported')
import psutil
from psutil._common import bytes2human
win = curses.initscr()
lineno = 0
colors_map = dict(green=3, red=10, yellow=4)
def printl(line, color=None, bold=False, highlight=False):
"""A thin wrapper around curses's addstr()."""
global lineno
try:
flags = 0
if color:
flags |= curses.color_pair(colors_map[color])
if bold:
flags |= curses.A_BOLD
if highlight:
line += " " * (win.getmaxyx()[1] - len(line))
flags |= curses.A_STANDOUT
win.addstr(lineno, 0, line, flags)
except curses.error:
lineno = 0
win.refresh()
raise
else:
lineno += 1
# --- /curses stuff
def poll(interval):
# sleep some time
time.sleep(interval)
procs = []
procs_status = {}
for p in psutil.process_iter():
try:
p.dict = p.as_dict([
'username',
'nice',
'memory_info',
'memory_percent',
'cpu_percent',
'cpu_times',
'name',
'status',
])
try:
procs_status[p.dict['status']] += 1
except KeyError:
procs_status[p.dict['status']] = 1
except psutil.NoSuchProcess:
pass
else:
procs.append(p)
# return processes sorted by CPU percent usage
processes = sorted(
procs, key=lambda p: p.dict['cpu_percent'], reverse=True
)
return (processes, procs_status)
def get_color(perc):
if perc <= 30:
return "green"
elif perc <= 80:
return "yellow"
else:
return "red"
def print_header(procs_status, num_procs):
"""Print system-related info, above the process list."""
def get_dashes(perc):
dashes = "|" * int(float(perc) / 10 * 4)
empty_dashes = " " * (40 - len(dashes))
return dashes, empty_dashes
# cpu usage
percs = psutil.cpu_percent(interval=0, percpu=True)
for cpu_num, perc in enumerate(percs):
dashes, empty_dashes = get_dashes(perc)
line = " CPU{:<2} [{}{}] {:>5}%".format(
cpu_num, dashes, empty_dashes, perc
)
printl(line, color=get_color(perc))
# memory usage
mem = psutil.virtual_memory()
dashes, empty_dashes = get_dashes(mem.percent)
line = " Mem [{}{}] {:>5}% {:>6} / {}".format(
dashes,
empty_dashes,
mem.percent,
bytes2human(mem.used),
bytes2human(mem.total),
)
printl(line, color=get_color(mem.percent))
# swap usage
swap = psutil.swap_memory()
dashes, empty_dashes = get_dashes(swap.percent)
line = " Swap [{}{}] {:>5}% {:>6} / {}".format(
dashes,
empty_dashes,
swap.percent,
bytes2human(swap.used),
bytes2human(swap.total),
)
printl(line, color=get_color(swap.percent))
# processes number and status
st = []
for x, y in procs_status.items():
if y:
st.append(f"{x}={y}")
st.sort(key=lambda x: x[:3] in {'run', 'sle'}, reverse=1)
printl(f" Processes: {num_procs} ({', '.join(st)})")
# load average, uptime
uptime = datetime.datetime.now() - datetime.datetime.fromtimestamp(
psutil.boot_time()
)
av1, av2, av3 = psutil.getloadavg()
line = " Load average: {:.2f} {:.2f} {:.2f} Uptime: {}".format(
av1,
av2,
av3,
str(uptime).split('.')[0],
)
printl(line)
def refresh_window(procs, procs_status):
"""Print results on screen by using curses."""
curses.endwin()
templ = "{:<6} {:<8} {:>4} {:>6} {:>6} {:>5} {:>5} {:>9} {:>2}"
win.erase()
header = templ.format(
"PID",
"USER",
"NI",
"VIRT",
"RES",
"CPU%",
"MEM%",
"TIME+",
"NAME",
)
print_header(procs_status, len(procs))
printl("")
printl(header, bold=True, highlight=True)
for p in procs:
# TIME+ column shows process CPU cumulative time and it
# is expressed as: "mm:ss.ms"
if p.dict['cpu_times'] is not None:
ctime = datetime.timedelta(seconds=sum(p.dict['cpu_times']))
ctime = "{}:{}.{}".format(
ctime.seconds // 60 % 60,
str(ctime.seconds % 60).zfill(2),
str(ctime.microseconds)[:2],
)
else:
ctime = ''
if p.dict['memory_percent'] is not None:
p.dict['memory_percent'] = round(p.dict['memory_percent'], 1)
else:
p.dict['memory_percent'] = ''
if p.dict['cpu_percent'] is None:
p.dict['cpu_percent'] = ''
username = p.dict['username'][:8] if p.dict['username'] else ''
line = templ.format(
p.pid,
username,
p.dict['nice'],
bytes2human(getattr(p.dict['memory_info'], 'vms', 0)),
bytes2human(getattr(p.dict['memory_info'], 'rss', 0)),
p.dict['cpu_percent'],
p.dict['memory_percent'],
ctime,
p.dict['name'] or '',
)
try:
printl(line)
except curses.error:
break
win.refresh()
def setup():
curses.start_color()
curses.use_default_colors()
for i in range(curses.COLORS):
curses.init_pair(i + 1, i, -1)
curses.endwin()
win.nodelay(1)
def tear_down():
win.keypad(0)
curses.nocbreak()
curses.echo()
curses.endwin()
def main():
setup()
try:
interval = 0
while True:
if win.getch() == ord('q'):
break
args = poll(interval)
refresh_window(*args)
interval = 1
except (KeyboardInterrupt, SystemExit):
pass
finally:
tear_down()
if __name__ == '__main__':
main()