python/psutil/examples/iotop.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/python/psutil/examples/iotop.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,159 @@
     1.4 +#!/usr/bin/env python
     1.5 +
     1.6 +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
     1.7 +# Use of this source code is governed by a BSD-style license that can be
     1.8 +# found in the LICENSE file.
     1.9 +
    1.10 +"""
    1.11 +A clone of iotop (http://guichaz.free.fr/iotop/) showing real time
    1.12 +disk I/O statistics.
    1.13 +
    1.14 +It works on Linux only (FreeBSD and OSX are missing support for IO
    1.15 +counters).
    1.16 +It doesn't work on Windows as curses module is required.
    1.17 +
    1.18 +Author: Giampaolo Rodola' <g.rodola@gmail.com>
    1.19 +"""
    1.20 +
    1.21 +import os
    1.22 +import sys
    1.23 +import psutil
    1.24 +if not hasattr(psutil.Process, 'get_io_counters') or os.name != 'posix':
    1.25 +    sys.exit('platform not supported')
    1.26 +import time
    1.27 +import curses
    1.28 +import atexit
    1.29 +
    1.30 +
    1.31 +# --- curses stuff
    1.32 +def tear_down():
    1.33 +    win.keypad(0)
    1.34 +    curses.nocbreak()
    1.35 +    curses.echo()
    1.36 +    curses.endwin()
    1.37 +
    1.38 +win = curses.initscr()
    1.39 +atexit.register(tear_down)
    1.40 +curses.endwin()
    1.41 +lineno = 0
    1.42 +
    1.43 +def print_line(line, highlight=False):
    1.44 +    """A thin wrapper around curses's addstr()."""
    1.45 +    global lineno
    1.46 +    try:
    1.47 +        if highlight:
    1.48 +            line += " " * (win.getmaxyx()[1] - len(line))
    1.49 +            win.addstr(lineno, 0, line, curses.A_REVERSE)
    1.50 +        else:
    1.51 +            win.addstr(lineno, 0, line, 0)
    1.52 +    except curses.error:
    1.53 +        lineno = 0
    1.54 +        win.refresh()
    1.55 +        raise
    1.56 +    else:
    1.57 +        lineno += 1
    1.58 +# --- /curses stuff
    1.59 +
    1.60 +
    1.61 +def bytes2human(n):
    1.62 +    """
    1.63 +    >>> bytes2human(10000)
    1.64 +    '9.8 K/s'
    1.65 +    >>> bytes2human(100001221)
    1.66 +    '95.4 M/s'
    1.67 +    """
    1.68 +    symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
    1.69 +    prefix = {}
    1.70 +    for i, s in enumerate(symbols):
    1.71 +        prefix[s] = 1 << (i+1)*10
    1.72 +    for s in reversed(symbols):
    1.73 +        if n >= prefix[s]:
    1.74 +            value = float(n) / prefix[s]
    1.75 +            return '%.2f %s/s' % (value, s)
    1.76 +    return '%.2f B/s' % (n)
    1.77 +
    1.78 +def poll(interval):
    1.79 +    """Calculate IO usage by comparing IO statics before and
    1.80 +    after the interval.
    1.81 +    Return a tuple including all currently running processes
    1.82 +    sorted by IO activity and total disks I/O activity.
    1.83 +    """
    1.84 +    # first get a list of all processes and disk io counters
    1.85 +    procs = [p for p in psutil.process_iter()]
    1.86 +    for p in procs[:]:
    1.87 +        try:
    1.88 +            p._before = p.get_io_counters()
    1.89 +        except psutil.Error:
    1.90 +            procs.remove(p)
    1.91 +            continue
    1.92 +    disks_before = psutil.disk_io_counters()
    1.93 +
    1.94 +    # sleep some time
    1.95 +    time.sleep(interval)
    1.96 +
    1.97 +    # then retrieve the same info again
    1.98 +    for p in procs[:]:
    1.99 +        try:
   1.100 +            p._after = p.get_io_counters()
   1.101 +            p._cmdline = ' '.join(p.cmdline)
   1.102 +            if not p._cmdline:
   1.103 +                p._cmdline = p.name
   1.104 +            p._username = p.username
   1.105 +        except psutil.NoSuchProcess:
   1.106 +            procs.remove(p)
   1.107 +    disks_after = psutil.disk_io_counters()
   1.108 +
   1.109 +    # finally calculate results by comparing data before and
   1.110 +    # after the interval
   1.111 +    for p in procs:
   1.112 +        p._read_per_sec = p._after.read_bytes - p._before.read_bytes
   1.113 +        p._write_per_sec = p._after.write_bytes - p._before.write_bytes
   1.114 +        p._total = p._read_per_sec + p._write_per_sec
   1.115 +
   1.116 +    disks_read_per_sec = disks_after.read_bytes - disks_before.read_bytes
   1.117 +    disks_write_per_sec = disks_after.write_bytes - disks_before.write_bytes
   1.118 +
   1.119 +    # sort processes by total disk IO so that the more intensive
   1.120 +    # ones get listed first
   1.121 +    processes = sorted(procs, key=lambda p: p._total, reverse=True)
   1.122 +
   1.123 +    return (processes, disks_read_per_sec, disks_write_per_sec)
   1.124 +
   1.125 +
   1.126 +def refresh_window(procs, disks_read, disks_write):
   1.127 +    """Print results on screen by using curses."""
   1.128 +    curses.endwin()
   1.129 +    templ = "%-5s %-7s %11s %11s  %s"
   1.130 +    win.erase()
   1.131 +
   1.132 +    disks_tot = "Total DISK READ: %s | Total DISK WRITE: %s" \
   1.133 +                % (bytes2human(disks_read), bytes2human(disks_write))
   1.134 +    print_line(disks_tot)
   1.135 +
   1.136 +    header = templ % ("PID", "USER", "DISK READ", "DISK WRITE", "COMMAND")
   1.137 +    print_line(header, highlight=True)
   1.138 +
   1.139 +    for p in procs:
   1.140 +        line = templ % (p.pid,
   1.141 +                        p._username[:7],
   1.142 +                        bytes2human(p._read_per_sec),
   1.143 +                        bytes2human(p._write_per_sec),
   1.144 +                        p._cmdline)
   1.145 +        try:
   1.146 +            print_line(line)
   1.147 +        except curses.error:
   1.148 +            break
   1.149 +    win.refresh()
   1.150 +
   1.151 +def main():
   1.152 +    try:
   1.153 +        interval = 0
   1.154 +        while 1:
   1.155 +            args = poll(interval)
   1.156 +            refresh_window(*args)
   1.157 +            interval = 1
   1.158 +    except (KeyboardInterrupt, SystemExit):
   1.159 +        pass
   1.160 +
   1.161 +if __name__ == '__main__':
   1.162 +    main()

mercurial