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):
25 res
= '%.2f %s' % ((float(bw
) / base
), UNITS
[i
])
28 res
= str(bw
) + ' ' + UNITS
[0]
31 def human_stats(stats
, duration
):
32 # Keep in sync with TaskStatsNetlink.members_offsets and
33 # IOTopUI.get_data(self)
34 def delay2percent(name
): # delay in ns, duration in s
35 return '%.2f %%' % min(99.99, stats
[name
][1] / (duration
* 10000000.0))
36 io_delay
= delay2percent('blkio_delay_total')
37 swapin_delay
= delay2percent('swapin_delay_total')
38 read_bytes
= human_bandwidth(stats
['read_bytes'][1], duration
)
39 written_bytes
= stats
['write_bytes'][1] - stats
['cancelled_write_bytes'][1]
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
48 class IOTopUI(object):
51 (lambda p
: p
.pid
, False),
52 (lambda p
: p
.user
, False),
53 (lambda p
: p
.stats
['read_bytes'][1], True),
54 (lambda p
: p
.stats
['write_bytes'][1] -
55 p
.stats
['cancelled_write_bytes'][1], True),
56 (lambda p
: p
.stats
['swapin_delay_total'][1], 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
['blkio_delay_total'][1] or
60 int(not(not(p
.stats
['read_bytes'][1] or
61 p
.stats
['write_bytes'][1]))), 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
69 self
.sorting_reverse
= IOTopUI
.sorting_keys
[5][1]
70 if not self
.options
.batch
:
74 curses
.use_default_colors()
78 # This call can fail with misconfigured terminals, for example
79 # TERM=xterm-color. This is harmless
83 self
.height
, self
.width
= self
.win
.getmaxyx()
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:
98 if iterations
>= self
.options
.iterations
:
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
:
108 if not self
.options
.batch
:
111 key
= self
.win
.getch()
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
134 lambda: self
.reverse_sorting(),
136 lambda: self
.reverse_sorting(),
140 lambda: self
.adjust_sorting_key(-1),
142 lambda: self
.adjust_sorting_key(1),
144 lambda: self
.adjust_sorting_key(-len(IOTopUI
.sorting_keys
)),
146 lambda: self
.adjust_sorting_key(len(IOTopUI
.sorting_keys
))
149 action
= key_bindings
.get(key
, lambda: None)
154 stats
= human_stats(p
.stats
, 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 if self
.options
.batch
:
159 max_cmdline_length
= 4096
161 max_cmdline_length
= self
.width
- len(line
)
162 line
+= p
.get_cmdline()[:max_cmdline_length
]
165 def should_format(p
):
166 return not self
.options
.only
or p
.did_some_io()
168 processes
= self
.process_list
.processes
.values()
169 processes
= filter(should_format
, processes
)
170 key
= IOTopUI
.sorting_keys
[self
.sorting_key
][0]
171 processes
.sort(key
=key
, reverse
=self
.sorting_reverse
)
172 if not self
.options
.batch
:
173 del processes
[self
.height
- 2:]
174 return map(format
, processes
)
176 def refresh_display(self
, total_read
, total_write
, duration
):
177 summary
= 'Total DISK READ: %s | Total DISK WRITE: %s' % (
178 human_bandwidth(total_read
, duration
),
179 human_bandwidth(total_write
, duration
))
180 titles
= [' PID', ' USER', ' DISK READ', ' DISK WRITE',
181 ' SWAPIN', ' IO', ' COMMAND']
182 lines
= self
.get_data()
183 if self
.options
.batch
:
185 print ''.join(titles
)
190 self
.win
.addstr(summary
)
191 self
.win
.hline(1, 0, ord(' ') | curses
.A_REVERSE
, self
.width
)
192 for i
in xrange(len(titles
)):
193 attr
= curses
.A_REVERSE
195 if i
== self
.sorting_key
:
196 attr |
= curses
.A_BOLD
197 title
+= self
.sorting_reverse
and '>' or '<'
198 self
.win
.addstr(title
, attr
)
199 for i
in xrange(len(lines
)):
200 self
.win
.insstr(i
+ 2, 0, lines
[i
])
203 def run_iotop(win
, options
):
204 taskstats_connection
= TaskStatsNetlink(options
)
205 process_list
= ProcessList(taskstats_connection
, options
)
206 ui
= IOTopUI(win
, process_list
, options
)
213 USAGE
= '''%s [OPTIONS]
215 DISK READ and DISK WRITE are the block I/O bandwidth used during the sampling
216 period. SWAPIN and IO are the percentages of time the thread spent respectively
217 while swapping in and waiting on I/O more generally.
218 Controls: left and right arrows to change the sorting column, r to invert the
219 sorting order, o to toggle the --only option, q to quit, any other key to force
220 a refresh''' % sys
.argv
[0]
223 locale
.setlocale(locale
.LC_ALL
, '')
224 parser
= optparse
.OptionParser(usage
=USAGE
, version
='iotop ' + VERSION
)
225 parser
.add_option('-o', '--only', action
='store_true',
226 dest
='only', default
=False,
227 help='only show processes or threads actually doing I/O')
228 parser
.add_option('-b', '--batch', action
='store_true', dest
='batch',
229 help='non-interactive mode')
230 parser
.add_option('-n', '--iter', type='int', dest
='iterations',
232 help='number of iterations before ending [infinite]')
233 parser
.add_option('-d', '--delay', type='float', dest
='delay_seconds',
234 help='delay between iterations [1 second]',
235 metavar
='SEC', default
=1)
236 parser
.add_option('-p', '--pid', type='int', dest
='pids', action
='append',
237 help='processes to monitor [all]', metavar
='PID')
238 parser
.add_option('-u', '--user', type='str', dest
='users', action
='append',
239 help='users to monitor [all]', metavar
='USER')
240 parser
.add_option('-P', '--processes', action
='store_true',
242 help='only show processes, not all threads')
243 options
, args
= parser
.parse_args()
245 parser
.error('Unexpected arguments: ' + ' '.join(args
))
247 options
.pids
= options
.pids
or []
249 run_iotop(None, options
)
251 curses
.wrapper(run_iotop
, options
)