~ubuntu-branches/ubuntu/trusty/iotop/trusty

« back to all changes in this revision

Viewing changes to .pc/0003-Exit-when-a-SIGTERM-signal-is-received-in-interactiv.patch/iotop/ui.py

  • Committer: Package Import Robot
  • Author(s): Paul Wise
  • Date: 2013-06-07 14:00:56 UTC
  • mfrom: (13.1.6 sid)
  • Revision ID: package-import@ubuntu.com-20130607140056-vl0ayl9tda9v50b8
Tags: 0.6-1
* New upstream release
  - Doesn't crash with UTF-8 command-lines (Closes: #708252) (LP: #1179975)

Show diffs side-by-side

added added

removed removed

Lines of Context:
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.
5
 
#
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.
10
 
#
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
14
 
#
15
 
# See the COPYING file for license information.
16
 
#
17
 
# Copyright (c) 2007 Guillaume Chazarain <guichaz@gmail.com>
18
 
 
19
 
# Allow printing with same syntax in Python 2/3
20
 
from __future__ import print_function
21
 
 
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
32
 
 
33
 
from iotop.data import find_uids, TaskStatsNetlink, ProcessList, Stats
34
 
from iotop.data import ThreadInfo
35
 
from iotop.version import VERSION
36
 
from iotop import ioprio
37
 
from iotop.ioprio import IoprioSetError
38
 
 
39
 
#
40
 
# Utility functions for the UI
41
 
#
42
 
 
43
 
UNITS = ['B', 'K', 'M', 'G', 'T', 'P', 'E']
44
 
 
45
 
def human_size(size):
46
 
    if size > 0:
47
 
        sign = ''
48
 
    elif size < 0:
49
 
        sign = '-'
50
 
        size = -size
51
 
    else:
52
 
        return '0.00 B'
53
 
 
54
 
    expo = int(math.log(size / 2, 2) / 10)
55
 
    return '%s%.2f %s' % (sign, (float(size) / (1 << (10 * expo))), UNITS[expo])
56
 
 
57
 
def format_size(options, bytes):
58
 
    if options.kilobytes:
59
 
        return '%.2f K' % (bytes / 1024.0)
60
 
    return human_size(bytes)
61
 
 
62
 
def format_bandwidth(options, size, duration):
63
 
    return format_size(options, size and float(size) / duration) + '/s'
64
 
 
65
 
def format_stats(options, process, duration):
66
 
    # Keep in sync with TaskStatsNetlink.members_offsets and
67
 
    # IOTopUI.get_data(self)
68
 
    def delay2percent(delay): # delay in ns, duration in s
69
 
        return '%.2f %%' % min(99.99, delay / (duration * 10000000.0))
70
 
    if options.accumulated:
71
 
        stats = process.stats_accum
72
 
        display_format = lambda size, duration: format_size(options, size)
73
 
        duration = time.time() - process.stats_accum_timestamp
74
 
    else:
75
 
        stats = process.stats_delta
76
 
        display_format = lambda size, duration: format_bandwidth(
77
 
                                                        options, size, duration)
78
 
    io_delay = delay2percent(stats.blkio_delay_total)
79
 
    swapin_delay = delay2percent(stats.swapin_delay_total)
80
 
    read_bytes = display_format(stats.read_bytes, duration)
81
 
    written_bytes = stats.write_bytes - stats.cancelled_write_bytes
82
 
    written_bytes = max(0, written_bytes)
83
 
    write_bytes = display_format(written_bytes, duration)
84
 
    return io_delay, swapin_delay, read_bytes, write_bytes
85
 
 
86
 
def get_max_pid_width():
87
 
    try:
88
 
        return len(open('/proc/sys/kernel/pid_max').read().strip())
89
 
    except Exception as e:
90
 
        print(e)
91
 
        # Reasonable default in case something fails
92
 
        return 5
93
 
 
94
 
MAX_PID_WIDTH = get_max_pid_width()
95
 
 
96
 
#
97
 
# UI Exceptions
98
 
#
99
 
 
100
 
class CancelInput(Exception): pass
101
 
class InvalidInt(Exception): pass
102
 
class InvalidPid(Exception): pass
103
 
class InvalidTid(Exception): pass
104
 
class InvalidIoprioData(Exception): pass
105
 
 
106
 
#
107
 
# The UI
108
 
#
109
 
 
110
 
class IOTopUI(object):
111
 
    # key, reverse
