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):
29 res
= '%.2f %s/s' % ((float(bw
) / base
), UNITS
[i
])
32 def human_stats(stats
, duration
):
33 # Keep in sync with TaskStatsNetlink.members_offsets and
34 # IOTopUI.get_data(self)
35 def delay2percent(delay
): # delay in ns, duration in s
36 return '%.2f %%' % min(99.99, delay
/ (duration
* 10000000.0))
37 io_delay
= delay2percent(stats
.blkio_delay_total
)
38 swapin_delay
= delay2percent(stats
.swapin_delay_total
)
39 read_bytes
= human_bandwidth(stats
.read_bytes
, duration
)
40 written_bytes
= stats
.write_bytes
- stats
.cancelled_write_bytes
41 written_bytes
= max(0, written_bytes
)
42 write_bytes
= human_bandwidth(written_bytes
, duration
)
43 return io_delay
, swapin_delay
, read_bytes
, write_bytes
49 class IOTopUI(object):
52 (lambda p
: p
.pid
, False),
53 (lambda p
: p
.ioprio_sort_key(), False),
54 (lambda p
: p
.get_user(), False),
55 (lambda p
: p
.stats_delta
.read_bytes
, True),
56 (lambda p
: p
.stats_delta
.write_bytes
-
57 p
.stats_delta
.cancelled_write_bytes
, True),
58 (lambda p
: p
.stats_delta
.swapin_delay_total
, 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_delta
.blkio_delay_total
or
62 int(not(not(p
.stats_delta
.read_bytes
or
63 p
.stats_delta
.write_bytes
))), 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
71 self
.sorting_reverse
= IOTopUI
.sorting_keys
[self
.sorting_key
][1]
72 if not self
.options
.batch
:
76 curses
.use_default_colors()
80 # This call can fail with misconfigured terminals, for example
81 # TERM=xterm-color. This is harmless
85 self
.height
, self
.width
= self
.win
.getmaxyx()
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
= total
96 self
.refresh_display(total_read
, total_write
,
97 self
.process_list
.duration
)
98 if self
.options
.iterations
is not None:
100 if iterations
>= self
.options
.iterations
:
104 events
= poll
.poll(self
.options
.delay_seconds
* 1000.0)
105 except select
.error
, e
:
106 if e
.args
and e
.args
[0] == errno
.EINTR
:
110 if not self
.options
.batch
:
113 key
= self
.win
.getch()
116 def reverse_sorting(self
):
117 self
.sorting_reverse
= not self
.sorting_reverse
119 def adjust_sorting_key(self
, delta
):
120 orig_sorting_key
= self
.sorting_key
121 self
.sorting_key
+= delta
122 self
.sorting_key
= max(0, self
.sorting_key
)
123 self
.sorting_key
= min(len(IOTopUI
.sorting_keys
) - 1, self
.sorting_key
)
124 if orig_sorting_key
!= self
.sorting_key
:
125 self
.sorting_reverse
= IOTopUI
.sorting_keys
[self
.sorting_key
][1]
127 def handle_key(self
, key
):
128 def toggle_only_io():
129 self
.options
.only ^
= True
130 def toggle_processes():
131 self
.options
.processes ^
= True
132 self
.process_list
.refresh_processes()
139 lambda: self
.reverse_sorting(),
141 lambda: self
.reverse_sorting(),
151 lambda: self
.adjust_sorting_key(-1),
153 lambda: self
.adjust_sorting_key(1),
155 lambda: self
.adjust_sorting_key(-len(IOTopUI
.sorting_keys
)),
157 lambda: self
.adjust_sorting_key(len(IOTopUI
.sorting_keys
))
160 action
= key_bindings
.get(key
, lambda: None)
165 stats
= human_stats(p
.stats_delta
, self
.process_list
.duration
)
166 io_delay
, swapin_delay
, read_bytes
, write_bytes
= stats
167 line
= '%5d %4s %-8s %11s %11s %7s %7s ' % (
168 p
.pid
, p
.get_ioprio(), p
.get_user()[:8], read_bytes
,
169 write_bytes
, swapin_delay
, io_delay
)
170 line
+= p
.get_cmdline()
171 if not self
.options
.batch
:
172 line
= line
[:self
.width
- 1]
175 def should_format(p
):
176 return not self
.options
.only
or p
.did_some_io()
178 processes
= filter(should_format
, self
.process_list
.processes
.values())
179 key
= IOTopUI
.sorting_keys
[self
.sorting_key
][0]
180 processes
.sort(key
=key
, reverse
=self
.sorting_reverse
)
181 if not self
.options
.batch
:
182 del processes
[self
.height
- 2:]
183 return map(format
, processes
)
185 def refresh_display(self
, total_read
, total_write
, duration
):
186 summary
= 'Total DISK READ: %s | Total DISK WRITE: %s' % (
187 human_bandwidth(total_read
, duration
),
188 human_bandwidth(total_write
, duration
))
189 if self
.options
.processes
:
193 titles
= [pid
, ' PRIO', ' USER', ' DISK READ', ' DISK WRITE',
194 ' SWAPIN', ' IO', ' COMMAND']
195 lines
= self
.get_data()
196 if self
.options
.batch
:
198 print ''.join(titles
)
204 self
.win
.addstr(summary
)
205 self
.win
.hline(1, 0, ord(' ') | curses
.A_REVERSE
, self
.width
)
206 for i
in xrange(len(titles
)):
207 attr
= curses
.A_REVERSE
209 if i
== self
.sorting_key
:
210 attr |
= curses
.A_BOLD
211 title
+= self
.sorting_reverse
and '>' or '<'
212 self
.win
.addstr(title
, attr
)
213 for i
in xrange(len(lines
)):
215 self
.win
.addstr(i
+ 2, 0, lines
[i
].encode('utf-8'))
217 exc_type
, value
, traceback
= sys
.exc_info()
218 value
= '%s win:%s i:%d line:%s' % \
219 (value
, self
.win
.getmaxyx(), i
, lines
[i
])
220 raise exc_type
, value
.encode('string_escape'), traceback
223 def run_iotop_window(win
, options
):
224 taskstats_connection
= TaskStatsNetlink(options
)
225 process_list
= ProcessList(taskstats_connection
, options
)
226 ui
= IOTopUI(win
, process_list
, options
)
229 def run_iotop(options
):
231 return run_iotop_window(None, options
)
233 return curses
.wrapper(run_iotop_window
, options
)
239 def _profile(continuation
):
240 prof_file
= 'iotop.prof'
244 print 'Profiling using cProfile'
245 cProfile
.runctx('continuation()', globals(), locals(), prof_file
)
246 stats
= pstats
.Stats(prof_file
)
250 prof
= hotshot
.Profile(prof_file
, lineevents
=1)
251 print 'Profiling using hotshot'
252 prof
.runcall(continuation
)
254 stats
= hotshot
.stats
.load(prof_file
)
256 stats
.sort_stats('time', 'calls')
257 stats
.print_stats(50)
258 stats
.print_callees(50)
265 USAGE
= '''%s [OPTIONS]
267 DISK READ and DISK WRITE are the block I/O bandwidth used during the sampling
268 period. SWAPIN and IO are the percentages of time the thread spent respectively
269 while swapping in and waiting on I/O more generally. PRIO is the I/O priority at
270 which the thread is running (set using the ionice command).
271 Controls: left and right arrows to change the sorting column, r to invert the
272 sorting order, o to toggle the --only option, p to toggle the --processes
273 option, q to quit, any other key to force a refresh.''' % sys
.argv
[0]
276 locale
.setlocale(locale
.LC_ALL
, '')
277 parser
= optparse
.OptionParser(usage
=USAGE
, version
='iotop ' + VERSION
)
278 parser
.add_option('-o', '--only', action
='store_true',
279 dest
='only', default
=False,
280 help='only show processes or threads actually doing I/O')
281 parser
.add_option('-b', '--batch', action
='store_true', dest
='batch',
282 help='non-interactive mode')
283 parser
.add_option('-n', '--iter', type='int', dest
='iterations',
285 help='number of iterations before ending [infinite]')
286 parser
.add_option('-d', '--delay', type='float', dest
='delay_seconds',
287 help='delay between iterations [1 second]',
288 metavar
='SEC', default
=1)
289 parser
.add_option('-p', '--pid', type='int', dest
='pids', action
='append',
290 help='processes/threads to monitor [all]', metavar
='PID')
291 parser
.add_option('-u', '--user', type='str', dest
='users', action
='append',
292 help='users to monitor [all]', metavar
='USER')
293 parser
.add_option('-P', '--processes', action
='store_true',
294 dest
='processes', default
=False,
295 help='only show processes, not all threads')
296 parser
.add_option('--profile', action
='store_true', dest
='profile',
297 default
=False, help=optparse
.SUPPRESS_HELP
)
299 options
, args
= parser
.parse_args()
301 parser
.error('Unexpected arguments: ' + ' '.join(args
))
303 options
.pids
= options
.pids
or []
305 main_loop
= lambda: run_iotop(options
)
308 def safe_main_loop():
313 _profile(safe_main_loop
)