| #!/usr/bin/env python |
| |
| # 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 iotop (http://guichaz.free.fr/iotop/) showing real time |
| disk I/O statistics. |
| |
| It works on Linux only (FreeBSD and OSX are missing support for IO |
| counters). |
| It doesn't work on Windows as curses module is required. |
| |
| Example output: |
| |
| $ python examples/iotop.py |
| Total DISK READ: 0.00 B/s | Total DISK WRITE: 472.00 K/s |
| PID USER DISK READ DISK WRITE COMMAND |
| 13155 giampao 0.00 B/s 428.00 K/s /usr/bin/google-chrome-beta |
| 3260 giampao 0.00 B/s 0.00 B/s bash |
| 3779 giampao 0.00 B/s 0.00 B/s gnome-session --session=ubuntu |
| 3830 giampao 0.00 B/s 0.00 B/s /usr/bin/dbus-launch |
| 3831 giampao 0.00 B/s 0.00 B/s //bin/dbus-daemon --fork --print-pid 5 |
| 3841 giampao 0.00 B/s 0.00 B/s /usr/lib/at-spi-bus-launcher |
| 3845 giampao 0.00 B/s 0.00 B/s /bin/dbus-daemon |
| 3848 giampao 0.00 B/s 0.00 B/s /usr/lib/at-spi2-core/at-spi2-registryd |
| 3862 giampao 0.00 B/s 0.00 B/s /usr/lib/gnome-settings-daemon |
| |
| Author: Giampaolo Rodola' <g.rodola@gmail.com> |
| """ |
| |
| import atexit |
| import time |
| import sys |
| try: |
| import curses |
| except ImportError: |
| sys.exit('platform not supported') |
| |
| import psutil |
| |
| |
| # --- curses stuff |
| def tear_down(): |
| win.keypad(0) |
| curses.nocbreak() |
| curses.echo() |
| curses.endwin() |
| |
| win = curses.initscr() |
| atexit.register(tear_down) |
| curses.endwin() |
| lineno = 0 |
| |
| |
| def print_line(line, highlight=False): |
| """A thin wrapper around curses's addstr().""" |
| global lineno |
| try: |
| if highlight: |
| line += " " * (win.getmaxyx()[1] - len(line)) |
| win.addstr(lineno, 0, line, curses.A_REVERSE) |
| else: |
| win.addstr(lineno, 0, line, 0) |
| except curses.error: |
| lineno = 0 |
| win.refresh() |
| raise |
| else: |
| lineno += 1 |
| # --- /curses stuff |
| |
| |
| def bytes2human(n): |
| """ |
| >>> bytes2human(10000) |
| '9.8 K/s' |
| >>> bytes2human(100001221) |
| '95.4 M/s' |
| """ |
| symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') |
| prefix = {} |
| for i, s in enumerate(symbols): |
| prefix[s] = 1 << (i + 1) * 10 |
| for s in reversed(symbols): |
| if n >= prefix[s]: |
| value = float(n) / prefix[s] |
| return '%.2f %s/s' % (value, s) |
| return '%.2f B/s' % (n) |
| |
| |
| def poll(interval): |
| """Calculate IO usage by comparing IO statics before and |
| after the interval. |
| Return a tuple including all currently running processes |
| sorted by IO activity and total disks I/O activity. |
| """ |
| # first get a list of all processes and disk io counters |
| procs = [p for p in psutil.process_iter()] |
| for p in procs[:]: |
| try: |
| p._before = p.io_counters() |
| except psutil.Error: |
| procs.remove(p) |
| continue |
| disks_before = psutil.disk_io_counters() |
| |
| # sleep some time |
| time.sleep(interval) |
| |
| # then retrieve the same info again |
| for p in procs[:]: |
| try: |
| p._after = p.io_counters() |
| p._cmdline = ' '.join(p.cmdline()) |
| if not p._cmdline: |
| p._cmdline = p.name() |
| p._username = p.username() |
| except (psutil.NoSuchProcess, psutil.ZombieProcess): |
| procs.remove(p) |
| disks_after = psutil.disk_io_counters() |
| |
| # finally calculate results by comparing data before and |
| # after the interval |
| for p in procs: |
| p._read_per_sec = p._after.read_bytes - p._before.read_bytes |
| p._write_per_sec = p._after.write_bytes - p._before.write_bytes |
| p._total = p._read_per_sec + p._write_per_sec |
| |
| disks_read_per_sec = disks_after.read_bytes - disks_before.read_bytes |
| disks_write_per_sec = disks_after.write_bytes - disks_before.write_bytes |
| |
| # sort processes by total disk IO so that the more intensive |
| # ones get listed first |
| processes = sorted(procs, key=lambda p: p._total, reverse=True) |
| |
| return (processes, disks_read_per_sec, disks_write_per_sec) |
| |
| |
| def refresh_window(procs, disks_read, disks_write): |
| """Print results on screen by using curses.""" |
| curses.endwin() |
| templ = "%-5s %-7s %11s %11s %s" |
| win.erase() |
| |
| disks_tot = "Total DISK READ: %s | Total DISK WRITE: %s" \ |
| % (bytes2human(disks_read), bytes2human(disks_write)) |
| print_line(disks_tot) |
| |
| header = templ % ("PID", "USER", "DISK READ", "DISK WRITE", "COMMAND") |
| print_line(header, highlight=True) |
| |
| for p in procs: |
| line = templ % ( |
| p.pid, |
| p._username[:7], |
| bytes2human(p._read_per_sec), |
| bytes2human(p._write_per_sec), |
| p._cmdline) |
| try: |
| print_line(line) |
| except curses.error: |
| break |
| win.refresh() |
| |
| |
| def main(): |
| try: |
| interval = 0 |
| while True: |
| args = poll(interval) |
| refresh_window(*args) |
| interval = 1 |
| except (KeyboardInterrupt, SystemExit): |
| pass |
| |
| if __name__ == '__main__': |
| main() |