data.py get_cmdline: use errors=replace for unicode errors
[iotop.git] / iotop / ui.py
blob77f82c7d18cf69a7a437e48178aa794166adb2cc
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 if not self.has_swapin_io:
228 if new_sorting_key in (5, 6):
229 if delta <= 0:
230 new_sorting_key = 4
231 elif delta > 0:
232 new_sorting_key = 7
233 return new_sorting_key
235 # I wonder if switching to urwid for the display would be better here
237 def prompt_str(self, prompt, default=None, empty_is_cancel=True):
238 self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width)
239 self.win.addstr(1, 0, prompt, curses.A_BOLD)
240 self.win.refresh()
241 curses.echo()
242 curses.curs_set(1)
243 inp = self.win.getstr(1, len(prompt))
244 curses.curs_set(0)
245 curses.noecho()
246 if inp not in (None, ''):
247 return inp
248 if empty_is_cancel:
249 raise CancelInput()
250 return default
252 def prompt_int(self, prompt, default=None, empty_is_cancel=True):
253 inp = self.prompt_str(prompt, default, empty_is_cancel)
254 try:
255 return int(inp)
256 except ValueError:
257 raise InvalidInt()
259 def prompt_pid(self):
260 try:
261 return self.prompt_int('PID to ionice: ')
262 except InvalidInt:
263 raise InvalidPid()
264 except CancelInput:
265 raise
267 def prompt_tid(self):
268 try:
269 return self.prompt_int('TID to ionice: ')
270 except InvalidInt:
271 raise InvalidTid()
272 except CancelInput:
273 raise
275 def prompt_data(self, ioprio_data):
276 try:
277 if ioprio_data is not None:
278 inp = self.prompt_int('I/O priority data (0-7, currently %s): '
279 % ioprio_data, ioprio_data, False)
280 else:
281 inp = self.prompt_int('I/O priority data (0-7): ', None, False)
282 except InvalidInt:
283 raise InvalidIoprioData()
284 if inp < 0 or inp > 7:
285 raise InvalidIoprioData()
286 return inp
288 def prompt_set(self, prompt, display_list, ret_list, selected):
289 try:
290 selected = ret_list.index(selected)
291 except ValueError:
292 selected = -1
293 set_len = len(display_list) - 1
294 while True:
295 self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width)
296 self.win.insstr(1, 0, prompt, curses.A_BOLD)
297 offset = len(prompt)
298 for i, item in enumerate(display_list):
299 display = ' %s ' % item
300 if i is selected:
301 attr = curses.A_REVERSE
302 else:
303 attr = curses.A_NORMAL
304 self.win.insstr(1, offset, display, attr)
305 offset += len(display)
306 while True:
307 key = self.win.getch()
308 if key in (curses.KEY_LEFT, ord('l')) and selected > 0:
309 selected -= 1
310 break
311 elif (key in (curses.KEY_RIGHT, ord('r')) and
312 selected < set_len):
313 selected += 1
314 break
315 elif key in (curses.KEY_ENTER, ord('\n'), ord('\r')):
316 return ret_list[selected]
317 elif key in (27, curses.KEY_CANCEL, curses.KEY_CLOSE,
318 curses.KEY_EXIT, ord('q'), ord('Q')):
319 raise CancelInput()
321 def prompt_class(self, ioprio_class=None):
322 prompt = 'I/O priority class: '
323 classes_prompt = ['Real-time', 'Best-effort', 'Idle']
324 classes_ret = ['rt', 'be', 'idle']
325 if ioprio_class is None:
326 ioprio_class = 2
327 inp = self.prompt_set(prompt, classes_prompt,
328 classes_ret, ioprio_class)
329 return inp
331 def prompt_error(self, error='Error!'):
332 self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width)
333 self.win.insstr(1, 0, ' %s ' % error, curses.A_REVERSE)
334 self.win.refresh()
335 time.sleep(1)
337 def prompt_clear(self):
338 self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width)
339 self.win.refresh()
341 def handle_key(self, key):
342 def toggle_accumulated():
343 self.options.accumulated ^= True
345 def toggle_only_io():
346 self.options.only ^= True
348 def toggle_processes():
349 self.options.processes ^= True
350 self.process_list.clear()
351 self.process_list.refresh_processes()
353 def ionice():
354 try:
355 if self.options.processes:
356 pid = self.prompt_pid()
357 exec_unit = self.process_list.get_process(pid)
358 else:
359 tid = self.prompt_tid()
360 exec_unit = ThreadInfo(tid,
361 self.process_list.taskstats_connection)
362 ioprio_value = exec_unit.get_ioprio()
363 (ioprio_class, ioprio_data) = \
364 ioprio.to_class_and_data(ioprio_value)
365 ioprio_class = self.prompt_class(ioprio_class)
366 if ioprio_class == 'idle':
367 ioprio_data = 0
368 else:
369 ioprio_data = self.prompt_data(ioprio_data)
370 exec_unit.set_ioprio(ioprio_class, ioprio_data)
371 self.process_list.clear()
372 self.process_list.refresh_processes()
373 except IoprioSetError as e:
374 self.prompt_error('Error setting I/O priority: %s' % e.err)
375 except InvalidPid:
376 self.prompt_error('Invalid process id!')
377 except InvalidTid:
378 self.prompt_error('Invalid thread id!')
379 except InvalidIoprioData:
380 self.prompt_error('Invalid I/O priority data!')
381 except InvalidInt:
382 self.prompt_error('Invalid integer!')
383 except CancelInput:
384 self.prompt_clear()
385 else:
386 self.prompt_clear()
388 key_bindings = {
389 ord('q'):
390 lambda: sys.exit(0),
391 ord('Q'):
392 lambda: sys.exit(0),
393 ord('r'):
394 lambda: self.reverse_sorting(),
395 ord('R'):
396 lambda: self.reverse_sorting(),
397 ord('a'):
398 toggle_accumulated,
399 ord('A'):
400 toggle_accumulated,
401 ord('o'):
402 toggle_only_io,
403 ord('O'):
404 toggle_only_io,
405 ord('p'):
406 toggle_processes,
407 ord('P'):
408 toggle_processes,
409 ord('i'):
410 ionice,
411 ord('I'):
412 ionice,
413 curses.KEY_LEFT:
414 lambda: self.adjust_sorting_key(-1),
415 curses.KEY_RIGHT:
416 lambda: self.adjust_sorting_key(1),
417 curses.KEY_HOME:
418 lambda: self.adjust_sorting_key(-len(IOTopUI.sorting_keys)),
419 curses.KEY_END:
420 lambda: self.adjust_sorting_key(len(IOTopUI.sorting_keys))
423 action = key_bindings.get(key, lambda: None)
424 action()
426 def get_data(self):
427 def format(p):
428 stats = format_stats(self.options, p, self.process_list.duration)
429 io_delay, swapin_delay, read_bytes, write_bytes = stats
430 format = '%%%dd' % MAX_PID_WIDTH
431 params = p.pid,
432 format += ' %4s'
433 params += p.get_ioprio(),
434 format += ' %-8s'
435 params += p.get_user()[:8],
436 format += ' %11s %11s'
437 params += read_bytes, write_bytes
438 if self.has_swapin_io:
439 format += ' %7s %7s'
440 params += swapin_delay, io_delay
441 elif self.options.batch:
442 format += ' %s '
443 params += '?unavailable?',
444 format += ' '
445 line = format % (params)
446 cmdline = p.get_cmdline()
447 if not self.options.batch:
448 remaining_length = self.width - len(line)
449 if 2 < remaining_length < len(cmdline):
450 len1 = (remaining_length - 1) // 2
451 offset2 = -(remaining_length - len1 - 1)
452 cmdline = cmdline[:len1] + '~' + cmdline[offset2:]
453 line += cmdline
454 if not self.options.batch:
455 line = line[:self.width]
456 return line
458 def should_format(p):
459 return not self.options.only or \
460 p.did_some_io(self.options.accumulated)
462 processes = list(filter(should_format,
463 self.process_list.processes.values()))
464 key = IOTopUI.sorting_keys[self.sorting_key][0]
465 if self.options.accumulated:
466 stats_lambda = lambda p: p.stats_accum
467 else:
468 stats_lambda = lambda p: p.stats_delta
469 processes.sort(key=lambda p: key(p, stats_lambda(p)),
470 reverse=self.sorting_reverse)
471 return list(map(format, processes))
473 def refresh_display(self, first_time, total, current, duration):
474 summary = [
475 'Total DISK READ: %s | Total DISK WRITE: %s' % (
476 format_bandwidth(self.options, total[0], duration).rjust(14),
477 format_bandwidth(self.options, total[1], duration).rjust(14)),
478 'Current DISK READ: %s | Current DISK WRITE: %s' % (
479 format_bandwidth(self.options, current[0], duration).rjust(14),
480 format_bandwidth(self.options, current[1], duration).rjust(14))
483 pid = max(0, (MAX_PID_WIDTH - 3)) * ' '
484 if self.options.processes:
485 pid += 'PID'
486 else:
487 pid += 'TID'
488 titles = [pid, ' PRIO', ' USER', ' DISK READ', ' DISK WRITE',
489 ' SWAPIN', ' IO', ' COMMAND']
490 self.has_swapin_io = Stats.has_blkio_delay_total
491 if self.has_swapin_io:
492 # Linux kernels without the sysctl return None and
493 # iotop just uses the heuristic for those versions.
494 # Linux kernels with the sysctl return True or False
495 # and iotop then uses the sysctl value instead.
496 if sysctl_task_delayacct() == False:
497 self.has_swapin_io = False
498 self.adjust_sorting_key(0)
499 lines = self.get_data()
500 if self.options.time:
501 titles = [' TIME'] + titles
502 current_time = time.strftime('%H:%M:%S ')
503 lines = [current_time + l for l in lines]
504 summary = [current_time + s for s in summary]
505 if self.options.batch:
506 if self.options.quiet <= 2:
507 for s in summary:
508 print(s)
509 if self.options.quiet <= int(first_time):
510 print(''.join(titles))
511 for l in lines:
512 print(l)
513 sys.stdout.flush()
514 else:
515 self.win.erase()
517 if self.has_swapin_io:
518 status_msg = None
519 else:
520 status_msg = ('CONFIG_TASK_DELAY_ACCT '
521 'and kernel.task_delayacct sysctl '
522 'not enabled in kernel, '
523 'cannot determine SWAPIN and IO %')
525 help_lines = []
526 help_attrs = []
527 if self.options.help:
528 prev = self.get_sorting_key(-1)
529 next = self.get_sorting_key(1)
530 help = OrderedDict([
531 ('keys', ''),
532 ('any', 'refresh'),
533 ('q', 'quit'),
534 ('i', 'ionice'),
535 ('o', 'all' if self.options.only else 'active'),
536 ('p', 'threads' if self.options.processes else 'procs'),
537 ('a', 'bandwidth' if self.options.accumulated else 'accum'),
538 ('sort', ''),
539 ('r', 'asc' if self.sorting_reverse else 'desc'),
540 ('left', titles[prev].strip()),
541 ('right', titles[next].strip()),
542 ('home', titles[0].strip()),
543 ('end', titles[-1].strip()),
545 help_line = -1
546 for key, help in help.items():
547 if help:
548 help_item = [' ', key, ': ', help]
549 help_attr = [0, 0 if key == 'any' else curses.A_UNDERLINE, 0, 0]
550 else:
551 help_item = [' ', key, ':']
552 help_attr = [0, 0, 0]
553 if not help_lines or not help or len(''.join(help_lines[help_line]) + ''.join(help_item)) > self.width:
554 help_lines.append(help_item)
555 help_attrs.append(help_attr)
556 help_line += 1
557 else:
558 help_lines[help_line] += help_item
559 help_attrs[help_line] += help_attr
561 len_summary = len(summary)
562 len_titles = int(bool(titles))
563 len_status_msg = int(bool(status_msg))
564 len_help = len(help_lines)
565 max_lines = self.height - len_summary - len_titles - len_status_msg - len_help
566 if max_lines < 5:
567 titles = []
568 len_titles = 0
569 if max_lines < 6:
570 summary = []
571 len_summary = 0
572 if max_lines < 7:
573 status_msg = None
574 len_status_msg = 0
575 if max_lines < 8:
576 help_lines = []
577 help_attrs = []
578 len_help = 0
579 max_lines = self.height - len_summary - len_titles - len_status_msg - len_help
580 num_lines = min(len(lines), max_lines)
582 for i, s in enumerate(summary):
583 self.win.addstr(i, 0, s[:self.width])
584 if titles:
585 self.win.hline(len_summary, 0, ord(' ') | curses.A_REVERSE, self.width)
586 pos = 0
587 remaining_cols = self.width
588 for i in range(len(titles)):
589 if not self.has_swapin_io and i in (5, 6):
590 continue
591 attr = curses.A_REVERSE
592 title = titles[i]
593 if i == self.sorting_key:
594 title = title[1:]
595 if i == self.sorting_key:
596 attr |= curses.A_BOLD
597 title += self.sorting_reverse and '>' or '<'
598 title = title[:remaining_cols]
599 remaining_cols -= len(title)
600 if title:
601 self.win.addstr(len_summary, pos, title, attr)
602 pos += len(title)
603 for i in range(num_lines):
604 try:
605 def print_line(line):
606 self.win.addstr(i + len_summary + len_titles, 0, line)
607 try:
608 print_line(lines[i])
609 except UnicodeEncodeError:
610 # Python2: 'ascii' codec can't encode character ...
611 # https://bugs.debian.org/708252
612 print_line(lines[i].encode('utf-8'))
613 except curses.error:
614 pass
615 for ln in range(len_help):
616 line = self.height - len_status_msg - len_help + ln
617 self.win.hline(line, 0, ord(' ') | curses.A_REVERSE, self.width)
618 pos = 0
619 for i in range(len(help_lines[ln])):
620 self.win.insstr(line, pos, help_lines[ln][i], curses.A_REVERSE | help_attrs[ln][i])
621 pos += len(help_lines[ln][i])
622 if status_msg:
623 self.win.insstr(self.height - 1, 0, status_msg,
624 curses.A_BOLD)
625 self.win.refresh()
628 def run_iotop_window(win, options):
629 if options.batch:
630 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
631 else:
632 def clean_exit(*args, **kwargs):
633 sys.exit(0)
634 signal.signal(signal.SIGINT, clean_exit)
635 signal.signal(signal.SIGTERM, clean_exit)
636 taskstats_connection = TaskStatsNetlink(options)
637 process_list = ProcessList(taskstats_connection, options)
638 ui = IOTopUI(win, process_list, options)
639 ui.run()
642 def run_iotop(options):
643 try:
644 if options.batch:
645 return run_iotop_window(None, options)
646 else:
647 return curses.wrapper(run_iotop_window, options)
648 except curses.error as e:
649 print('iotop interface error:', e, file=sys.stderr)
650 sys.exit(1)
651 except OSError as e:
652 if e.errno == errno.EPERM:
653 print(e, file=sys.stderr)
654 print('''
655 The Linux kernel interfaces that iotop relies on now require root privileges
656 or the NET_ADMIN capability. This change occurred because a security issue
657 (CVE-2011-2494) was found that allows leakage of sensitive data across user
658 boundaries. If you require the ability to run iotop as a non-root user, please
659 configure sudo to allow you to run iotop as root.
661 Please do not file bugs on iotop about this.''', file=sys.stderr)
662 sys.exit(1)
663 if e.errno == errno.ENOENT:
664 print(e, file=sys.stderr)
665 print('''
666 The Linux kernel interfaces that iotop relies on for process I/O statistics
667 were not found. Please enable CONFIG_TASKSTATS in your Linux kernel build
668 configuration, use iotop outside a container and or share the host's
669 network namespace with the container.
671 Please do not file bugs on iotop about this.''', file=sys.stderr)
672 else:
673 raise
676 # Profiling
680 def _profile(continuation):
681 prof_file = 'iotop.prof'
682 try:
683 import cProfile
684 import pstats
685 print('Profiling using cProfile')
686 cProfile.runctx('continuation()', globals(), locals(), prof_file)
687 stats = pstats.Stats(prof_file)
688 except ImportError:
689 import hotshot
690 import hotshot.stats
691 prof = hotshot.Profile(prof_file, lineevents=1)
692 print('Profiling using hotshot')
693 prof.runcall(continuation)
694 prof.close()
695 stats = hotshot.stats.load(prof_file)
696 stats.strip_dirs()
697 stats.sort_stats('time', 'calls')
698 stats.print_stats(50)
699 stats.print_callees(50)
700 os.remove(prof_file)
703 # Main program
706 USAGE = '''%s [OPTIONS]
708 DISK READ and DISK WRITE are the block I/O bandwidth used during the sampling
709 period. SWAPIN and IO are the percentages of time the thread spent respectively
710 while swapping in and waiting on I/O more generally. PRIO is the I/O priority
711 at which the thread is running (set using the ionice command).
713 Controls: left and right arrows to change the sorting column, r to invert the
714 sorting order, o to toggle the --only option, p to toggle the --processes
715 option, a to toggle the --accumulated option, i to change I/O priority, q to
716 quit, any other key to force a refresh.''' % sys.argv[0]
719 def main():
720 try:
721 locale.setlocale(locale.LC_ALL, '')
722 except locale.Error:
723 print('unable to set locale, falling back to the default locale')
724 parser = optparse.OptionParser(usage=USAGE, version='iotop ' + VERSION)
725 parser.add_option('-o', '--only', action='store_true',
726 dest='only', default=False,
727 help='only show processes or threads actually doing I/O')
728 parser.add_option('-b', '--batch', action='store_true', dest='batch',
729 help='non-interactive mode')
730 parser.add_option('-n', '--iter', type='int', dest='iterations',
731 metavar='NUM',
732 help='number of iterations before ending [infinite]')
733 parser.add_option('-d', '--delay', type='float', dest='delay_seconds',
734 help='delay between iterations [1 second]',
735 metavar='SEC', default=1)
736 parser.add_option('-p', '--pid', type='int', dest='pids', action='append',
737 help='processes/threads to monitor [all]', metavar='PID')
738 parser.add_option('-u', '--user', type='str', dest='users',
739 action='append', help='users to monitor [all]',
740 metavar='USER')
741 parser.add_option('-P', '--processes', action='store_true',
742 dest='processes', default=False,
743 help='only show processes, not all threads')
744 parser.add_option('-a', '--accumulated', action='store_true',
745 dest='accumulated', default=False,
746 help='show accumulated I/O instead of bandwidth')
747 parser.add_option('-k', '--kilobytes', action='store_true',
748 dest='kilobytes', default=False,
749 help='use kilobytes instead of a human friendly unit')
750 parser.add_option('-t', '--time', action='store_true', dest='time',
751 help='add a timestamp on each line (implies --batch)')
752 parser.add_option('-q', '--quiet', action='count', dest='quiet', default=0,
753 help='suppress some lines of header (implies --batch)')
754 parser.add_option('--profile', action='store_true', dest='profile',
755 default=False, help=optparse.SUPPRESS_HELP)
756 parser.add_option('--no-help', action='store_false', dest='help', default=True,
757 help='suppress listing of shortcuts')
759 options, args = parser.parse_args()
760 if args:
761 parser.error('Unexpected arguments: ' + ' '.join(args))
762 find_uids(options)
763 options.pids = options.pids or []
764 options.batch = options.batch or options.time or options.quiet
766 main_loop = lambda: run_iotop(options)
768 if options.profile:
769 def safe_main_loop():
770 try:
771 main_loop()
772 except Exception:
773 pass
774 _profile(safe_main_loop)
775 else:
776 main_loop()