|
1 #!/usr/bin/env python |
|
2 |
|
3 # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. |
|
4 # Use of this source code is governed by a BSD-style license that can be |
|
5 # found in the LICENSE file. |
|
6 |
|
7 """ |
|
8 A clone of iotop (http://guichaz.free.fr/iotop/) showing real time |
|
9 disk I/O statistics. |
|
10 |
|
11 It works on Linux only (FreeBSD and OSX are missing support for IO |
|
12 counters). |
|
13 It doesn't work on Windows as curses module is required. |
|
14 |
|
15 Author: Giampaolo Rodola' <g.rodola@gmail.com> |
|
16 """ |
|
17 |
|
18 import os |
|
19 import sys |
|
20 import psutil |
|
21 if not hasattr(psutil.Process, 'get_io_counters') or os.name != 'posix': |
|
22 sys.exit('platform not supported') |
|
23 import time |
|
24 import curses |
|
25 import atexit |
|
26 |
|
27 |
|
28 # --- curses stuff |
|
29 def tear_down(): |
|
30 win.keypad(0) |
|
31 curses.nocbreak() |
|
32 curses.echo() |
|
33 curses.endwin() |
|
34 |
|
35 win = curses.initscr() |
|
36 atexit.register(tear_down) |
|
37 curses.endwin() |
|
38 lineno = 0 |
|
39 |
|
40 def print_line(line, highlight=False): |
|
41 """A thin wrapper around curses's addstr().""" |
|
42 global lineno |
|
43 try: |
|
44 if highlight: |
|
45 line += " " * (win.getmaxyx()[1] - len(line)) |
|
46 win.addstr(lineno, 0, line, curses.A_REVERSE) |
|
47 else: |
|
48 win.addstr(lineno, 0, line, 0) |
|
49 except curses.error: |
|
50 lineno = 0 |
|
51 win.refresh() |
|
52 raise |
|
53 else: |
|
54 lineno += 1 |
|
55 # --- /curses stuff |
|
56 |
|
57 |
|
58 def bytes2human(n): |
|
59 """ |
|
60 >>> bytes2human(10000) |
|
61 '9.8 K/s' |
|
62 >>> bytes2human(100001221) |
|
63 '95.4 M/s' |
|
64 """ |
|
65 symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') |
|
66 prefix = {} |
|
67 for i, s in enumerate(symbols): |
|
68 prefix[s] = 1 << (i+1)*10 |
|
69 for s in reversed(symbols): |
|
70 if n >= prefix[s]: |
|
71 value = float(n) / prefix[s] |
|
72 return '%.2f %s/s' % (value, s) |
|
73 return '%.2f B/s' % (n) |
|
74 |
|
75 def poll(interval): |
|
76 """Calculate IO usage by comparing IO statics before and |
|
77 after the interval. |
|
78 Return a tuple including all currently running processes |
|
79 sorted by IO activity and total disks I/O activity. |
|
80 """ |
|
81 # first get a list of all processes and disk io counters |
|
82 procs = [p for p in psutil.process_iter()] |
|
83 for p in procs[:]: |
|
84 try: |
|
85 p._before = p.get_io_counters() |
|
86 except psutil.Error: |
|
87 procs.remove(p) |
|
88 continue |
|
89 disks_before = psutil.disk_io_counters() |
|
90 |
|
91 # sleep some time |
|
92 time.sleep(interval) |
|
93 |
|
94 # then retrieve the same info again |
|
95 for p in procs[:]: |
|
96 try: |
|
97 p._after = p.get_io_counters() |
|
98 p._cmdline = ' '.join(p.cmdline) |
|
99 if not p._cmdline: |
|
100 p._cmdline = p.name |
|
101 p._username = p.username |
|
102 except psutil.NoSuchProcess: |
|
103 procs.remove(p) |
|
104 disks_after = psutil.disk_io_counters() |
|
105 |
|
106 # finally calculate results by comparing data before and |
|
107 # after the interval |
|
108 for p in procs: |
|
109 p._read_per_sec = p._after.read_bytes - p._before.read_bytes |
|
110 p._write_per_sec = p._after.write_bytes - p._before.write_bytes |
|
111 p._total = p._read_per_sec + p._write_per_sec |
|
112 |
|
113 disks_read_per_sec = disks_after.read_bytes - disks_before.read_bytes |
|
114 disks_write_per_sec = disks_after.write_bytes - disks_before.write_bytes |
|
115 |
|
116 # sort processes by total disk IO so that the more intensive |
|
117 # ones get listed first |
|
118 processes = sorted(procs, key=lambda p: p._total, reverse=True) |
|
119 |
|
120 return (processes, disks_read_per_sec, disks_write_per_sec) |
|
121 |
|
122 |
|
123 def refresh_window(procs, disks_read, disks_write): |
|
124 """Print results on screen by using curses.""" |
|
125 curses.endwin() |
|
126 templ = "%-5s %-7s %11s %11s %s" |
|
127 win.erase() |
|
128 |
|
129 disks_tot = "Total DISK READ: %s | Total DISK WRITE: %s" \ |
|
130 % (bytes2human(disks_read), bytes2human(disks_write)) |
|
131 print_line(disks_tot) |
|
132 |
|
133 header = templ % ("PID", "USER", "DISK READ", "DISK WRITE", "COMMAND") |
|
134 print_line(header, highlight=True) |
|
135 |
|
136 for p in procs: |
|
137 line = templ % (p.pid, |
|
138 p._username[:7], |
|
139 bytes2human(p._read_per_sec), |
|
140 bytes2human(p._write_per_sec), |
|
141 p._cmdline) |
|
142 try: |
|
143 print_line(line) |
|
144 except curses.error: |
|
145 break |
|
146 win.refresh() |
|
147 |
|
148 def main(): |
|
149 try: |
|
150 interval = 0 |
|
151 while 1: |
|
152 args = poll(interval) |
|
153 refresh_window(*args) |
|
154 interval = 1 |
|
155 except (KeyboardInterrupt, SystemExit): |
|
156 pass |
|
157 |
|
158 if __name__ == '__main__': |
|
159 main() |