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