Clarify -p help text, and cosmetically add a terminating '.'.
[iotop.git] / iotop / ui.py
blob74f6291b1d67b7346afcc3e546aa6df8a1e34730
1 import curses
2 import errno
3 import locale
4 import optparse
5 import os
6 import pwd
7 import select
8 import struct
9 import sys
11 from iotop.data import find_uids, TaskStatsNetlink, ProcessList
12 from iotop.version import VERSION
15 # Utility functions for the UI
18 UNITS = ['B', 'K', 'M', 'G', 'T', 'P', 'E']
20 def human_bandwidth(size, duration):
21 bw = size and float(size) / duration
22 for i in xrange(len(UNITS) - 1, 0, -1):
23 base = 1 << (10 * i)
24 if 2 * base < size:
25 res = '%.2f %s' % ((float(bw) / base), UNITS[i])
26 break
27 else:
28 res = str(bw) + ' ' + UNITS[0]
29 return res + '/s'
31 def human_stats(stats, duration):
32 # Keep in sync with TaskStatsNetlink.members_offsets and
33 # IOTopUI.get_data(self)
34 def delay2percent(delay): # delay in ns, duration in s
35 return '%.2f %%' % min(99.99, delay / (duration * 10000000.0))
36 io_delay = delay2percent(stats.blkio_delay_total)
37 swapin_delay = delay2percent(stats.swapin_delay_total)
38 read_bytes = human_bandwidth(stats.read_bytes, duration)
39 written_bytes = stats.write_bytes - stats.cancelled_write_bytes
40 written_bytes = max(0, written_bytes)
41 write_bytes = human_bandwidth(written_bytes, duration)
42 return io_delay, swapin_delay, read_bytes, write_bytes
45 # The UI
48 class IOTopUI(object):
49 # key, reverse
50 sorting_keys = [
51 (lambda p: p.pid, False),
52 (lambda p: p.user, False),
53 (lambda p: p.stats_delta.read_bytes, True),
54 (lambda p: p.stats_delta.write_bytes -
55 p.stats_delta.cancelled_write_bytes, True),
56 (lambda p: p.stats_delta.swapin_delay_total, True),
57 # The default sorting (by I/O % time) should show processes doing
58 # only writes, without waiting on them
59 (lambda p: p.stats_delta.blkio_delay_total or
60 int(not(not(p.stats_delta.read_bytes or
61 p.stats_delta.write_bytes))), True),
62 (lambda p: p.get_cmdline(), False),
65 def __init__(self, win, process_list, options):
66 self.process_list = process_list
67 self.options = options
68 self.sorting_key = 5
69 self.sorting_reverse = IOTopUI.sorting_keys[5][1]
70 if not self.options.batch:
71 self.win = win
72 self.resize()
73 try:
74 curses.use_default_colors()
75 curses.start_color()
76 curses.curs_set(0)
77 except curses.error:
78 # This call can fail with misconfigured terminals, for example
79 # TERM=xterm-color. This is harmless
80 pass
82 def resize(self):
83 self.height, self.width = self.win.getmaxyx()
85 def run(self):
86 iterations = 0
87 poll = select.poll()
88 if not self.options.batch:
89 poll.register(sys.stdin.fileno(), select.POLLIN|select.POLLPRI)
90 while self.options.iterations is None or \
91 iterations < self.options.iterations:
92 total = self.process_list.refresh_processes()
93 total_read, total_write = total
94 self.refresh_display(total_read, total_write,
95 self.process_list.duration)
96 if self.options.iterations is not None:
97 iterations += 1
98 if iterations >= self.options.iterations:
99 break
101 try:
102 events = poll.poll(self.options.delay_seconds * 1000.0)
103 except select.error, e:
104 if e.args and e.args[0] == errno.EINTR:
105 events = 0
106 else:
107 raise
108 if not self.options.batch:
109 self.resize()
110 if events:
111 key = self.win.getch()
112 self.handle_key(key)
114 def reverse_sorting(self):
115 self.sorting_reverse = not self.sorting_reverse
117 def adjust_sorting_key(self, delta):
118 orig_sorting_key = self.sorting_key
119 self.sorting_key += delta
120 self.sorting_key = max(0, self.sorting_key)
121 self.sorting_key = min(len(IOTopUI.sorting_keys) - 1, self.sorting_key)
122 if orig_sorting_key != self.sorting_key:
123 self.sorting_reverse = IOTopUI.sorting_keys[self.sorting_key][1]
125 def handle_key(self, key):
126 def toggle_only_io():
127 self.options.only ^= True
128 key_bindings = {
129 ord('q'):
130 lambda: sys.exit(0),
131 ord('Q'):
132 lambda: sys.exit(0),
133 ord('r'):
134 lambda: self.reverse_sorting(),
135 ord('R'):
136 lambda: self.reverse_sorting(),
137 ord('o'):
138 toggle_only_io,
139 curses.KEY_LEFT:
140 lambda: self.adjust_sorting_key(-1),
141 curses.KEY_RIGHT:
142 lambda: self.adjust_sorting_key(1),
143 curses.KEY_HOME:
144 lambda: self.adjust_sorting_key(-len(IOTopUI.sorting_keys)),
145 curses.KEY_END:
146 lambda: self.adjust_sorting_key(len(IOTopUI.sorting_keys))
149 action = key_bindings.get(key, lambda: None)
150 action()
152 def get_data(self):
153 def format(p):
154 stats = human_stats(p.stats_delta, self.process_list.duration)
155 io_delay, swapin_delay, read_bytes, write_bytes = stats
156 line = '%5d %-8s %11s %11s %7s %7s ' % (p.pid, p.user[:8],
157 read_bytes, write_bytes, swapin_delay, io_delay)
158 line += p.get_cmdline()
159 if not self.options.batch:
160 line = line[:self.width - 1]
161 return line
163 def should_format(p):
164 return not self.options.only or p.did_some_io()
166 processes = self.process_list.processes.values()
167 processes = filter(should_format, processes)
168 key = IOTopUI.sorting_keys[self.sorting_key][0]
169 processes.sort(key=key, reverse=self.sorting_reverse)
170 if not self.options.batch:
171 del processes[self.height - 2:]
172 return map(format, processes)
174 def refresh_display(self, total_read, total_write, duration):
175 summary = 'Total DISK READ: %s | Total DISK WRITE: %s' % (
176 human_bandwidth(total_read, duration),
177 human_bandwidth(total_write, duration))
178 titles = [' PID', ' USER', ' DISK READ', ' DISK WRITE',
179 ' SWAPIN', ' IO', ' COMMAND']
180 lines = self.get_data()
181 if self.options.batch:
182 print summary
183 print ''.join(titles)
184 for l in lines:
185 print l
186 else:
187 self.win.erase()
188 self.win.addstr(summary)
189 self.win.hline(1, 0, ord(' ') | curses.A_REVERSE, self.width)
190 for i in xrange(len(titles)):
191 attr = curses.A_REVERSE
192 title = titles[i]
193 if i == self.sorting_key:
194 attr |= curses.A_BOLD
195 title += self.sorting_reverse and '>' or '<'
196 self.win.addstr(title, attr)
197 for i in xrange(len(lines)):
198 self.win.addstr(i + 2, 0, lines[i].encode('utf-8'))
199 self.win.refresh()
201 def run_iotop(win, options):
202 taskstats_connection = TaskStatsNetlink(options)
203 process_list = ProcessList(taskstats_connection, options)
204 ui = IOTopUI(win, process_list, options)
205 ui.run()
208 # Main program
211 USAGE = '''%s [OPTIONS]
213 DISK READ and DISK WRITE are the block I/O bandwidth used during the sampling
214 period. SWAPIN and IO are the percentages of time the thread spent respectively
215 while swapping in and waiting on I/O more generally.
216 Controls: left and right arrows to change the sorting column, r to invert the
217 sorting order, o to toggle the --only option, q to quit, any other key to force
218 a refresh.''' % sys.argv[0]
220 def main():
221 locale.setlocale(locale.LC_ALL, '')
222 parser = optparse.OptionParser(usage=USAGE, version='iotop ' + VERSION)
223 parser.add_option('-o', '--only', action='store_true',
224 dest='only', default=False,
225 help='only show processes or threads actually doing I/O')
226 parser.add_option('-b', '--batch', action='store_true', dest='batch',
227 help='non-interactive mode')
228 parser.add_option('-n', '--iter', type='int', dest='iterations',
229 metavar='NUM',
230 help='number of iterations before ending [infinite]')
231 parser.add_option('-d', '--delay', type='float', dest='delay_seconds',
232 help='delay between iterations [1 second]',
233 metavar='SEC', default=1)
234 parser.add_option('-p', '--pid', type='int', dest='pids', action='append',
235 help='processes/threads to monitor [all]', metavar='PID')
236 parser.add_option('-u', '--user', type='str', dest='users', action='append',
237 help='users to monitor [all]', metavar='USER')
238 parser.add_option('-P', '--processes', action='store_true',
239 dest='processes',
240 help='only show processes, not all threads')
241 options, args = parser.parse_args()
242 if args:
243 parser.error('Unexpected arguments: ' + ' '.join(args))
244 find_uids(options)
245 options.pids = options.pids or []
246 if options.batch:
247 run_iotop(None, options)
248 else:
249 curses.wrapper(run_iotop, options)