112
 
    sorting_keys = [
113
 
        (lambda p, s: p.pid, False),
114
 
        (lambda p, s: p.ioprio_sort_key(), False),
115
 
        (lambda p, s: p.get_user(), False),
116
 
        (lambda p, s: s.read_bytes, True),
117
 
        (lambda p, s: s.write_bytes - s.cancelled_write_bytes, True),
118
 
        (lambda p, s: s.swapin_delay_total, True),
119
 
        # The default sorting (by I/O % time) should show processes doing
120
 
        # only writes, without waiting on them
121
 
        (lambda p, s: s.blkio_delay_total or
122
 
                      int(not(not(s.read_bytes or s.write_bytes))), True),
123
 
        (lambda p, s: p.get_cmdline(), False),
124
 
    ]
125
 
 
126
 
    def __init__(self, win, process_list, options):
127
 
        self.process_list = process_list
128
 
        self.options = options
129
 
        self.sorting_key = 6
130
 
        self.sorting_reverse = IOTopUI.sorting_keys[self.sorting_key][1]
131
 
        if not self.options.batch:
132
 
            self.win = win
133
 
            self.resize()
134
 
            try:
135
 
                curses.use_default_colors()
136
 
                curses.start_color()
137
 
                curses.curs_set(0)
138
 
            except curses.error:
139
 
                # This call can fail with misconfigured terminals, for example
140
 
                # TERM=xterm-color. This is harmless
141
 
                pass
142
 
 
143
 
    def resize(self):
144
 
        self.height, self.width = self.win.getmaxyx()
145
 
 
146
 
    def run(self):
147
 
        iterations = 0
148
 
        poll = select.poll()
149
 
        if not self.options.batch:
150
 
            poll.register(sys.stdin.fileno(), select.POLLIN|select.POLLPRI)
151
 
        while self.options.iterations is None or \
152
 
              iterations < self.options.iterations:
153
 
            total, actual = self.process_list.refresh_processes()
154
 
            self.refresh_display(iterations == 0, total, actual,
155
 
                                 self.process_list.duration)
156
 
            if self.options.iterations is not None:
157
 
                iterations += 1
158
 
                if iterations >= self.options.iterations:
159
 
                    break
160
 
            elif iterations == 0:
161
 
                iterations = 1
162
 
 
163
 
            try:
164
 
                events = poll.poll(self.options.delay_seconds * 1000.0)
165
 
            except select.error as e:
166
 
                if e.args and e.args[0] == errno.EINTR:
167
 
                    events = 0
168
 
                else:
169
 
                    raise
170
 
            if not self.options.batch:
171
 
                self.resize()
172
 
            if events:
173
 
                key = self.win.getch()
174
 
                self.handle_key(key)
175
 
 
176
 
    def reverse_sorting(self):
177
 
        self.sorting_reverse = not self.sorting_reverse
178
 
 
179
 
    def adjust_sorting_key(self, delta):
180
 
        orig_sorting_key = self.sorting_key
181
 
        self.sorting_key += delta
182
 
        self.sorting_key = max(0, self.sorting_key)
183
 
        self.sorting_key = min(len(IOTopUI.sorting_keys) - 1, self.sorting_key)
184
 
        if orig_sorting_key != self.sorting_key:
185
 
            self.sorting_reverse = IOTopUI.sorting_keys[self.sorting_key][1]
186
 
 
187
 
    # I wonder if switching to urwid for the display would be better here
188
 
 
189
 
    def prompt_str(self, prompt, default=None, empty_is_cancel=True):
190
 
        self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width)
191
 
        self.win.addstr(1, 0, prompt, curses.A_BOLD)
192
 
        self.win.refresh()
193
 
        curses.echo()
194
 
        curses.curs_set(1)
195
 
        inp = self.win.getstr(1, len(prompt))
196
 
        curses.curs_set(0)
197
 
        curses.noecho()
198
 
        if inp not in (None, ''):
199
 
            return inp
200
 
        if empty_is_cancel:
201
 
            raise CancelInput()
202
 
        return default
203
 
 
204
 
    def prompt_int(self, prompt, default = None, empty_is_cancel = True):
205
 
        inp = self.prompt_str(prompt, default, empty_is_cancel)
206
 
        try:
207
 
            return int(inp)
208
 
        except ValueError:
209
 
            raise InvalidInt()
210
 
 
211
 
    def prompt_pid(self):
212
 
        try:
213
 
            return self.prompt_int('PID to ionice: ')
214
 
        except InvalidInt:
215
 
            raise InvalidPid()
216
 
        except CancelInput:
