Detect the kernel.task_delayacct sysctl value
[iotop.git] / iotop / ui.py
blob7ae8badbb75d7415814549c9345e357ce7736f41
1 # This program is free software; you can redistribute it and/or modify
2 # it under the terms of the GNU General Public License as published by
3 # the Free Software Foundation; either version 2 of the License, or
4 # (at your option) any later version.
6 # This program is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # GNU Library General Public License for more details.
11 # You should have received a copy of the GNU General Public License
12 # along with this program; if not, write to the Free Software
13 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
15 # See the COPYING file for license information.
17 # Copyright (c) 2007 Guillaume Chazarain <guichaz@gmail.com>
19 # Allow printing with same syntax in Python 2/3
20 from __future__ import print_function
22 import curses
23 import errno
24 import locale
25 import math
26 import optparse
27 import os
28 import select
29 import signal
30 import sys
31 import time
33 # Try to ensure time.monotonic() is available.
34 # This normally requires Python 3.3 or later.
35 # Use PyPI monotonic if needed and available.
36 # Fall back on non-monotonic time if needed.
37 try:
38 if not hasattr(time, 'monotonic'):
39 from monotonic import monotonic
40 time.monotonic = monotonic
41 except (ImportError, RuntimeError):
42 time.monotonic = time.time
44 from collections import OrderedDict
46 from iotop.data import find_uids, TaskStatsNetlink, ProcessList, Stats, sysctl_task_delayacct
47 from iotop.data import ThreadInfo
48 from iotop.version import VERSION
49 from iotop import ioprio
50 from iotop.ioprio import IoprioSetError
53 # Utility functions for the UI
56 UNITS = ['B', 'K', 'M', 'G', 'T', 'P', 'E']
59 def human_size(size):
60 if size > 0:
61 sign = ''
62 elif size < 0:
63 sign = '-'
64 size = -size
65 else:
66 return '0.00 B'
68 expo = int(math.log(size / 2, 2) / 10)
69 return '%s%.2f %s' % (
70 sign, (float(size) / (1 << (10 * expo))), UNITS[expo])
73 def format_size(options, bytes):
74 if options.kilobytes:
75 return '%.2f K' % (bytes / 1024.0)
76 return human_size(bytes)
79 def format_bandwidth(options, size, duration):
80 return format_size(options, size and float(size) / duration) + '/s'
83 def format_stats(options, process, duration):
84 # Keep in sync with TaskStatsNetlink.members_offsets and
85 # IOTopUI.get_data(self)
86 def delay2percent(delay): # delay in ns, duration in s
87 return '%.2f %%' % min(99.99, delay / (duration * 10000000.0))
88 if options.accumulated:
89 stats = process.stats_accum
90 display_format = lambda size, duration: format_size(options, size)
91 duration = time.monotonic() - process.stats_accum_timestamp
92 else:
93 stats = process.stats_delta
94 display_format = lambda size, duration: format_bandwidth(
95 options, size, duration)
96 io_delay = delay2percent(stats.blkio_delay_total)
97 swapin_delay = delay2percent(stats.swapin_delay_total)
98 read_bytes = display_format(stats.read_bytes, duration)
99 written_bytes = stats.write_bytes - stats.cancelled_write_bytes
100 written_bytes = max(0, written_bytes)
101 write_bytes = display_format(written_bytes, duration)
102 return io_delay, swapin_delay, read_bytes, write_bytes
105 def get_max_pid_width():
106 try:
107 return len(open('/proc/sys/kernel/pid_max').read().strip())
108 except Exception as e:
109 print(e)
110 # Reasonable default in case something fails
111 return 5
113 MAX_PID_WIDTH = get_max_pid_width()
116 # UI Exceptions
120 class CancelInput(Exception):
121 pass
124 class InvalidInt(Exception):
125 pass
128 class InvalidPid(Exception):
129 pass
132 class InvalidTid(Exception):
133 pass
136 class InvalidIoprioData(Exception):
137 pass
140 # The UI
144 class IOTopUI(object):
145 # key, reverse
146 sorting_keys = [
147 (lambda p, s: p.pid, False),
148 (lambda p, s: p.ioprio_sort_key(), False),
149 (lambda p, s: p.get_user(), False),
150 (lambda p, s: s.read_bytes, True),
151 (lambda p, s: s.write_bytes - s.cancelled_write_bytes, True),
152 (lambda p, s: s.swapin_delay_total, True),
153 # The default sorting (by I/O % time) should show processes doing
154 # only writes, without waiting on them
155 (lambda p, s: s.blkio_delay_total or
156 int(not(not(s.read_bytes or s.write_bytes))), True),
157 (lambda p, s: p.get_cmdline(), False),
160 def __init__(self, win, process_list, options):
161 self.process_list = process_list
162 self.options = options
163 self.sorting_key = 6
164 self.sorting_reverse = IOTopUI.sorting_keys[self.sorting_key][1]
165 if not self.options.batch:
166 self.win = win
167 self.resize()
168 try:
169 curses.use_default_colors()
170 curses.start_color()
171 curses.curs_set(0)
172 except curses.error:
173 # This call can fail with misconfigured terminals, for example
174 # TERM=xterm-color. This is harmless
175 pass
177 def resize(self):
178 self.height, self.width = self.win.getmaxyx()
180 def run(self):
181 iterations = 0
182 poll = select.poll()
183 if not self.options.batch:
184 poll.register(sys.stdin.fileno(), select.POLLIN | select.POLLPRI)
185 while self.options.iterations is None or \
186 iterations < self.options.iterations:
187 total, current = self.process_list.refresh_processes()
188 self.refresh_display(iterations == 0, total, current,
189 self.process_list.duration)
190 if self.options.iterations is not None:
191 iterations += 1
192 if iterations >= self.options.iterations:
193 break
194 elif iterations == 0:
195 iterations = 1
197 try:
198 events = poll.poll(self.options.delay_seconds * 1000.0)
199 except select.error as e:
200 if e.args and e.args[0] == errno.EINTR:
201 events = []
202 else:
203 raise
204 for (fd, event) in events:
205 if event & (select.POLLERR | select.POLLHUP):
206 sys.exit(1)
207 if not self.options.batch:
208 self.resize()
209 if events:
210 key = self.win.getch()
211 self.handle_key(key)
213 def reverse_sorting(self):
214 self.sorting_reverse = not self.sorting_reverse
216 def adjust_sorting_key(self, delta):
217 orig_sorting_key = self.sorting_key
218 self.sorting_key = self.get_sorting_key(delta)
219 if orig_sorting_key != self.sorting_key:
220 self.sorting_reverse = IOTopUI.sorting_keys[self.sorting_key][1]
222 def get_sorting_key(self, delta):
223 new_sorting_key = self.sorting_key
224 new_sorting_key += delta
225 new_sorting_key = max(0, new_sorting_key)
226 new_sorting_key = min(len(IOTopUI.sorting_keys) - 1, new_sorting_key)
227 return new_sorting_key
229 # I wonder if switching to urwid for the display would be better here
231 def prompt_str(self, prompt, default=None, empty_is_cancel=True):
232 self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width)
233 self.win.addstr(1, 0, prompt, curses.A_BOLD)
234 self.win.refresh()
235 curses.echo()
236 curses.curs_set(1)
237 inp = self.win.getstr(1, len(prompt))
238 curses.curs_set(0)
239 curses.noecho()
240 if inp not in (None, ''):
241 return inp
242 if empty_is_cancel:
243 raise CancelInput()
244 return default
246 def prompt_int(self, prompt, default=None, empty_is_cancel=True):
247 inp = self.prompt_str(prompt, default, empty_is_cancel)
248 try:
249 return int(inp)
250 except ValueError:
251 raise InvalidInt()
253 def prompt_pid(self):
254 try:
255 return self.prompt_int('PID to ionice: ')
256 except InvalidInt:
257 raise InvalidPid()
258 except CancelInput:
259 raise
261 def prompt_tid(self):
262 try:
263 return self.prompt_int('TID to ionice: ')
264 except InvalidInt:
265 raise InvalidTid()
266 except CancelInput:
267 raise
269 def prompt_data(self, ioprio_data):
270 try:
271 if ioprio_data is not None:
272 inp = self.prompt_int('I/O priority data (0-7, currently %s): '
273 % ioprio_data, ioprio_data, False)
274 else:
275 inp = self.prompt_int('I/O priority data (0-7): ', None, False)
276 except InvalidInt:
277 raise InvalidIoprioData()
278 if inp < 0 or inp > 7:
279 raise InvalidIoprioData()
280 return inp
282 def prompt_set(self, prompt, display_list, ret_list, selected):
283 try:
284 selected = ret_list.index(selected)
285 except ValueError:
286 selected = -1
287 set_len = len(display_list) - 1
288 while True:
289 self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width)
290 self.win.insstr(1, 0, prompt, curses.A_BOLD)
291 offset = len(prompt)
292 for i, item in enumerate(display_list):
293 display = ' %s ' % item
294 if i is selected:
295 attr = curses.A_REVERSE
296 else:
297 attr = curses.A_NORMAL
298 self.win.insstr(1, offset, display, attr)
299 offset += len(display)
300 while True:
301 key = self.win.getch()
302 if key in (curses.KEY_LEFT, ord('l')) and selected > 0:
303 selected -= 1
304 break
305 elif (key in (curses.KEY_RIGHT, ord('r')) and
306 selected < set_len):
307 selected += 1
308 break
309 elif key in (curses.KEY_ENTER, ord('\n'), ord('\r')):
310 return ret_list[selected]
311 elif key in (27, curses.KEY_CANCEL, curses.KEY_CLOSE,
312 curses.KEY_EXIT, ord('q'), ord('Q')):
313 raise CancelInput()
315 def prompt_class(self, ioprio_class=None):
316 prompt = 'I/O priority class: '
317 classes_prompt = ['Real-time', 'Best-effort', 'Idle']
318 classes_ret = ['rt', 'be', 'idle']
319 if ioprio_class is None:
320 ioprio_class = 2
321 inp = self.prompt_set(prompt, classes_prompt,
322 classes_ret, ioprio_class)
323 return inp
325 def prompt_error(self, error='Error!'):
326 self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width)
327 self.win.insstr(1, 0, ' %s ' % error, curses.A_REVERSE)
328 self.win.refresh()
329 time.sleep(1)
331 def prompt_clear(self):
332 self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width)
333 self.win.refresh()
335 def handle_key(self, key):
336 def toggle_accumulated():
337 self.options.accumulated ^= True
339 def toggle_only_io():
340 self.options.only ^= True
342 def toggle_processes():
343 self.options.processes ^= True
344 self.process_list.clear()
345 self.process_list.refresh_processes()
347 def ionice():
348 try:
349 if self.options.processes:
350 pid = self.prompt_pid()
351 exec_unit = self.process_list.get_process(pid)
352 else:
353 tid = self.prompt_tid()
354 exec_unit = ThreadInfo(tid,
355 self.process_list.taskstats_connection)
356 ioprio_value = exec_unit.get_ioprio()
357 (ioprio_class, ioprio_data) = \
358 ioprio.to_class_and_data(ioprio_value)
359 ioprio_class = self.prompt_class(ioprio_class)
360 if ioprio_class == 'idle':
361 ioprio_data = 0
362 else:
363 ioprio_data = self.prompt_data(ioprio_data)
364 exec_unit.set_ioprio(ioprio_class, ioprio_data)
365 self.process_list.clear()
366 self.process_list.refresh_processes()
367 except IoprioSetError as e:
368 self.prompt_error('Error setting I/O priority: %s' % e.err)
369 except InvalidPid:
370 self.prompt_error('Invalid process id!')
371 except InvalidTid:
372 self.prompt_error('Invalid thread id!')
373 except InvalidIoprioData:
374 self.prompt_error('Invalid I/O priority data!')
375 except InvalidInt:
376 self.prompt_error('Invalid integer!')
377 except CancelInput:
378 self.prompt_clear()
379 else:
380 self.prompt_clear()
382 key_bindings = {
383 ord('q'):
384 lambda: sys.exit(0),
385 ord('Q'):
386 lambda: sys.exit(0),
387 ord('r'):
388 lambda: self.reverse_sorting(),
389 ord('R'):
390 lambda: self.reverse_sorting(),
391 ord('a'):
392 toggle_accumulated,
393 ord('A'):
394 toggle_accumulated,
395 ord('o'):
396 toggle_only_io,
397 ord('O'):
398 toggle_only_io,
399 ord('p'):
400 toggle_processes,
401 ord('P'):
402 toggle_processes,
403 ord('i'):
404 ionice,
405 ord('I'):
406 ionice,
407 curses.KEY_LEFT:
408 lambda: self.adjust_sorting_key(-1),
409 curses.KEY_RIGHT:
410 lambda: self.adjust_sorting_key(1),
411 curses.KEY_HOME:
412 lambda: self.adjust_sorting_key(-len(IOTopUI.sorting_keys)),
413 curses.KEY_END:
414 lambda: self.adjust_sorting_key(len(IOTopUI.sorting_keys))
417 action = key_bindings.get(key, lambda: None)
418 action()
420 def get_data(self):
421 def format(p):
422 stats = format_stats(self.options, p, self.process_list.duration)
423 io_delay, swapin_delay, read_bytes, write_bytes = stats
424 if self.has_swapin_io:
425 delay_stats = '%7s %7s ' % (swapin_delay, io_delay)
426 else:
427 delay_stats = ' ?unavailable? '
428 pid_format = '%%%dd' % MAX_PID_WIDTH
429 line = (pid_format + ' %4s %-8s %11s %11s %s') % (
430 p.pid, p.get_ioprio(), p.get_user()[:8], read_bytes,
431 write_bytes, delay_stats)
432 cmdline = p.get_cmdline()
433 if not self.options.batch:
434 remaining_length = self.width - len(line)
435 if 2 < remaining_length < len(cmdline):
436 len1 = (remaining_length - 1) // 2
437 offset2 = -(remaining_length - len1 - 1)
438 cmdline = cmdline[:len1] + '~' + cmdline[offset2:]
439 line += cmdline
440 if not self.options.batch:
441 line = line[:self.width]
442 return line
444 def should_format(p):
445 return not self.options.only or \
446 p.did_some_io(self.options.accumulated)
448 processes = list(filter(should_format,
449 self.process_list.processes.values()))
450 key = IOTopUI.sorting_keys[self.sorting_key][0]
451 if self.options.accumulated:
452 stats_lambda = lambda p: p.stats_accum
453 else:
454 stats_lambda = lambda p: p.stats_delta
455 processes.sort(key=lambda p: key(p, stats_lambda(p)),
456 reverse=self.sorting_reverse)
457 return list(map(format, processes))
459 def refresh_display(self, first_time, total, current, duration):
460 summary = [
461 'Total DISK READ: %s | Total DISK WRITE: %s' % (
462 format_bandwidth(self.options, total[0], duration).rjust(14),
463 format_bandwidth(self.options, total[1], duration).rjust(14)),
464 'Current DISK READ: %s | Current DISK WRITE: %s' % (
465 format_bandwidth(self.options, current[0], duration).rjust(14),
466 format_bandwidth(self.options, current[1], duration).rjust(14))
469 pid = max(0, (MAX_PID_WIDTH - 3)) * ' '
470 if self.options.processes:
471 pid += 'PID'
472 else:
473 pid += 'TID'
474 titles = [pid, ' PRIO', ' USER', ' DISK READ', ' DISK WRITE',
475 ' SWAPIN', ' IO', ' COMMAND']
476 self.has_swapin_io = Stats.has_blkio_delay_total
477 if self.has_swapin_io:
478 # Linux kernels without the sysctl return None and
479 # iotop just uses the heuristic for those versions.
480 # Linux kernels with the sysctl return True or False
481 # and iotop then uses the sysctl value instead.
482 if sysctl_task_delayacct() == False:
483 self.has_swapin_io = False
484 lines = self.get_data()
485 if self.options.time:
486 titles = [' TIME'] + titles
487 current_time = time.strftime('%H:%M:%S ')
488 lines = [current_time + l for l in lines]
489 summary = [current_time + s for s in summary]
490 if self.options.batch:
491 if self.options.quiet <= 2:
492 for s in summary:
493 print(s)
494 if self.options.quiet <= int(first_time):
495 print(''.join(titles))
496 for l in lines:
497 print(l)
498 sys.stdout.flush()
499 else:
500 self.win.erase()
502 if self.has_swapin_io:
503 status_msg = None
504 else:
505 status_msg = ('CONFIG_TASK_DELAY_ACCT '
506 'and kernel.task_delayacct sysctl '
507 'not enabled in kernel, '
508 'cannot determine SWAPIN and IO %')
510 help_lines = []
511 help_attrs = []
512 if self.options.help:
513 prev = self.get_sorting_key(-1)
514 next = self.get_sorting_key(1)
515 help = OrderedDict([
516 ('keys', ''),
517 ('any', 'refresh'),
518 ('q', 'quit'),
519 ('i', 'ionice'),
520 ('o', 'all' if self.options.only else 'active'),
521 ('p', 'threads' if self.options.processes else 'procs'),
522 ('a', 'bandwidth' if self.options.accumulated else 'accum'),
523 ('sort', ''),
524 ('r', 'asc' if self.sorting_reverse else 'desc'),
525 ('left', titles[prev].strip()),
526 ('right', titles[next].strip()),
527 ('home', titles[0].strip()),
528 ('end', titles[-1].strip()),
530 help_line = -1
531 for key, help in help.items():
532 if help:
533 help_item = [' ', key, ': ', help]
534 help_attr = [0, 0 if key == 'any' else curses.A_UNDERLINE, 0, 0]
535 else:
536 help_item = [' ', key, ':']
537 help_attr = [0, 0, 0]
538 if not help_lines or not help or len(''.join(help_lines[help_line]) + ''.join(help_item)) > self.width:
539 help_lines.append(help_item)
540 help_attrs.append(help_attr)
541 help_line += 1
542 else:
543 help_lines[help_line] += help_item
544 help_attrs[help_line] += help_attr
546 len_summary = len(summary)
547 len_titles = int(bool(titles))
548 len_status_msg = int(bool(status_msg))
549 len_help = len(help_lines)
550 max_lines = self.height - len_summary - len_titles - len_status_msg - len_help
551 if max_lines < 5:
552 titles = []
553 len_titles = 0
554 if max_lines < 6:
555 summary = []
556 len_summary = 0
557 if max_lines < 7:
558 status_msg = None
559 len_status_msg = 0
560 if max_lines < 8:
561 help_lines = []
562 help_attrs = []
563 len_help = 0
564 max_lines = self.height - len_summary - len_titles - len_status_msg - len_help
565 num_lines = min(len(lines), max_lines)
567 for i, s in enumerate(summary):
568 self.win.addstr(i, 0, s[:self.width])
569 if titles:
570 self.win.hline(len_summary, 0, ord(' ') | curses.A_REVERSE, self.width)
571 pos = 0
572 remaining_cols = self.width
573 for i in range(len(titles)):
574 attr = curses.A_REVERSE
575 title = titles[i]
576 if i == self.sorting_key:
577 title = title[1:]
578 if i == self.sorting_key:
579 attr |= curses.A_BOLD
580 title += self.sorting_reverse and '>' or '<'
581 title = title[:remaining_cols]
582 remaining_cols -= len(title)
583 if title:
584 self.win.addstr(len_summary, pos, title, attr)
585 pos += len(title)
586 for i in range(num_lines):
587 try:
588 def print_line(line):
589 self.win.addstr(i + len_summary + len_titles, 0, line)
590 try:
591 print_line(lines[i])
592 except UnicodeEncodeError:
593 # Python2: 'ascii' codec can't encode character ...
594 # https://bugs.debian.org/708252
595 print_line(lines[i].encode('utf-8'))
596 except curses.error:
597 pass
598 for ln in range(len_help):
599 line = self.height - len_status_msg - len_help + ln
600 self.win.hline(line, 0, ord(' ') | curses.A_REVERSE, self.width)
601 pos = 0
602 for i in range(len(help_lines[ln])):
603 self.win.insstr(line, pos, help_lines[ln][i], curses.A_REVERSE | help_attrs[ln][i])
604 pos += len(help_lines[ln][i])
605 if status_msg:
606 self.win.insstr(self.height - 1, 0, status_msg,
607 curses.A_BOLD)
608 self.win.refresh()
611 def run_iotop_window(win, options):
612 if options.batch:
613 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
614 else:
615 def clean_exit(*args, **kwargs):
616 sys.exit(0)
617 signal.signal(signal.SIGINT, clean_exit)
618 signal.signal(signal.SIGTERM, clean_exit)
619 taskstats_connection = TaskStatsNetlink(options)
620 process_list = ProcessList(taskstats_connection, options)
621 ui = IOTopUI(win, process_list, options)
622 ui.run()
625 def run_iotop(options):
626 try:
627 if options.batch:
628 return run_iotop_window(None, options)
629 else:
630 return curses.wrapper(run_iotop_window, options)
631 except curses.error as e:
632 print('iotop interface error:', e, file=sys.stderr)
633 sys.exit(1)
634 except OSError as e:
635 if e.errno == errno.EPERM:
636 print(e, file=sys.stderr)
637 print('''
638 The Linux kernel interfaces that iotop relies on now require root privileges
639 or the NET_ADMIN capability. This change occurred because a security issue
640 (CVE-2011-2494) was found that allows leakage of sensitive data across user
641 boundaries. If you require the ability to run iotop as a non-root user, please
642 configure sudo to allow you to run iotop as root.
644 Please do not file bugs on iotop about this.''', file=sys.stderr)
645 sys.exit(1)
646 if e.errno == errno.ENOENT:
647 print(e, file=sys.stderr)
648 print('''
649 The Linux kernel interfaces that iotop relies on for process I/O statistics
650 were not found. Please enable CONFIG_TASKSTATS in your Linux kernel build
651 configuration, use iotop outside a container and or share the host's
652 network namespace with the container.
654 Please do not file bugs on iotop about this.''', file=sys.stderr)
655 else:
656 raise
659 # Profiling
663 def _profile(continuation):
664 prof_file = 'iotop.prof'
665 try:
666 import cProfile
667 import pstats
668 print('Profiling using cProfile')
669 cProfile.runctx('continuation()', globals(), locals(), prof_file)
670 stats = pstats.Stats(prof_file)
671 except ImportError:
672 import hotshot
673 import hotshot.stats
674 prof = hotshot.Profile(prof_file, lineevents=1)
675 print('Profiling using hotshot')
676 prof.runcall(continuation)
677 prof.close()
678 stats = hotshot.stats.load(prof_file)
679 stats.strip_dirs()
680 stats.sort_stats('time', 'calls')
681 stats.print_stats(50)
682 stats.print_callees(50)
683 os.remove(prof_file)
686 # Main program
689 USAGE = '''%s [OPTIONS]
691 DISK READ and DISK WRITE are the block I/O bandwidth used during the sampling
692 period. SWAPIN and IO are the percentages of time the thread spent respectively
693 while swapping in and waiting on I/O more generally. PRIO is the I/O priority
694 at which the thread is running (set using the ionice command).
696 Controls: left and right arrows to change the sorting column, r to invert the
697 sorting order, o to toggle the --only option, p to toggle the --processes
698 option, a to toggle the --accumulated option, i to change I/O priority, q to
699 quit, any other key to force a refresh.''' % sys.argv[0]
702 def main():
703 try:
704 locale.setlocale(locale.LC_ALL, '')
705 except locale.Error:
706 print('unable to set locale, falling back to the default locale')
707 parser = optparse.OptionParser(usage=USAGE, version='iotop ' + VERSION)
708 parser.add_option('-o', '--only', action='store_true',
709 dest='only', default=False,
710 help='only show processes or threads actually doing I/O')
711 parser.add_option('-b', '--batch', action='store_true', dest='batch',
712 help='non-interactive mode')
713 parser.add_option('-n', '--iter', type='int', dest='iterations',
714 metavar='NUM',
715 help='number of iterations before ending [infinite]')
716 parser.add_option('-d', '--delay', type='float', dest='delay_seconds',
717 help='delay between iterations [1 second]',
718 metavar='SEC', default=1)
719 parser.add_option('-p', '--pid', type='int', dest='pids', action='append',
720 help='processes/threads to monitor [all]', metavar='PID')
721 parser.add_option('-u', '--user', type='str', dest='users',
722 action='append', help='users to monitor [all]',
723 metavar='USER')
724 parser.add_option('-P', '--processes', action='store_true',
725 dest='processes', default=False,
726 help='only show processes, not all threads')
727 parser.add_option('-a', '--accumulated', action='store_true',
728 dest='accumulated', default=False,
729 help='show accumulated I/O instead of bandwidth')
730 parser.add_option('-k', '--kilobytes', action='store_true',
731 dest='kilobytes', default=False,
732 help='use kilobytes instead of a human friendly unit')
733 parser.add_option('-t', '--time', action='store_true', dest='time',
734 help='add a timestamp on each line (implies --batch)')
735 parser.add_option('-q', '--quiet', action='count', dest='quiet', default=0,
736 help='suppress some lines of header (implies --batch)')
737 parser.add_option('--profile', action='store_true', dest='profile',
738 default=False, help=optparse.SUPPRESS_HELP)
739 parser.add_option('--no-help', action='store_false', dest='help', default=True,
740 help='suppress listing of shortcuts')
742 options, args = parser.parse_args()
743 if args:
744 parser.error('Unexpected arguments: ' + ' '.join(args))
745 find_uids(options)
746 options.pids = options.pids or []
747 options.batch = options.batch or options.time or options.quiet
749 main_loop = lambda: run_iotop(options)
751 if options.profile:
752 def safe_main_loop():
753 try:
754 main_loop()
755 except Exception:
756 pass
757 _profile(safe_main_loop)
758 else:
759 main_loop()