|
Packit |
88fe6f |
# This program is free software; you can redistribute it and/or modify
|
|
Packit |
88fe6f |
# it under the terms of the GNU General Public License as published by
|
|
Packit |
88fe6f |
# the Free Software Foundation; either version 2 of the License, or
|
|
Packit |
88fe6f |
# (at your option) any later version.
|
|
Packit |
88fe6f |
#
|
|
Packit |
88fe6f |
# This program is distributed in the hope that it will be useful,
|
|
Packit |
88fe6f |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit |
88fe6f |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
Packit |
88fe6f |
# GNU Library General Public License for more details.
|
|
Packit |
88fe6f |
#
|
|
Packit |
88fe6f |
# You should have received a copy of the GNU General Public License
|
|
Packit |
88fe6f |
# along with this program; if not, write to the Free Software
|
|
Packit |
88fe6f |
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
Packit |
88fe6f |
#
|
|
Packit |
88fe6f |
# See the COPYING file for license information.
|
|
Packit |
88fe6f |
#
|
|
Packit |
88fe6f |
# Copyright (c) 2007 Guillaume Chazarain <guichaz@gmail.com>
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
# Allow printing with same syntax in Python 2/3
|
|
Packit |
88fe6f |
from __future__ import print_function
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
import curses
|
|
Packit |
88fe6f |
import errno
|
|
Packit |
88fe6f |
import locale
|
|
Packit |
88fe6f |
import math
|
|
Packit |
88fe6f |
import optparse
|
|
Packit |
88fe6f |
import os
|
|
Packit |
88fe6f |
import select
|
|
Packit |
88fe6f |
import signal
|
|
Packit |
88fe6f |
import sys
|
|
Packit |
88fe6f |
import time
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
from iotop.data import find_uids, TaskStatsNetlink, ProcessList, Stats
|
|
Packit |
88fe6f |
from iotop.data import ThreadInfo
|
|
Packit |
88fe6f |
from iotop.version import VERSION
|
|
Packit |
88fe6f |
from iotop import ioprio
|
|
Packit |
88fe6f |
from iotop.ioprio import IoprioSetError
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
#
|
|
Packit |
88fe6f |
# Utility functions for the UI
|
|
Packit |
88fe6f |
#
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
UNITS = ['B', 'K', 'M', 'G', 'T', 'P', 'E']
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def human_size(size):
|
|
Packit |
88fe6f |
if size > 0:
|
|
Packit |
88fe6f |
sign = ''
|
|
Packit |
88fe6f |
elif size < 0:
|
|
Packit |
88fe6f |
sign = '-'
|
|
Packit |
88fe6f |
size = -size
|
|
Packit |
88fe6f |
else:
|
|
Packit |
88fe6f |
return '0.00 B'
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
expo = int(math.log(size / 2, 2) / 10)
|
|
Packit |
88fe6f |
return '%s%.2f %s' % (sign, (float(size) / (1 << (10 * expo))), UNITS[expo])
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def format_size(options, bytes):
|
|
Packit |
88fe6f |
if options.kilobytes:
|
|
Packit |
88fe6f |
return '%.2f K' % (bytes / 1024.0)
|
|
Packit |
88fe6f |
return human_size(bytes)
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def format_bandwidth(options, size, duration):
|
|
Packit |
88fe6f |
return format_size(options, size and float(size) / duration) + '/s'
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def format_stats(options, process, duration):
|
|
Packit |
88fe6f |
# Keep in sync with TaskStatsNetlink.members_offsets and
|
|
Packit |
88fe6f |
# IOTopUI.get_data(self)
|
|
Packit |
88fe6f |
def delay2percent(delay): # delay in ns, duration in s
|
|
Packit |
88fe6f |
return '%.2f %%' % min(99.99, delay / (duration * 10000000.0))
|
|
Packit |
88fe6f |
if options.accumulated:
|
|
Packit |
88fe6f |
stats = process.stats_accum
|
|
Packit |
88fe6f |
display_format = lambda size, duration: format_size(options, size)
|
|
Packit |
88fe6f |
duration = time.time() - process.stats_accum_timestamp
|
|
Packit |
88fe6f |
else:
|
|
Packit |
88fe6f |
stats = process.stats_delta
|
|
Packit |
88fe6f |
display_format = lambda size, duration: format_bandwidth(
|
|
Packit |
88fe6f |
options, size, duration)
|
|
Packit |
88fe6f |
io_delay = delay2percent(stats.blkio_delay_total)
|
|
Packit |
88fe6f |
swapin_delay = delay2percent(stats.swapin_delay_total)
|
|
Packit |
88fe6f |
read_bytes = display_format(stats.read_bytes, duration)
|
|
Packit |
88fe6f |
written_bytes = stats.write_bytes - stats.cancelled_write_bytes
|
|
Packit |
88fe6f |
written_bytes = max(0, written_bytes)
|
|
Packit |
88fe6f |
write_bytes = display_format(written_bytes, duration)
|
|
Packit |
88fe6f |
return io_delay, swapin_delay, read_bytes, write_bytes
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def get_max_pid_width():
|
|
Packit |
88fe6f |
try:
|
|
Packit |
88fe6f |
return len(open('/proc/sys/kernel/pid_max').read().strip())
|
|
Packit |
88fe6f |
except Exception as e:
|
|
Packit |
88fe6f |
print(e)
|
|
Packit |
88fe6f |
# Reasonable default in case something fails
|
|
Packit |
88fe6f |
return 5
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
MAX_PID_WIDTH = get_max_pid_width()
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
#
|
|
Packit |
88fe6f |
# UI Exceptions
|
|
Packit |
88fe6f |
#
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
class CancelInput(Exception): pass
|
|
Packit |
88fe6f |
class InvalidInt(Exception): pass
|
|
Packit |
88fe6f |
class InvalidPid(Exception): pass
|
|
Packit |
88fe6f |
class InvalidTid(Exception): pass
|
|
Packit |
88fe6f |
class InvalidIoprioData(Exception): pass
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
#
|
|
Packit |
88fe6f |
# The UI
|
|
Packit |
88fe6f |
#
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
class IOTopUI(object):
|
|
Packit |
88fe6f |
# key, reverse
|
|
Packit |
88fe6f |
sorting_keys = [
|
|
Packit |
88fe6f |
(lambda p, s: p.pid, False),
|
|
Packit |
88fe6f |
(lambda p, s: p.ioprio_sort_key(), False),
|
|
Packit |
88fe6f |
(lambda p, s: p.get_user(), False),
|
|
Packit |
88fe6f |
(lambda p, s: s.read_bytes, True),
|
|
Packit |
88fe6f |
(lambda p, s: s.write_bytes - s.cancelled_write_bytes, True),
|
|
Packit |
88fe6f |
(lambda p, s: s.swapin_delay_total, True),
|
|
Packit |
88fe6f |
# The default sorting (by I/O % time) should show processes doing
|
|
Packit |
88fe6f |
# only writes, without waiting on them
|
|
Packit |
88fe6f |
(lambda p, s: s.blkio_delay_total or
|
|
Packit |
88fe6f |
int(not(not(s.read_bytes or s.write_bytes))), True),
|
|
Packit |
88fe6f |
(lambda p, s: p.get_cmdline(), False),
|
|
Packit |
88fe6f |
]
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def __init__(self, win, process_list, options):
|
|
Packit |
88fe6f |
self.process_list = process_list
|
|
Packit |
88fe6f |
self.options = options
|
|
Packit |
88fe6f |
self.sorting_key = 6
|
|
Packit |
88fe6f |
self.sorting_reverse = IOTopUI.sorting_keys[self.sorting_key][1]
|
|
Packit |
88fe6f |
if not self.options.batch:
|
|
Packit |
88fe6f |
self.win = win
|
|
Packit |
88fe6f |
self.resize()
|
|
Packit |
88fe6f |
try:
|
|
Packit |
88fe6f |
curses.use_default_colors()
|
|
Packit |
88fe6f |
curses.start_color()
|
|
Packit |
88fe6f |
curses.curs_set(0)
|
|
Packit |
88fe6f |
except curses.error:
|
|
Packit |
88fe6f |
# This call can fail with misconfigured terminals, for example
|
|
Packit |
88fe6f |
# TERM=xterm-color. This is harmless
|
|
Packit |
88fe6f |
pass
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def resize(self):
|
|
Packit |
88fe6f |
self.height, self.width = self.win.getmaxyx()
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def run(self):
|
|
Packit |
88fe6f |
iterations = 0
|
|
Packit |
88fe6f |
poll = select.poll()
|
|
Packit |
88fe6f |
if not self.options.batch:
|
|
Packit |
88fe6f |
poll.register(sys.stdin.fileno(), select.POLLIN|select.POLLPRI)
|
|
Packit |
88fe6f |
while self.options.iterations is None or \
|
|
Packit |
88fe6f |
iterations < self.options.iterations:
|
|
Packit |
88fe6f |
total, actual = self.process_list.refresh_processes()
|
|
Packit |
88fe6f |
self.refresh_display(iterations == 0, total, actual,
|
|
Packit |
88fe6f |
self.process_list.duration)
|
|
Packit |
88fe6f |
if self.options.iterations is not None:
|
|
Packit |
88fe6f |
iterations += 1
|
|
Packit |
88fe6f |
if iterations >= self.options.iterations:
|
|
Packit |
88fe6f |
break
|
|
Packit |
88fe6f |
elif iterations == 0:
|
|
Packit |
88fe6f |
iterations = 1
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
try:
|
|
Packit |
88fe6f |
events = poll.poll(self.options.delay_seconds * 1000.0)
|
|
Packit |
88fe6f |
except select.error as e:
|
|
Packit |
88fe6f |
if e.args and e.args[0] == errno.EINTR:
|
|
Packit |
88fe6f |
events = []
|
|
Packit |
88fe6f |
else:
|
|
Packit |
88fe6f |
raise
|
|
Packit |
88fe6f |
for (fd, event) in events:
|
|
Packit |
88fe6f |
if event & (select.POLLERR | select.POLLHUP):
|
|
Packit |
88fe6f |
sys.exit(1)
|
|
Packit |
88fe6f |
if not self.options.batch:
|
|
Packit |
88fe6f |
self.resize()
|
|
Packit |
88fe6f |
if events:
|
|
Packit |
88fe6f |
key = self.win.getch()
|
|
Packit |
88fe6f |
self.handle_key(key)
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def reverse_sorting(self):
|
|
Packit |
88fe6f |
self.sorting_reverse = not self.sorting_reverse
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def adjust_sorting_key(self, delta):
|
|
Packit |
88fe6f |
orig_sorting_key = self.sorting_key
|
|
Packit |
88fe6f |
self.sorting_key += delta
|
|
Packit |
88fe6f |
self.sorting_key = max(0, self.sorting_key)
|
|
Packit |
88fe6f |
self.sorting_key = min(len(IOTopUI.sorting_keys) - 1, self.sorting_key)
|
|
Packit |
88fe6f |
if orig_sorting_key != self.sorting_key:
|
|
Packit |
88fe6f |
self.sorting_reverse = IOTopUI.sorting_keys[self.sorting_key][1]
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
# I wonder if switching to urwid for the display would be better here
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def prompt_str(self, prompt, default=None, empty_is_cancel=True):
|
|
Packit |
88fe6f |
self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width)
|
|
Packit |
88fe6f |
self.win.addstr(1, 0, prompt, curses.A_BOLD)
|
|
Packit |
88fe6f |
self.win.refresh()
|
|
Packit |
88fe6f |
curses.echo()
|
|
Packit |
88fe6f |
curses.curs_set(1)
|
|
Packit |
88fe6f |
inp = self.win.getstr(1, len(prompt))
|
|
Packit |
88fe6f |
curses.curs_set(0)
|
|
Packit |
88fe6f |
curses.noecho()
|
|
Packit |
88fe6f |
if inp not in (None, ''):
|
|
Packit |
88fe6f |
return inp
|
|
Packit |
88fe6f |
if empty_is_cancel:
|
|
Packit |
88fe6f |
raise CancelInput()
|
|
Packit |
88fe6f |
return default
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def prompt_int(self, prompt, default = None, empty_is_cancel = True):
|
|
Packit |
88fe6f |
inp = self.prompt_str(prompt, default, empty_is_cancel)
|
|
Packit |
88fe6f |
try:
|
|
Packit |
88fe6f |
return int(inp)
|
|
Packit |
88fe6f |
except ValueError:
|
|
Packit |
88fe6f |
raise InvalidInt()
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def prompt_pid(self):
|
|
Packit |
88fe6f |
try:
|
|
Packit |
88fe6f |
return self.prompt_int('PID to ionice: ')
|
|
Packit |
88fe6f |
except InvalidInt:
|
|
Packit |
88fe6f |
raise InvalidPid()
|
|
Packit |
88fe6f |
except CancelInput:
|
|
Packit |
88fe6f |
raise
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def prompt_tid(self):
|
|
Packit |
88fe6f |
try:
|
|
Packit |
88fe6f |
return self.prompt_int('TID to ionice: ')
|
|
Packit |
88fe6f |
except InvalidInt:
|
|
Packit |
88fe6f |
raise InvalidTid()
|
|
Packit |
88fe6f |
except CancelInput:
|
|
Packit |
88fe6f |
raise
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def prompt_data(self, ioprio_data):
|
|
Packit |
88fe6f |
try:
|
|
Packit |
88fe6f |
if ioprio_data is not None:
|
|
Packit |
88fe6f |
inp = self.prompt_int('I/O priority data (0-7, currently %s): '
|
|
Packit |
88fe6f |
% ioprio_data, ioprio_data, False)
|
|
Packit |
88fe6f |
else:
|
|
Packit |
88fe6f |
inp = self.prompt_int('I/O priority data (0-7): ', None, False)
|
|
Packit |
88fe6f |
except InvalidInt:
|
|
Packit |
88fe6f |
raise InvalidIoprioData()
|
|
Packit |
88fe6f |
if inp < 0 or inp > 7:
|
|
Packit |
88fe6f |
raise InvalidIoprioData()
|
|
Packit |
88fe6f |
return inp
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def prompt_set(self, prompt, display_list, ret_list, selected):
|
|
Packit |
88fe6f |
try:
|
|
Packit |
88fe6f |
selected = ret_list.index(selected)
|
|
Packit |
88fe6f |
except ValueError:
|
|
Packit |
88fe6f |
selected = -1
|
|
Packit |
88fe6f |
set_len = len(display_list) - 1
|
|
Packit |
88fe6f |
while True:
|
|
Packit |
88fe6f |
self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width)
|
|
Packit |
88fe6f |
self.win.insstr(1, 0, prompt, curses.A_BOLD)
|
|
Packit |
88fe6f |
offset = len(prompt)
|
|
Packit |
88fe6f |
for i, item in enumerate(display_list):
|
|
Packit |
88fe6f |
display = ' %s ' % item
|
|
Packit |
88fe6f |
if i is selected:
|
|
Packit |
88fe6f |
attr = curses.A_REVERSE
|
|
Packit |
88fe6f |
else:
|
|
Packit |
88fe6f |
attr = curses.A_NORMAL
|
|
Packit |
88fe6f |
self.win.insstr(1, offset, display, attr)
|
|
Packit |
88fe6f |
offset += len(display)
|
|
Packit |
88fe6f |
while True:
|
|
Packit |
88fe6f |
key = self.win.getch()
|
|
Packit |
88fe6f |
if key in (curses.KEY_LEFT, ord('l')) and selected > 0:
|
|
Packit |
88fe6f |
selected -= 1
|
|
Packit |
88fe6f |
break
|
|
Packit |
88fe6f |
elif key in (curses.KEY_RIGHT, ord('r')) and selected < set_len:
|
|
Packit |
88fe6f |
selected += 1
|
|
Packit |
88fe6f |
break
|
|
Packit |
88fe6f |
elif key in (curses.KEY_ENTER, ord('\n'), ord('\r')):
|
|
Packit |
88fe6f |
return ret_list[selected]
|
|
Packit |
88fe6f |
elif key in (27, curses.KEY_CANCEL, curses.KEY_CLOSE,
|
|
Packit |
88fe6f |
curses.KEY_EXIT, ord('q'), ord('Q')):
|
|
Packit |
88fe6f |
raise CancelInput()
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def prompt_class(self, ioprio_class=None):
|
|
Packit |
88fe6f |
prompt = 'I/O priority class: '
|
|
Packit |
88fe6f |
classes_prompt = ['Real-time', 'Best-effort', 'Idle']
|
|
Packit |
88fe6f |
classes_ret = ['rt', 'be', 'idle']
|
|
Packit |
88fe6f |
if ioprio_class is None:
|
|
Packit |
88fe6f |
ioprio_class = 2
|
|
Packit |
88fe6f |
inp = self.prompt_set(prompt, classes_prompt, classes_ret, ioprio_class)
|
|
Packit |
88fe6f |
return inp
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def prompt_error(self, error = 'Error!'):
|
|
Packit |
88fe6f |
self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width)
|
|
Packit |
88fe6f |
self.win.insstr(1, 0, ' %s ' % error, curses.A_REVERSE)
|
|
Packit |
88fe6f |
self.win.refresh()
|
|
Packit |
88fe6f |
time.sleep(1)
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def prompt_clear(self):
|
|
Packit |
88fe6f |
self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width)
|
|
Packit |
88fe6f |
self.win.refresh()
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def handle_key(self, key):
|
|
Packit |
88fe6f |
def toggle_accumulated():
|
|
Packit |
88fe6f |
self.options.accumulated ^= True
|
|
Packit |
88fe6f |
def toggle_only_io():
|
|
Packit |
88fe6f |
self.options.only ^= True
|
|
Packit |
88fe6f |
def toggle_processes():
|
|
Packit |
88fe6f |
self.options.processes ^= True
|
|
Packit |
88fe6f |
self.process_list.clear()
|
|
Packit |
88fe6f |
self.process_list.refresh_processes()
|
|
Packit |
88fe6f |
def ionice():
|
|
Packit |
88fe6f |
try:
|
|
Packit |
88fe6f |
if self.options.processes:
|
|
Packit |
88fe6f |
pid = self.prompt_pid()
|
|
Packit |
88fe6f |
exec_unit = self.process_list.get_process(pid)
|
|
Packit |
88fe6f |
else:
|
|
Packit |
88fe6f |
tid = self.prompt_tid()
|
|
Packit |
88fe6f |
exec_unit = ThreadInfo(tid,
|
|
Packit |
88fe6f |
self.process_list.taskstats_connection)
|
|
Packit |
88fe6f |
ioprio_value = exec_unit.get_ioprio()
|
|
Packit |
88fe6f |
(ioprio_class, ioprio_data) = \
|
|
Packit |
88fe6f |
ioprio.to_class_and_data(ioprio_value)
|
|
Packit |
88fe6f |
ioprio_class = self.prompt_class(ioprio_class)
|
|
Packit |
88fe6f |
if ioprio_class == 'idle':
|
|
Packit |
88fe6f |
ioprio_data = 0
|
|
Packit |
88fe6f |
else:
|
|
Packit |
88fe6f |
ioprio_data = self.prompt_data(ioprio_data)
|
|
Packit |
88fe6f |
exec_unit.set_ioprio(ioprio_class, ioprio_data)
|
|
Packit |
88fe6f |
self.process_list.clear()
|
|
Packit |
88fe6f |
self.process_list.refresh_processes()
|
|
Packit |
88fe6f |
except IoprioSetError as e:
|
|
Packit |
88fe6f |
self.prompt_error('Error setting I/O priority: %s' % e.err)
|
|
Packit |
88fe6f |
except InvalidPid:
|
|
Packit |
88fe6f |
self.prompt_error('Invalid process id!')
|
|
Packit |
88fe6f |
except InvalidTid:
|
|
Packit |
88fe6f |
self.prompt_error('Invalid thread id!')
|
|
Packit |
88fe6f |
except InvalidIoprioData:
|
|
Packit |
88fe6f |
self.prompt_error('Invalid I/O priority data!')
|
|
Packit |
88fe6f |
except InvalidInt:
|
|
Packit |
88fe6f |
self.prompt_error('Invalid integer!')
|
|
Packit |
88fe6f |
except CancelInput:
|
|
Packit |
88fe6f |
self.prompt_clear()
|
|
Packit |
88fe6f |
else:
|
|
Packit |
88fe6f |
self.prompt_clear()
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
key_bindings = {
|
|
Packit |
88fe6f |
ord('q'):
|
|
Packit |
88fe6f |
lambda: sys.exit(0),
|
|
Packit |
88fe6f |
ord('Q'):
|
|
Packit |
88fe6f |
lambda: sys.exit(0),
|
|
Packit |
88fe6f |
ord('r'):
|
|
Packit |
88fe6f |
lambda: self.reverse_sorting(),
|
|
Packit |
88fe6f |
ord('R'):
|
|
Packit |
88fe6f |
lambda: self.reverse_sorting(),
|
|
Packit |
88fe6f |
ord('a'):
|
|
Packit |
88fe6f |
toggle_accumulated,
|
|
Packit |
88fe6f |
ord('A'):
|
|
Packit |
88fe6f |
toggle_accumulated,
|
|
Packit |
88fe6f |
ord('o'):
|
|
Packit |
88fe6f |
toggle_only_io,
|
|
Packit |
88fe6f |
ord('O'):
|
|
Packit |
88fe6f |
toggle_only_io,
|
|
Packit |
88fe6f |
ord('p'):
|
|
Packit |
88fe6f |
toggle_processes,
|
|
Packit |
88fe6f |
ord('P'):
|
|
Packit |
88fe6f |
toggle_processes,
|
|
Packit |
88fe6f |
ord('i'):
|
|
Packit |
88fe6f |
ionice,
|
|
Packit |
88fe6f |
ord('I'):
|
|
Packit |
88fe6f |
ionice,
|
|
Packit |
88fe6f |
curses.KEY_LEFT:
|
|
Packit |
88fe6f |
lambda: self.adjust_sorting_key(-1),
|
|
Packit |
88fe6f |
curses.KEY_RIGHT:
|
|
Packit |
88fe6f |
lambda: self.adjust_sorting_key(1),
|
|
Packit |
88fe6f |
curses.KEY_HOME:
|
|
Packit |
88fe6f |
lambda: self.adjust_sorting_key(-len(IOTopUI.sorting_keys)),
|
|
Packit |
88fe6f |
curses.KEY_END:
|
|
Packit |
88fe6f |
lambda: self.adjust_sorting_key(len(IOTopUI.sorting_keys))
|
|
Packit |
88fe6f |
}
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
action = key_bindings.get(key, lambda: None)
|
|
Packit |
88fe6f |
action()
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def get_data(self):
|
|
Packit |
88fe6f |
def format(p):
|
|
Packit |
88fe6f |
stats = format_stats(self.options, p, self.process_list.duration)
|
|
Packit |
88fe6f |
io_delay, swapin_delay, read_bytes, write_bytes = stats
|
|
Packit |
88fe6f |
if Stats.has_blkio_delay_total:
|
|
Packit |
88fe6f |
delay_stats = '%7s %7s ' % (swapin_delay, io_delay)
|
|
Packit |
88fe6f |
else:
|
|
Packit |
88fe6f |
delay_stats = ' ?unavailable? '
|
|
Packit |
88fe6f |
pid_format = '%%%dd' % MAX_PID_WIDTH
|
|
Packit |
88fe6f |
line = (pid_format + ' %4s %-8s %11s %11s %s') % (
|
|
Packit |
88fe6f |
p.pid, p.get_ioprio(), p.get_user()[:8], read_bytes,
|
|
Packit |
88fe6f |
write_bytes, delay_stats)
|
|
Packit |
88fe6f |
cmdline = p.get_cmdline()
|
|
Packit |
88fe6f |
if not self.options.batch:
|
|
Packit |
88fe6f |
remaining_length = self.width - len(line)
|
|
Packit |
88fe6f |
if 2 < remaining_length < len(cmdline):
|
|
Packit |
88fe6f |
len1 = (remaining_length - 1) // 2
|
|
Packit |
88fe6f |
offset2 = -(remaining_length - len1 - 1)
|
|
Packit |
88fe6f |
cmdline = cmdline[:len1] + '~' + cmdline[offset2:]
|
|
Packit |
88fe6f |
line += cmdline
|
|
Packit |
88fe6f |
if not self.options.batch:
|
|
Packit |
88fe6f |
line = line[:self.width]
|
|
Packit |
88fe6f |
return line
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def should_format(p):
|
|
Packit |
88fe6f |
return not self.options.only or \
|
|
Packit |
88fe6f |
p.did_some_io(self.options.accumulated)
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
processes = list(filter(should_format,
|
|
Packit |
88fe6f |
self.process_list.processes.values()))
|
|
Packit |
88fe6f |
key = IOTopUI.sorting_keys[self.sorting_key][0]
|
|
Packit |
88fe6f |
if self.options.accumulated:
|
|
Packit |
88fe6f |
stats_lambda = lambda p: p.stats_accum
|
|
Packit |
88fe6f |
else:
|
|
Packit |
88fe6f |
stats_lambda = lambda p: p.stats_delta
|
|
Packit |
88fe6f |
processes.sort(key=lambda p: key(p, stats_lambda(p)),
|
|
Packit |
88fe6f |
reverse=self.sorting_reverse)
|
|
Packit |
88fe6f |
if not self.options.batch:
|
|
Packit |
88fe6f |
del processes[self.height - 2:]
|
|
Packit |
88fe6f |
return list(map(format, processes))
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def refresh_display(self, first_time, total, actual, duration):
|
|
Packit |
88fe6f |
summary = [
|
|
Packit |
88fe6f |
'Total DISK READ : %s | Total DISK WRITE : %s' % (
|
|
Packit |
88fe6f |
format_bandwidth(self.options, total[0], duration).rjust(14),
|
|
Packit |
88fe6f |
format_bandwidth(self.options, total[1], duration).rjust(14)),
|
|
Packit |
88fe6f |
'Actual DISK READ: %s | Actual DISK WRITE: %s' % (
|
|
Packit |
88fe6f |
format_bandwidth(self.options, actual[0], duration).rjust(14),
|
|
Packit |
88fe6f |
format_bandwidth(self.options, actual[1], duration).rjust(14))
|
|
Packit |
88fe6f |
]
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
pid = max(0, (MAX_PID_WIDTH - 3)) * ' '
|
|
Packit |
88fe6f |
if self.options.processes:
|
|
Packit |
88fe6f |
pid += 'PID'
|
|
Packit |
88fe6f |
else:
|
|
Packit |
88fe6f |
pid += 'TID'
|
|
Packit |
88fe6f |
titles = [pid, ' PRIO', ' USER', ' DISK READ', ' DISK WRITE',
|
|
Packit |
88fe6f |
' SWAPIN', ' IO', ' COMMAND']
|
|
Packit |
88fe6f |
lines = self.get_data()
|
|
Packit |
88fe6f |
if self.options.time:
|
|
Packit |
88fe6f |
titles = [' TIME'] + titles
|
|
Packit |
88fe6f |
current_time = time.strftime('%H:%M:%S ')
|
|
Packit |
88fe6f |
lines = [current_time + l for l in lines]
|
|
Packit |
88fe6f |
summary = [current_time + s for s in summary]
|
|
Packit |
88fe6f |
if self.options.batch:
|
|
Packit |
88fe6f |
if self.options.quiet <= 2:
|
|
Packit |
88fe6f |
for s in summary:
|
|
Packit |
88fe6f |
print(s)
|
|
Packit |
88fe6f |
if self.options.quiet <= int(first_time):
|
|
Packit |
88fe6f |
print(''.join(titles))
|
|
Packit |
88fe6f |
for l in lines:
|
|
Packit |
cd1537 |
print(l.encode('utf-8'))
|
|
Packit |
88fe6f |
sys.stdout.flush()
|
|
Packit |
88fe6f |
else:
|
|
Packit |
88fe6f |
self.win.erase()
|
|
Packit |
88fe6f |
for i, s in enumerate(summary):
|
|
Packit |
88fe6f |
self.win.addstr(i, 0, s[:self.width])
|
|
Packit |
88fe6f |
self.win.hline(len(summary), 0, ord(' ') | curses.A_REVERSE,
|
|
Packit |
88fe6f |
self.width)
|
|
Packit |
88fe6f |
remaining_cols = self.width
|
|
Packit |
88fe6f |
for i in range(len(titles)):
|
|
Packit |
88fe6f |
attr = curses.A_REVERSE
|
|
Packit |
88fe6f |
title = titles[i]
|
|
Packit |
88fe6f |
if i == self.sorting_key:
|
|
Packit |
88fe6f |
title = title[1:]
|
|
Packit |
88fe6f |
if i == self.sorting_key:
|
|
Packit |
88fe6f |
attr |= curses.A_BOLD
|
|
Packit |
88fe6f |
title += self.sorting_reverse and '>' or '<'
|
|
Packit |
88fe6f |
title = title[:remaining_cols]
|
|
Packit |
88fe6f |
remaining_cols -= len(title)
|
|
Packit |
88fe6f |
self.win.addstr(title, attr)
|
|
Packit |
88fe6f |
if Stats.has_blkio_delay_total:
|
|
Packit |
88fe6f |
status_msg = None
|
|
Packit |
88fe6f |
else:
|
|
Packit |
88fe6f |
status_msg = ('CONFIG_TASK_DELAY_ACCT not enabled in kernel, '
|
|
Packit |
88fe6f |
'cannot determine SWAPIN and IO %')
|
|
Packit |
88fe6f |
num_lines = min(len(lines), self.height - 2 - int(bool(status_msg)))
|
|
Packit |
88fe6f |
for i in range(num_lines):
|
|
Packit |
88fe6f |
try:
|
|
Packit |
88fe6f |
def print_line(line):
|
|
Packit |
88fe6f |
self.win.addstr(i + len(summary) + 1, 0, line)
|
|
Packit |
88fe6f |
try:
|
|
Packit |
88fe6f |
print_line(lines[i])
|
|
Packit |
88fe6f |
except UnicodeEncodeError:
|
|
Packit |
88fe6f |
# Python2: 'ascii' codec can't encode character ...
|
|
Packit |
88fe6f |
# http://bugs.debian.org/708252
|
|
Packit |
88fe6f |
print_line(lines[i].encode('utf-8'))
|
|
Packit |
88fe6f |
except curses.error:
|
|
Packit |
88fe6f |
pass
|
|
Packit |
88fe6f |
if status_msg:
|
|
Packit |
88fe6f |
self.win.insstr(self.height - len(summary), 0, status_msg,
|
|
Packit |
88fe6f |
curses.A_BOLD)
|
|
Packit |
88fe6f |
self.win.refresh()
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def run_iotop_window(win, options):
|
|
Packit |
88fe6f |
if options.batch:
|
|
Packit |
88fe6f |
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
|
Packit |
88fe6f |
else:
|
|
Packit |
88fe6f |
def clean_exit(*args, **kwargs):
|
|
Packit |
88fe6f |
sys.exit(0)
|
|
Packit |
88fe6f |
signal.signal(signal.SIGINT, clean_exit)
|
|
Packit |
88fe6f |
signal.signal(signal.SIGTERM, clean_exit)
|
|
Packit |
88fe6f |
taskstats_connection = TaskStatsNetlink(options)
|
|
Packit |
88fe6f |
process_list = ProcessList(taskstats_connection, options)
|
|
Packit |
88fe6f |
ui = IOTopUI(win, process_list, options)
|
|
Packit |
88fe6f |
ui.run()
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def run_iotop(options):
|
|
Packit |
88fe6f |
try:
|
|
Packit |
88fe6f |
if options.batch:
|
|
Packit |
88fe6f |
return run_iotop_window(None, options)
|
|
Packit |
88fe6f |
else:
|
|
Packit |
88fe6f |
return curses.wrapper(run_iotop_window, options)
|
|
Packit |
88fe6f |
except OSError as e:
|
|
Packit |
88fe6f |
if e.errno == errno.EPERM:
|
|
Packit |
88fe6f |
print(e, file=sys.stderr)
|
|
Packit |
88fe6f |
print('''
|
|
Packit |
88fe6f |
The Linux kernel interfaces that iotop relies on now require root priviliges
|
|
Packit |
88fe6f |
or the NET_ADMIN capability. This change occured because a security issue
|
|
Packit |
88fe6f |
(CVE-2011-2494) was found that allows leakage of sensitive data across user
|
|
Packit |
88fe6f |
boundaries. If you require the ability to run iotop as a non-root user, please
|
|
Packit |
88fe6f |
configure sudo to allow you to run iotop as root.
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
Please do not file bugs on iotop about this.''', file=sys.stderr)
|
|
Packit |
88fe6f |
sys.exit(1)
|
|
Packit |
88fe6f |
else:
|
|
Packit |
88fe6f |
raise
|
|
Packit |
9a3822 |
except curses.error as e:
|
|
Packit |
9a3822 |
stre = str(e)
|
|
Packit |
9a3822 |
if stre.find('ERR')>=0 and (
|
|
Packit |
9a3822 |
stre.find('nocbreak()')>=0 or stre.find('endwin()')>=0
|
|
Packit |
9a3822 |
):
|
|
Packit |
9a3822 |
pass
|
|
Packit |
9a3822 |
# endwin and nocbreak can cause error (and raise hard to catch
|
|
Packit |
9a3822 |
# exception) if iotop was running in the terminal and that
|
|
Packit |
9a3822 |
# terminal got closed while iotop was still running
|
|
Packit |
9a3822 |
else:
|
|
Packit |
9a3822 |
raise
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
#
|
|
Packit |
88fe6f |
# Profiling
|
|
Packit |
88fe6f |
#
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def _profile(continuation):
|
|
Packit |
88fe6f |
prof_file = 'iotop.prof'
|
|
Packit |
88fe6f |
try:
|
|
Packit |
88fe6f |
import cProfile
|
|
Packit |
88fe6f |
import pstats
|
|
Packit |
88fe6f |
print('Profiling using cProfile')
|
|
Packit |
88fe6f |
cProfile.runctx('continuation()', globals(), locals(), prof_file)
|
|
Packit |
88fe6f |
stats = pstats.Stats(prof_file)
|
|
Packit |
88fe6f |
except ImportError:
|
|
Packit |
88fe6f |
import hotshot
|
|
Packit |
88fe6f |
import hotshot.stats
|
|
Packit |
88fe6f |
prof = hotshot.Profile(prof_file, lineevents=1)
|
|
Packit |
88fe6f |
print('Profiling using hotshot')
|
|
Packit |
88fe6f |
prof.runcall(continuation)
|
|
Packit |
88fe6f |
prof.close()
|
|
Packit |
88fe6f |
stats = hotshot.stats.load(prof_file)
|
|
Packit |
88fe6f |
stats.strip_dirs()
|
|
Packit |
88fe6f |
stats.sort_stats('time', 'calls')
|
|
Packit |
88fe6f |
stats.print_stats(50)
|
|
Packit |
88fe6f |
stats.print_callees(50)
|
|
Packit |
88fe6f |
os.remove(prof_file)
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
#
|
|
Packit |
88fe6f |
# Main program
|
|
Packit |
88fe6f |
#
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
USAGE = '''%s [OPTIONS]
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
DISK READ and DISK WRITE are the block I/O bandwidth used during the sampling
|
|
Packit |
88fe6f |
period. SWAPIN and IO are the percentages of time the thread spent respectively
|
|
Packit |
88fe6f |
while swapping in and waiting on I/O more generally. PRIO is the I/O priority at
|
|
Packit |
88fe6f |
which the thread is running (set using the ionice command).
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
Controls: left and right arrows to change the sorting column, r to invert the
|
|
Packit |
88fe6f |
sorting order, o to toggle the --only option, p to toggle the --processes
|
|
Packit |
88fe6f |
option, a to toggle the --accumulated option, i to change I/O priority, q to
|
|
Packit |
88fe6f |
quit, any other key to force a refresh.''' % sys.argv[0]
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
def main():
|
|
Packit |
88fe6f |
try:
|
|
Packit |
88fe6f |
locale.setlocale(locale.LC_ALL, '')
|
|
Packit |
88fe6f |
except locale.Error:
|
|
Packit |
88fe6f |
print('unable to set locale, falling back to the default locale')
|
|
Packit |
88fe6f |
parser = optparse.OptionParser(usage=USAGE, version='iotop ' + VERSION)
|
|
Packit |
88fe6f |
parser.add_option('-o', '--only', action='store_true',
|
|
Packit |
88fe6f |
dest='only', default=False,
|
|
Packit |
88fe6f |
help='only show processes or threads actually doing I/O')
|
|
Packit |
88fe6f |
parser.add_option('-b', '--batch', action='store_true', dest='batch',
|
|
Packit |
88fe6f |
help='non-interactive mode')
|
|
Packit |
88fe6f |
parser.add_option('-n', '--iter', type='int', dest='iterations',
|
|
Packit |
88fe6f |
metavar='NUM',
|
|
Packit |
88fe6f |
help='number of iterations before ending [infinite]')
|
|
Packit |
88fe6f |
parser.add_option('-d', '--delay', type='float', dest='delay_seconds',
|
|
Packit |
88fe6f |
help='delay between iterations [1 second]',
|
|
Packit |
88fe6f |
metavar='SEC', default=1)
|
|
Packit |
88fe6f |
parser.add_option('-p', '--pid', type='int', dest='pids', action='append',
|
|
Packit |
88fe6f |
help='processes/threads to monitor [all]', metavar='PID')
|
|
Packit |
88fe6f |
parser.add_option('-u', '--user', type='str', dest='users', action='append',
|
|
Packit |
88fe6f |
help='users to monitor [all]', metavar='USER')
|
|
Packit |
88fe6f |
parser.add_option('-P', '--processes', action='store_true',
|
|
Packit |
88fe6f |
dest='processes', default=False,
|
|
Packit |
88fe6f |
help='only show processes, not all threads')
|
|
Packit |
88fe6f |
parser.add_option('-a', '--accumulated', action='store_true',
|
|
Packit |
88fe6f |
dest='accumulated', default=False,
|
|
Packit |
88fe6f |
help='show accumulated I/O instead of bandwidth')
|
|
Packit |
88fe6f |
parser.add_option('-k', '--kilobytes', action='store_true',
|
|
Packit |
88fe6f |
dest='kilobytes', default=False,
|
|
Packit |
88fe6f |
help='use kilobytes instead of a human friendly unit')
|
|
Packit |
88fe6f |
parser.add_option('-t', '--time', action='store_true', dest='time',
|
|
Packit |
88fe6f |
help='add a timestamp on each line (implies --batch)')
|
|
Packit |
88fe6f |
parser.add_option('-q', '--quiet', action='count', dest='quiet', default=0,
|
|
Packit |
88fe6f |
help='suppress some lines of header (implies --batch)')
|
|
Packit |
88fe6f |
parser.add_option('--profile', action='store_true', dest='profile',
|
|
Packit |
88fe6f |
default=False, help=optparse.SUPPRESS_HELP)
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
options, args = parser.parse_args()
|
|
Packit |
88fe6f |
if args:
|
|
Packit |
88fe6f |
parser.error('Unexpected arguments: ' + ' '.join(args))
|
|
Packit |
88fe6f |
find_uids(options)
|
|
Packit |
88fe6f |
options.pids = options.pids or []
|
|
Packit |
88fe6f |
options.batch = options.batch or options.time or options.quiet
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
main_loop = lambda: run_iotop(options)
|
|
Packit |
88fe6f |
|
|
Packit |
88fe6f |
if options.profile:
|
|
Packit |
88fe6f |
def safe_main_loop():
|
|
Packit |
88fe6f |
try:
|
|
Packit |
88fe6f |
main_loop()
|
|
Packit |
88fe6f |
except:
|
|
Packit |
88fe6f |
pass
|
|
Packit |
88fe6f |
_profile(safe_main_loop)
|
|
Packit |
88fe6f |
else:
|
|
Packit |
88fe6f |
main_loop()
|
|
Packit |
88fe6f |
|