217
 
            raise
218
 
 
219
 
    def prompt_tid(self):
220
 
        try:
221
 
            return self.prompt_int('TID to ionice: ')
222
 
        except InvalidInt:
223
 
            raise InvalidTid()
224
 
        except CancelInput:
225
 
            raise
226
 
 
227
 
    def prompt_data(self, ioprio_data):
228
 
        try:
229
 
            if ioprio_data is not None:
230
 
                inp = self.prompt_int('I/O priority data (0-7, currently %s): '
231
 
                                      % ioprio_data, ioprio_data, False)
232
 
            else:
233
 
                inp = self.prompt_int('I/O priority data (0-7): ', None, False)
234
 
        except InvalidInt:
235
 
            raise InvalidIoprioData()
236
 
        if inp < 0 or inp > 7:
237
 
            raise InvalidIoprioData()
238
 
        return inp
239
 
 
240
 
    def prompt_set(self, prompt, display_list, ret_list, selected):
241
 
        try:
242
 
            selected = ret_list.index(selected)
243
 
        except ValueError:
244
 
            selected = -1
245
 
        set_len = len(display_list) - 1
246
 
        while True:
247
 
            self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width)
248
 
            self.win.insstr(1, 0, prompt, curses.A_BOLD)
249
 
            offset = len(prompt)
250
 
            for i, item in enumerate(display_list):
251
 
                display = ' %s ' % item
252
 
                if i is selected:
253
 
                    attr = curses.A_REVERSE
254
 
                else:
255
 
                    attr = curses.A_NORMAL
256
 
                self.win.insstr(1, offset, display, attr)
257
 
                offset += len(display)
258
 
            while True:
259
 
                key = self.win.getch()
260
 
                if key in (curses.KEY_LEFT, ord('l')) and selected > 0:
261
 
                    selected -= 1
262
 
                    break
263
 
                elif key in (curses.KEY_RIGHT, ord('r')) and selected < set_len:
264
 
                    selected += 1
265
 
                    break
266
 
                elif key in (curses.KEY_ENTER, ord('\n'), ord('\r')):
267
 
                    return ret_list[selected]
268
 
                elif key in (27, curses.KEY_CANCEL, curses.KEY_CLOSE,
269
 
                             curses.KEY_EXIT, ord('q'), ord('Q')):
270
 
                    raise CancelInput()
271
 
 
272
 
    def prompt_class(self, ioprio_class=None):
273
 
        prompt = 'I/O priority class: '
274
 
        classes_prompt = ['Real-time', 'Best-effort', 'Idle']
275
 
        classes_ret = ['rt', 'be', 'idle']
276
 
        if ioprio_class is None:
277
 
            ioprio_class = 2
278
 
        inp = self.prompt_set(prompt, classes_prompt, classes_ret, ioprio_class)
279
 
        return inp
280
 
 
281
 
    def prompt_error(self, error = 'Error!'):
282
 
        self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width)
283
 
        self.win.insstr(1, 0, '  %s  ' % error, curses.A_REVERSE)
284
 
        self.win.refresh()
285
 
        time.sleep(1)
286
 
 
287
 
    def prompt_clear(self):
288
 
        self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width)
289
 
        self.win.refresh()
290
 
 
291
 
    def handle_key(self, key):
292
 
        def toggle_accumulated():
293
 
            self.options.accumulated ^= True
294
 
        def toggle_only_io():
295
 
            self.options.only ^= True
296
 
        def toggle_processes():
297
 
            self.options.processes ^= True
298
 
            self.process_list.clear()
299
 
            self.process_list.refresh_processes()
300
 
        def ionice():
301
 
            try:
302
 
                if self.options.processes:
303
 
                    pid = self.prompt_pid()
304
 
                    exec_unit = self.process_list.get_process(pid)
305
 
                else:
306
 
                    tid = self.prompt_tid()
307
 
                    exec_unit = ThreadInfo(tid,
308
 
                                         self.process_list.taskstats_connection)
309
 
                ioprio_value = exec_unit.get_ioprio()
310
 
                (ioprio_class, ioprio_data) = \
311
 
                                          ioprio.to_class_and_data(ioprio_value)
312
 
                ioprio_class = self.prompt_class(ioprio_class)
313
 
                if ioprio_class == 'idle':
314
 
                    ioprio_data = 0
315
 
                else:
316
 
                    ioprio_data = self.prompt_data(ioprio_data)
317
 
                exec_unit.set_ioprio(ioprio_class, ioprio_data)
