12 from iotop
.data
import find_uids
, TaskStatsNetlink
, ProcessList
, Stats
13 from iotop
.version
import VERSION
16 # Utility functions for the UI
19 UNITS
= ['B', 'K', 'M', 'G', 'T', 'P', 'E']
22 for i
in xrange(len(UNITS
) - 1, 0, -1):
29 return '%.2f %s' % ((float(size
) / base
), UNITS
[i
])
31 def format_size(options
, bytes
):
33 return '%.2f K' % (bytes
/ 1024.0)
34 return human_size(bytes
)
36 def format_bandwidth(options
, size
, duration
):
37 return format_size(options
, size
and float(size
) / duration
) + '/s'
39 def format_stats(options
, process
, duration
):
40 # Keep in sync with TaskStatsNetlink.members_offsets and
41 # IOTopUI.get_data(self)
42 def delay2percent(delay
): # delay in ns, duration in s
43 return '%.2f %%' % min(99.99, delay
/ (duration
* 10000000.0))
44 if options
.accumulated
:
45 stats
= process
.stats_accum
46 display_format
= lambda size
, duration
: format_size(options
, size
)
47 duration
= time
.time() - process
.stats_accum_timestamp
49 stats
= process
.stats_delta
50 display_format
= lambda size
, duration
: format_bandwidth(
51 options
, size
, duration
)
52 io_delay
= delay2percent(stats
.blkio_delay_total
)
53 swapin_delay
= delay2percent(stats
.swapin_delay_total
)
54 read_bytes
= display_format(stats
.read_bytes
, duration
)
55 written_bytes
= stats
.write_bytes
- stats
.cancelled_write_bytes
56 written_bytes
= max(0, written_bytes
)
57 write_bytes
= display_format(written_bytes
, duration
)
58 return io_delay
, swapin_delay
, read_bytes
, write_bytes
64 class IOTopUI(object):
67 (lambda p
, s
: p
.pid
, False),
68 (lambda p
, s
: p
.ioprio_sort_key(), False),
69 (lambda p
, s
: p
.get_user(), False),
70 (lambda p
, s
: s
.read_bytes
, True),
71 (lambda p
, s
: s
.write_bytes
- s
.cancelled_write_bytes
, True),
72 (lambda p
, s
: s
.swapin_delay_total
, True),
73 # The default sorting (by I/O % time) should show processes doing
74 # only writes, without waiting on them
75 (lambda p
, s
: s
.blkio_delay_total
or
76 int(not(not(s
.read_bytes
or s
.write_bytes
))), True),
77 (lambda p
, s
: p
.get_cmdline(), False),
80 def __init__(self
, win
, process_list
, options
):
81 self
.process_list
= process_list
82 self
.options
= options
84 self
.sorting_reverse
= IOTopUI
.sorting_keys
[self
.sorting_key
][1]
85 if not self
.options
.batch
:
89 curses
.use_default_colors()
93 # This call can fail with misconfigured terminals, for example
94 # TERM=xterm-color. This is harmless
98 self
.height
, self
.width
= self
.win
.getmaxyx()
103 if not self
.options
.batch
:
104 poll
.register(sys
.stdin
.fileno(), select
.POLLIN|select
.POLLPRI
)
105 while self
.options
.iterations
is None or \
106 iterations
< self
.options
.iterations
:
107 total
= self
.process_list
.refresh_processes()
108 total_read
, total_write
= total
109 self
.refresh_display(iterations
== 0, total_read
, total_write
,
110 self
.process_list
.duration
)
111 if self
.options
.iterations
is not None:
113 if iterations
>= self
.options
.iterations
:
115 elif iterations
== 0:
119 events
= poll
.poll(self
.options
.delay_seconds
* 1000.0)
120 except select
.error
, e
:
121 if e
.args
and e
.args
[0] == errno
.EINTR
:
125 if not self
.options
.batch
:
128 key
= self
.win
.getch()
131 def reverse_sorting(self
):
132 self
.sorting_reverse
= not self
.sorting_reverse
134 def adjust_sorting_key(self
, delta
):
135 orig_sorting_key
= self
.sorting_key
136 self
.sorting_key
+= delta
137 self
.sorting_key
= max(0, self
.sorting_key
)
138 self
.sorting_key
= min(len(IOTopUI
.sorting_keys
) - 1, self
.sorting_key
)
139 if orig_sorting_key
!= self
.sorting_key
:
140 self
.sorting_reverse
= IOTopUI
.sorting_keys
[self
.sorting_key
][1]
142 def handle_key(self
, key
):
143 def toggle_accumulated():
144 self
.options
.accumulated ^
= True
145 self
.process_list
.clear()
146 def toggle_only_io():
147 self
.options
.only ^
= True
148 def toggle_processes():
149 self
.options
.processes ^
= True
150 self
.process_list
.clear()
151 self
.process_list
.refresh_processes()
158 lambda: self
.reverse_sorting(),
160 lambda: self
.reverse_sorting(),
174 lambda: self
.adjust_sorting_key(-1),
176 lambda: self
.adjust_sorting_key(1),
178 lambda: self
.adjust_sorting_key(-len(IOTopUI
.sorting_keys
)),
180 lambda: self
.adjust_sorting_key(len(IOTopUI
.sorting_keys
))
183 action
= key_bindings
.get(key
, lambda: None)
188 stats
= format_stats(self
.options
, p
, self
.process_list
.duration
)
189 io_delay
, swapin_delay
, read_bytes
, write_bytes
= stats
190 line
= '%5d %4s %-8s %11s %11s %7s %7s ' % (
191 p
.pid
, p
.get_ioprio(), p
.get_user()[:8], read_bytes
,
192 write_bytes
, swapin_delay
, io_delay
)
193 cmdline
= p
.get_cmdline()
194 if not self
.options
.batch
:
195 remaining_length
= self
.width
- len(line
)
196 if 2 < remaining_length
< len(cmdline
):
197 len1
= (remaining_length
- 1) // 2
198 offset2
= -(remaining_length
- len1
- 1)
199 cmdline
= cmdline
[:len1
] + '~' + cmdline
[offset2
:]
201 if not self
.options
.batch
:
202 line
= line
[:self
.width
]
205 def should_format(p
):
206 return not self
.options
.only
or \
207 p
.did_some_io(self
.options
.accumulated
)
209 processes
= filter(should_format
, self
.process_list
.processes
.values())
210 key
= IOTopUI
.sorting_keys
[self
.sorting_key
][0]
211 if self
.options
.accumulated
:
212 stats_lambda
= lambda p
: p
.stats_accum
214 stats_lambda
= lambda p
: p
.stats_delta
215 processes
.sort(key
=lambda p
: key(p
, stats_lambda(p
)),
216 reverse
=self
.sorting_reverse
)
217 if not self
.options
.batch
:
218 del processes
[self
.height
- 2:]
219 return map(format
, processes
)
221 def refresh_display(self
, first_time
, total_read
, total_write
, duration
):
222 summary
= 'Total DISK READ: %s | Total DISK WRITE: %s' % (
223 format_bandwidth(self
.options
, total_read
, duration
),
224 format_bandwidth(self
.options
, total_write
, duration
))
225 if self
.options
.processes
:
229 titles
= [pid
, ' PRIO', ' USER', ' DISK READ', ' DISK WRITE',
230 ' SWAPIN', ' IO', ' COMMAND']
231 lines
= self
.get_data()
232 if self
.options
.time
:
233 titles
= [' TIME'] + titles
234 current_time
= time
.strftime('%H:%M:%S ')
235 lines
= [current_time
+ l
for l
in lines
]
236 if self
.options
.batch
:
237 if self
.options
.quiet
<= 2:
239 if self
.options
.quiet
<= int(first_time
):
240 print ''.join(titles
)
246 self
.win
.addstr(summary
[:self
.width
])
247 self
.win
.hline(1, 0, ord(' ') | curses
.A_REVERSE
, self
.width
)
248 remaining_cols
= self
.width
249 for i
in xrange(len(titles
)):
250 attr
= curses
.A_REVERSE
252 if i
== self
.sorting_key
:
254 if i
== self
.sorting_key
:
255 attr |
= curses
.A_BOLD
256 title
+= self
.sorting_reverse
and '>' or '<'
257 title
= title
[:remaining_cols
]
258 remaining_cols
-= len(title
)
259 self
.win
.addstr(title
, attr
)
260 if Stats
.has_blkio_delay_total
:
263 status_msg
= ('CONFIG_TASK_DELAY_ACCT not enabled in kernel, '
264 'cannot determine IO %')
265 num_lines
= min(len(lines
), self
.height
- 2 - int(bool(status_msg
)))
266 for i
in xrange(num_lines
):
268 self
.win
.insstr(i
+ 2, 0, lines
[i
].encode('utf-8'))
270 exc_type
, value
, traceback
= sys
.exc_info()
271 value
= '%s win:%s i:%d line:%s' % \
272 (value
, self
.win
.getmaxyx(), i
, lines
[i
])
273 value
= str(value
).encode('string_escape')
274 raise exc_type
, value
, traceback
276 self
.win
.insstr(self
.height
- 1, 0, status_msg
, curses
.A_BOLD
)
279 def run_iotop_window(win
, options
):
280 taskstats_connection
= TaskStatsNetlink(options
)
281 process_list
= ProcessList(taskstats_connection
, options
)
282 ui
= IOTopUI(win
, process_list
, options
)
285 def run_iotop(options
):
287 return run_iotop_window(None, options
)
289 return curses
.wrapper(run_iotop_window
, options
)
295 def _profile(continuation
):
296 prof_file
= 'iotop.prof'
300 print 'Profiling using cProfile'
301 cProfile
.runctx('continuation()', globals(), locals(), prof_file
)
302 stats
= pstats
.Stats(prof_file
)
306 prof
= hotshot
.Profile(prof_file
, lineevents
=1)
307 print 'Profiling using hotshot'
308 prof
.runcall(continuation
)
310 stats
= hotshot
.stats
.load(prof_file
)
312 stats
.sort_stats('time', 'calls')
313 stats
.print_stats(50)
314 stats
.print_callees(50)
321 USAGE
= '''%s [OPTIONS]
323 DISK READ and DISK WRITE are the block I/O bandwidth used during the sampling
324 period. SWAPIN and IO are the percentages of time the thread spent respectively
325 while swapping in and waiting on I/O more generally. PRIO is the I/O priority at
326 which the thread is running (set using the ionice command).
328 Controls: left and right arrows to change the sorting column, r to invert the
329 sorting order, o to toggle the --only option, p to toggle the --processes
330 option, a to toggle the --accumulated option, q to quit, any other key to force
331 a refresh.''' % sys
.argv
[0]
334 locale
.setlocale(locale
.LC_ALL
, '')
335 parser
= optparse
.OptionParser(usage
=USAGE
, version
='iotop ' + VERSION
)
336 parser
.add_option('-o', '--only', action
='store_true',
337 dest
='only', default
=False,
338 help='only show processes or threads actually doing I/O')
339 parser
.add_option('-b', '--batch', action
='store_true', dest
='batch',
340 help='non-interactive mode')
341 parser
.add_option('-n', '--iter', type='int', dest
='iterations',
343 help='number of iterations before ending [infinite]')
344 parser
.add_option('-d', '--delay', type='float', dest
='delay_seconds',
345 help='delay between iterations [1 second]',
346 metavar
='SEC', default
=1)
347 parser
.add_option('-p', '--pid', type='int', dest
='pids', action
='append',
348 help='processes/threads to monitor [all]', metavar
='PID')
349 parser
.add_option('-u', '--user', type='str', dest
='users', action
='append',
350 help='users to monitor [all]', metavar
='USER')
351 parser
.add_option('-P', '--processes', action
='store_true',
352 dest
='processes', default
=False,
353 help='only show processes, not all threads')
354 parser
.add_option('-a', '--accumulated', action
='store_true',
355 dest
='accumulated', default
=False,
356 help='show accumulated I/O instead of bandwidth')
357 parser
.add_option('-k', '--kilobytes', action
='store_true',
358 dest
='kilobytes', default
=False,
359 help='use kilobytes instead of a human friendly unit')
360 parser
.add_option('-t', '--time', action
='store_true', dest
='time',
361 help='add a timestamp on each line (implies --batch)')
362 parser
.add_option('-q', '--quiet', action
='count', dest
='quiet',
363 help='suppress some lines of header (implies --batch)')
364 parser
.add_option('--profile', action
='store_true', dest
='profile',
365 default
=False, help=optparse
.SUPPRESS_HELP
)
367 options
, args
= parser
.parse_args()
369 parser
.error('Unexpected arguments: ' + ' '.join(args
))
371 options
.pids
= options
.pids
or []
372 options
.batch
= options
.batch
or options
.time
or options
.quiet
374 main_loop
= lambda: run_iotop(options
)
377 def safe_main_loop():
382 _profile(safe_main_loop
)