From 033efe8e816a5ac952a8e235fc9379bc1442d380 Mon Sep 17 00:00:00 2001 From: Guillaume Chazarain Date: Thu, 25 Dec 2008 20:12:54 +0100 Subject: [PATCH] Added support for showing the I/O priority --- iotop.1 | 4 +-- iotop/data.py | 5 +++ iotop/ioprio.py | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ iotop/ui.py | 14 +++++---- 4 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 iotop/ioprio.py diff --git a/iotop.1 b/iotop.1 index aafbf15..0d7098d 100644 --- a/iotop.1 +++ b/iotop.1 @@ -14,8 +14,8 @@ build configuration, these options depend on CONFIG_TASKSTATS. .PP iotop displays columns for the I/O bandwidth read and written by each process/thread during the sampling period. It also displays the percentage -of time the thread/process spent while swapping in and while waiting on I/O. -In addition the total I/O bandwidth read and written during the sampling +of time the thread/process spent while swapping in and while waiting on I/O. For each process, its I/O priority (class/level) is shown. +In addition, the total I/O bandwidth read and written during the sampling period is displayed at the top of the interface. .PP Use the left and right arrows to change the sorting, r to reverse the diff --git a/iotop/data.py b/iotop/data.py index d36c577..8ffd199 100644 --- a/iotop/data.py +++ b/iotop/data.py @@ -6,6 +6,7 @@ import struct import sys import time +from iotop import ioprio from netlink import Connection, NETLINK_GENERIC, U32Attr, NLM_F_REQUEST from genetlink import Controller, GeNlMessage @@ -176,6 +177,7 @@ class pinfo(object): self.stats_total = Stats.build_all_zero() self.stats_delta = Stats.build_all_zero() self.parse_status('/proc/%d/status' % pid, options) + self.ioprio = ioprio.get(pid) def check_if_valid(self, uid, options): self.valid = options.pids or not options.uids or uid in options.uids @@ -221,6 +223,9 @@ class pinfo(object): def did_some_io(self): return not self.stats_delta.is_all_zero() + def ioprio_sort_key(self): + return ioprio.sort_key(self.ioprio) + class ProcessList(object): def __init__(self, taskstats_connection, options): # {pid: pinfo} diff --git a/iotop/ioprio.py b/iotop/ioprio.py new file mode 100644 index 0000000..4e961d9 --- /dev/null +++ b/iotop/ioprio.py @@ -0,0 +1,97 @@ +import ctypes +import fnmatch +import os +import time + +# From http://git.kernel.org/?p=utils/util-linux-ng/util-linux-ng.git;a=blob; +# f=configure.ac;h=770eb45ae85d32757fc3cff1d70a7808a627f9f7;hb=HEAD#l363 +IOPRIO_GET_ARCH_SYSCALL = [ + ('alpha', 443), + ('i*86', 290), + ('ia64*', 1275), + ('powerpc*', 274), + ('s390*', 283), + ('sparc*', 218), + ('sh*', 289), + ('x86_64*', 252), +] + +def find_ioprio_get_syscall_number(): + arch = os.uname()[4] + + for candidate_arch, syscall_nr in IOPRIO_GET_ARCH_SYSCALL: + if fnmatch.fnmatch(arch, candidate_arch): + return syscall_nr + + +__NR_ioprio_get = find_ioprio_get_syscall_number() +ctypes_handle = ctypes.CDLL(None) +syscall = ctypes_handle.syscall + +PRIORITY_CLASSES = (None, 'rt', 'be', 'idle') + +WHO_PROCESS = 1 +IOPRIO_CLASS_SHIFT = 13 +IOPRIO_PRIO_MASK = (1 << IOPRIO_CLASS_SHIFT) - 1 + +def ioprio_class(ioprio): + return PRIORITY_CLASSES[ioprio >> IOPRIO_CLASS_SHIFT] + +def ioprio_data(ioprio): + return ioprio & IOPRIO_PRIO_MASK + +sched_getscheduler = ctypes_handle.sched_getscheduler +SCHED_OTHER, SCHED_FIFO, SCHED_RR, SCHED_BATCH, SCHED_ISO, SCHED_IDLE = range(6) + +def get_ioprio_from_sched(pid): + scheduler = sched_getscheduler(pid) + nice = int(open('/proc/%d/stat' % pid).read().split()[18]) + ioprio_nice = (nice + 20) / 5 + + if scheduler in (SCHED_FIFO, SCHED_RR): + return 'rt/%d' % ioprio_nice + elif scheduler == SCHED_IDLE: + return 'idle' + else: + return 'be/%d' % ioprio_nice + +def get(pid): + if __NR_ioprio_get is None: + return '?sys' + + ioprio = syscall(__NR_ioprio_get, WHO_PROCESS, pid) + if ioprio < 0: + return '?err' + + prio_class = ioprio_class(ioprio) + if not prio_class: + return get_ioprio_from_sched(pid) + if prio_class == 'idle': + return prio_class + return '%s/%d' % (prio_class, ioprio_data(ioprio)) + +def sort_key(key): + if key[0] == '?': + return -ord(key[1]) + + if '/' in key: + if key.startswith('rt/'): + shift = 0 + elif key.startswith('be/'): + shift = 1 + prio = int(key.split('/')[1]) + elif key == 'idle': + shift = 2 + prio = 0 + + return (1 << (shift * IOPRIO_CLASS_SHIFT)) + prio + +if __name__ == '__main__': + import sys + if len(sys.argv) == 2: + pid = int(sys.argv[1]) + else: + pid = os.getpid() + print 'pid:', pid + print 'ioprio:', get(pid) + diff --git a/iotop/ui.py b/iotop/ui.py index e9e722f..3030ea6 100644 --- a/iotop/ui.py +++ b/iotop/ui.py @@ -50,6 +50,7 @@ class IOTopUI(object): # key, reverse sorting_keys = [ (lambda p: p.pid, False), + (lambda p: p.ioprio_sort_key(), False), (lambda p: p.user, False), (lambda p: p.stats_delta.read_bytes, True), (lambda p: p.stats_delta.write_bytes - @@ -66,8 +67,8 @@ class IOTopUI(object): def __init__(self, win, process_list, options): self.process_list = process_list self.options = options - self.sorting_key = 5 - self.sorting_reverse = IOTopUI.sorting_keys[5][1] + self.sorting_key = 6 + self.sorting_reverse = IOTopUI.sorting_keys[self.sorting_key][1] if not self.options.batch: self.win = win self.resize() @@ -154,8 +155,8 @@ class IOTopUI(object): def format(p): stats = human_stats(p.stats_delta, self.process_list.duration) io_delay, swapin_delay, read_bytes, write_bytes = stats - line = '%5d %-8s %11s %11s %7s %7s ' % (p.pid, p.user[:8], - read_bytes, write_bytes, swapin_delay, io_delay) + line = '%5d %4s %-8s %11s %11s %7s %7s ' % (p.pid, p.ioprio, + p.user[:8], read_bytes, write_bytes, swapin_delay, io_delay) line += p.get_cmdline() if not self.options.batch: line = line[:self.width - 1] @@ -176,7 +177,7 @@ class IOTopUI(object): summary = 'Total DISK READ: %s | Total DISK WRITE: %s' % ( human_bandwidth(total_read, duration), human_bandwidth(total_write, duration)) - titles = [' PID', ' USER', ' DISK READ', ' DISK WRITE', + titles = [' PID', ' PRIO', ' USER', ' DISK READ', ' DISK WRITE', ' SWAPIN', ' IO', ' COMMAND'] lines = self.get_data() if self.options.batch: @@ -219,7 +220,8 @@ USAGE = '''%s [OPTIONS] DISK READ and DISK WRITE are the block I/O bandwidth used during the sampling period. SWAPIN and IO are the percentages of time the thread spent respectively -while swapping in and waiting on I/O more generally. +while swapping in and waiting on I/O more generally. PRIO is the I/O priority at +which the thread is running (set using the ionice command). Controls: left and right arrows to change the sorting column, r to invert the sorting order, o to toggle the --only option, q to quit, any other key to force a refresh.''' % sys.argv[0] -- 2.11.4.GIT