318
 
                self.process_list.clear()
319
 
                self.process_list.refresh_processes()
320
 
            except IoprioSetError as e:
321
 
                self.prompt_error('Error setting I/O priority: %s' % e.err)
322
 
            except InvalidPid:
323
 
                self.prompt_error('Invalid process id!')
324
 
            except InvalidTid:
325
 
                self.prompt_error('Invalid thread id!')
326
 
            except InvalidIoprioData:
327
 
                self.prompt_error('Invalid I/O priority data!')
328
 
            except InvalidInt:
329
 
                self.prompt_error('Invalid integer!')
330
 
            except CancelInput:
331
 
                self.prompt_clear()
332
 
            else:
333
 
                self.prompt_clear()
334
 
 
335
 
        key_bindings = {
336
 
            ord('q'):
337
 
                lambda: sys.exit(0),
338
 
            ord('Q'):
339
 
                lambda: sys.exit(0),
340
 
            ord('r'):
341
 
                lambda: self.reverse_sorting(),
342
 
            ord('R'):
343
 
                lambda: self.reverse_sorting(),
344
 
            ord('a'):
345
 
                toggle_accumulated,
346
 
            ord('A'):
347
 
                toggle_accumulated,
348
 
            ord('o'):
349
 
                toggle_only_io,
350
 
            ord('O'):
351
 
                toggle_only_io,
352
 
            ord('p'):
353
 
                toggle_processes,
354
 
            ord('P'):
355
 
                toggle_processes,
356
 
            ord('i'):
357
 
                ionice,
358
 
            ord('I'):
359
 
                ionice,
360
 
            curses.KEY_LEFT:
361
 
                lambda: self.adjust_sorting_key(-1),
362
 
            curses.KEY_RIGHT:
363
 
                lambda: self.adjust_sorting_key(1),
364
 
            curses.KEY_HOME:
365
 
                lambda: self.adjust_sorting_key(-len(IOTopUI.sorting_keys)),
366
 
            curses.KEY_END:
367
 
                lambda: self.adjust_sorting_key(len(IOTopUI.sorting_keys))
368
 
        }
369
 
 
370
 
        action = key_bindings.get(key, lambda: None)
371
 
        action()
372
 
 
373
 
    def get_data(self):
374
 
        def format(p):
375
 
            stats = format_stats(self.options, p, self.process_list.duration)
376
 
            io_delay, swapin_delay, read_bytes, write_bytes = stats
377
 
            if Stats.has_blkio_delay_total:
378
 
                delay_stats = '%7s %7s ' % (swapin_delay, io_delay)
379
 
            else:
380
 
                delay_stats = ' ?unavailable?  '
381
 
            pid_format = '%%%dd' % MAX_PID_WIDTH
382
 
            line = (pid_format + ' %4s %-8s %11s %11s %s') % (
383
 
                p.pid, p.get_ioprio(), p.get_user()[:8], read_bytes,
384
 
                write_bytes, delay_stats)
385
 
            cmdline = p.get_cmdline()
386
 
            if not self.options.batch:
387
 
                remaining_length = self.width - len(line)
388
 
                if 2 < remaining_length < len(cmdline):
389
 
                    len1 = (remaining_length - 1) // 2
390
 
                    offset2 = -(remaining_length - len1 - 1)
391
 
                    cmdline = cmdline[:len1] + '~' + cmdline[offset2:]
392
 
            line += cmdline
393
 
            if not self.options.batch:
394
 
                line = line[:self.width]
395
 
            return line
396
 
 
397
 
        def should_format(p):
398
 
            return not self.options.only or \
399
 
                   p.did_some_io(self.options.accumulated)
400
 
 
401
 
        processes = list(filter(should_format,
402
 
                                self.process_list.processes.values()))
403
 
        key = IOTopUI.sorting_keys[self.sorting_key][0]
404
 
        if self.options.accumulated:
405
 
            stats_lambda = lambda p: p.stats_accum
406
 
        else:
407
 
            stats_lambda = lambda p: p.stats_delta
408
 
        processes.sort(key=lambda p: key(p, stats_lambda(p)),
409
 
                       reverse=self.sorting_reverse)
410
 
        if not self.options.batch:
411
 
            del processes[self.height - 2:]
412
 
        return list(map(format, processes))
413
 
 
414
 
    def refresh_display(self, first_time, total, actual, duration):
415
 
        summary = [
416
 
                'Total DISK READ : %s | Total DISK WRITE : %s' % (
417
 
                format_bandwidth(self.options, total[0], duration).rjust(14),
418
 
                format_bandwidth(self.options, total[1], duration).rjust(14)),
419
 
                'Actual DISK READ: %s | Actual DISK WRITE: %s' % (
420
 
                format_bandwidth(self.options, actual[0], duration).rjust(14),
421
 
                format_bandwidth(self.options, actual[1], duration).rjust(14))
422
 
        ]
423
 
 
424
 
        pid = max(0, (MAX_PID_WIDTH - 3)) * ' '
425
 
        if self.options.processes:
426
 
            pid += 'PID'
427
 
        else:
428
 
            pid += 'TID'
429
 
        titles = [pid, '  PRIO', '  USER', '     DISK READ', '  DISK WRITE',
430
 
                  '  SWAPIN', '      IO', '    COMMAND']
431
 
        lines = self.get_data()
432
 
        if self.options.time:
433
 
            titles = ['    TIME'] + titles
434
 
            current_time = time.strftime('%H:%M:%S ')
435
 
            lines = [current_time + l for l in lines]
436
 
            summary = [current_time + s for s in summary]
437
 
        if self.options.batch:
438
 
            if self.options.quiet <= 2:
439
 
                for s in summary:
440
 
                    print(s)
441
 
                if self.options.quiet <= int(first_time):
442
 
                    print(''.join(titles))
443
 
            for l in lines:
444
 
                print(l)
445
 
            sys.stdout.flush()
446
 
        else:
447
 
            self.win.erase()
448
 
            for i, s in enumerate(summary):
449
 
                self.win.addstr(i, 0, s[:self.width])
450
 
            self.win.hline(len(summary), 0, ord(' ') | curses.A_REVERSE,
451
 
                           self.width)
452
 
            remaining_cols = self.width
453
 
            for i in range(len(titles)):
454
 
                attr = curses.A_REVERSE
455
 
                title = titles[i]
456
 
                if i == self.sorting_key:
457
 
                    title = title[1:]
458
 
                if i == self.sorting_key:
459
 
                    attr |= curses.A_BOLD
460
 
                    title += self.sorting_reverse and '>' or '<'
461
 
                title = title[:remaining_cols]
462
 
                remaining_cols -= len(title)
463
 
                self.win.addstr(title, attr)
464
 
            if Stats.has_blkio_delay_total:
465
 
                status_msg = None
466
 
            else:
467
 
                status_msg = ('CONFIG_TASK_DELAY_ACCT not enabled in kernel, '
468
 
                              'cannot determine SWAPIN and IO %')
469
 
            num_lines = min(len(lines), self.height - 2 - int(bool(status_msg)))
470
 
            for i in range(num_lines):
471
 
                try:
472
 
                    self.win.addstr(i + len(summary) + 1, 0, lines[i])
473
 
                except curses.error:
474
 
                    pass
475
 
            if status_msg:
476
 
                self.win.insstr(self.height - len(summary), 0, status_msg,
477
 
                                curses.A_BOLD)
478
 
            self.win.refresh()
479
 
 
480
 
def run_iotop_window(win, options):
481
 
    if options.batch:
482
 
        signal.signal(signal.SIGPIPE, signal.SIG_DFL)
483
 
    taskstats_connection = TaskStatsNetlink(options)
484
 
    process_list = ProcessList(taskstats_connection, options)
485
 
    ui = IOTopUI(win, process_list, options)
486
 
    ui.run()
487
 
 
488
 
def run_iotop(options):
489
 
    try:
490
 
        if options.batch:
491
 
            return run_iotop_window(None, options)
492
 
        else:
493
 
            return curses.wrapper(run_iotop_window, options)
494
 
    except OSError as e:
495
 
        if e.errno == errno.EPERM:
496
 
            print(e, file=sys.stderr)
497
 
            print('''
498
 
The Linux kernel interfaces that iotop relies on now require root priviliges
499
 
or the NET_ADMIN capability. This change occured because a security issue
500
 
(CVE-2011-2494) was found that allows leakage of sensitive data across user
501
 
boundaries. If you require the ability to run iotop as a non-root user, please
502
 
configure sudo to allow you to run iotop as root.
503
 
 
504
 
Please do not file bugs on iotop about this.''', file=sys.stderr)
505
 
            sys.exit(1)
506
 
        else:
507
 
            raise
508
 
 
509
 
#
510
 
# Profiling
511
 
#
512
 
 
513
 
def _profile(continuation):
514
 
    prof_file = 'iotop.prof'
515
 
    try:
516
 
        import cProfile
517
 
        import pstats
518
 
        print('Profiling using cProfile')
519
 
        cProfile.runctx('continuation()', globals(), locals(), prof_file)
520
 
        stats = pstats.Stats(prof_file)
521
 
    except ImportError:
522
 
        import hotshot
523
 
        import hotshot.stats
524
 
        prof = hotshot.Profile(prof_file, lineevents=1)
525
 
        print('Profiling using hotshot')
526
 
        prof.runcall(continuation)
527
 
        prof.close()
528
 
        stats = hotshot.stats.load(prof_file)
529
 
    stats.strip_dirs()
530
 
    stats.sort_stats('time', 'calls')
531
 
    stats.print_stats(50)
532
 
    stats.print_callees(50)
533
 
    os.remove(prof_file)
534
 
 
535
 
#
536
 
# Main program
537
 
#
538
 
 
539
 
USAGE = '''%s [OPTIONS]
540
 
 
541
 
DISK READ and DISK WRITE are the block I/O bandwidth used during the sampling
542
 
period. SWAPIN and IO are the percentages of time the thread spent respectively
543
 
while swapping in and waiting on I/O more generally. PRIO is the I/O priority at
544
 
which the thread is running (set using the ionice command).
545
 
 
546
 
Controls: left and right arrows to change the sorting column, r to invert the
547
 
sorting order, o to toggle the --only option, p to toggle the --processes
548
 
option, a to toggle the --accumulated option, i to change I/O priority, q to
549
 
quit, any other key to force a refresh.''' % sys.argv[0]
550
 
 
551
 
def main():
552
 
    try:
553
 
        locale.setlocale(locale.LC_ALL, '')
554
 
    except locale.Error:
555
 
        print('unable to set locale, falling back to the default locale')
556
 
    parser = optparse.OptionParser(usage=USAGE, version='iotop ' + VERSION)
557
 
    parser.add_option('-o', '--only', action='store_true',
558
 
                      dest='only', default=False,
559
 
                      help='only show processes or threads actually doing I/O')
560
 
    parser.add_option('-b', '--batch', action='store_true', dest='batch',
561
 
                      help='non-interactive mode')
562
 
    parser.add_option('-n', '--iter', type='int', dest='iterations',
563
 
                      metavar='NUM',
564
 
                      help='number of iterations before ending [infinite]')
565
 
    parser.add_option('-d', '--delay', type='float', dest='delay_seconds',
566
 
                      help='delay between iterations [1 second]',
567
 
                      metavar='SEC', default=1)
568
 
    parser.add_option('-p', '--pid', type='int', dest='pids', action='append',
569
 
                      help='processes/threads to monitor [all]', metavar='PID')
570
 
    parser.add_option('-u', '--user', type='str', dest='users', action='append',
571
 
                      help='users to monitor [all]', metavar='USER')
572
 
    parser.add_option('-P', '--processes', action='store_true',
573
 
                      dest='processes', default=False,
574
 
                      help='only show processes, not all threads')
575
 
    parser.add_option('-a', '--accumulated', action='store_true',
576
 
                      dest='accumulated', default=False,
577
 
                      help='show accumulated I/O instead of bandwidth')
578
 
    parser.add_option('-k', '--kilobytes', action='store_true',
579
 
                      dest='kilobytes', default=False,
580
 
                      help='use kilobytes instead of a human friendly unit')
581
 
    parser.add_option('-t', '--time', action='store_true', dest='time',
582
 
                      help='add a timestamp on each line (implies --batch)')
583
 
    parser.add_option('-q', '--quiet', action='count', dest='quiet', default=0,
584
 
                      help='suppress some lines of header (implies --batch)')
585
 
    parser.add_option('--profile', action='store_true', dest='profile',
586
 
                      default=False, help=optparse.SUPPRESS_HELP)
587
 
 
588
 
    options, args = parser.parse_args()
589
 
    if args:
590
 
        parser.error('Unexpected arguments: ' + ' '.join(args))
591
 
    find_uids(options)
592
 
    options.pids = options.pids or []
593
 
    options.batch = options.batch or options.time or options.quiet
594
 
 
595
 
    main_loop = lambda: run_iotop(options)
596
 
 
597
 
    if options.profile:
598
 
        def safe_main_loop():
599
 
            try:
600
 
                main_loop()
601
 
            except:
602
 
                pass
603
 
        _profile(safe_main_loop)
604
 
    else:
605
 
        main_loop()
606