Hi Guillame, I wrote a patch to implement ionice functionality in iotop, unfortunately it doesn't seem to work properly, I wonder if you could take a look at it and see what I am doing wrong.
-- bye, pabs http://wiki.debian.org/PaulWise
From 8e698bf02a94330bd27fc5832ade80b60d9f2f58 Mon Sep 17 00:00:00 2001 From: Paul Wise <pa...@bonedaddy.net> Date: Wed, 5 Aug 2009 01:17:26 +0200 Subject: [PATCH] WIP: implement ionice functionality (Closes: http://bugs.debian.org/535969) --- iotop.1 | 1 + iotop/data.py | 6 ++++ iotop/ioprio.py | 43 ++++++++++++++++++++++---- iotop/ui.py | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 128 insertions(+), 11 deletions(-) diff --git a/iotop.1 b/iotop.1 index e7e19be..4cc01e5 100644 --- a/iotop.1 +++ b/iotop.1 @@ -77,6 +77,7 @@ the I/O summary is never printed. .PD 1 .RE .SH SEE ALSO +.BR ionice (1), .BR top (1), .BR vmstat (1) .SH AUTHOR diff --git a/iotop/data.py b/iotop/data.py index 0c71315..0a2d380 100644 --- a/iotop/data.py +++ b/iotop/data.py @@ -170,6 +170,9 @@ class ThreadInfo(DumpableObject): def get_ioprio(self): return ioprio.get(self.tid) + def set_ioprio(self, ioprio_class, ioprio_data): + return ioprio.set(ioprio.IOPRIO_WHO_PROCESS, self.tid, ioprio_class, ioprio_data) + def update_stats(self, stats): if not self.stats_total: self.stats_total = stats @@ -273,6 +276,9 @@ class ProcessInfo(DumpableObject): return priorities.pop() return '?' + def set_ioprio(self, ioprio_class, ioprio_data): + return ioprio.set(ioprio.IOPRIO_WHO_PROCESS, self.pid, ioprio_class, ioprio_data) + def ioprio_sort_key(self): return ioprio.sort_key(self.get_ioprio()) diff --git a/iotop/ioprio.py b/iotop/ioprio.py index d1162a8..ef569e4 100644 --- a/iotop/ioprio.py +++ b/iotop/ioprio.py @@ -5,7 +5,7 @@ import platform 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 +# f=configure.ac;h=770eb45ae85d32757fc3cff1d70a7808a627f9f7;hb=HEAD#l354 # i386 bit userspace under an x86_64 kernel will have its uname() appear as # 'x86_64' but it will use the i386 syscall number, that's why we consider both # the architecture name and the word size. @@ -21,26 +21,46 @@ IOPRIO_GET_ARCH_SYSCALL = [ ('x86_64*', '64bit', 252), ] -def find_ioprio_get_syscall_number(): +IOPRIO_SET_ARCH_SYSCALL = [ + ('alpha', '*', 442), + ('i*86', '*', 289), + ('ia64*', '*', 1274), + ('powerpc*', '*', 273), + ('s390*', '*', 282), + ('sparc*', '*', 196), + ('sh*', '*', 288), + ('x86_64*', '32bit', 289), + ('x86_64*', '64bit', 251), +] + +def find_ioprio_syscall_number(syscall_list): arch = os.uname()[4] bits = platform.architecture()[0] - for candidate_arch, candidate_bits, syscall_nr in IOPRIO_GET_ARCH_SYSCALL: + for candidate_arch, candidate_bits, syscall_nr in syscall_list: if fnmatch.fnmatch(arch, candidate_arch) and \ fnmatch.fnmatch(bits, candidate_bits): return syscall_nr -__NR_ioprio_get = find_ioprio_get_syscall_number() +__NR_ioprio_get = find_ioprio_syscall_number(IOPRIO_GET_ARCH_SYSCALL) +__NR_ioprio_set = find_ioprio_syscall_number(IOPRIO_SET_ARCH_SYSCALL) ctypes_handle = ctypes.CDLL(None) syscall = ctypes_handle.syscall -PRIORITY_CLASSES = (None, 'rt', 'be', 'idle') +PRIORITY_CLASSES = [None, 'rt', 'be', 'idle'] -WHO_PROCESS = 1 +IOPRIO_WHO_PROCESS = 1 +IOPRIO_WHO_PGRP = 2 +IOPRIO_WHO_USER = 3 IOPRIO_CLASS_SHIFT = 13 IOPRIO_PRIO_MASK = (1 << IOPRIO_CLASS_SHIFT) - 1 +def ioprio_value(ioprio_class, ioprio_data): + try: ioprio_class = PRIORITY_CLASSES.index(ioprio_class) + except ValueError: ioprio_class = PRIORITY_CLASSES.index(None) + return (ioprio_class << IOPRIO_CLASS_SHIFT) | ioprio_data + def ioprio_class(ioprio): return PRIORITY_CLASSES[ioprio >> IOPRIO_CLASS_SHIFT] @@ -66,7 +86,7 @@ def get(pid): if __NR_ioprio_get is None: return '?sys' - ioprio = syscall(__NR_ioprio_get, WHO_PROCESS, pid) + ioprio = syscall(__NR_ioprio_get, IOPRIO_WHO_PROCESS, pid) if ioprio < 0: return '?err' @@ -77,6 +97,15 @@ def get(pid): return prio_class return '%s/%d' % (prio_class, ioprio_data(ioprio)) +def set(type, who, ioprio_class, ioprio_data): + if __NR_ioprio_set is None: + return '?sys' + + ioprio_val = ioprio_value(ioprio_class, ioprio_data) + ret = syscall(__NR_ioprio_set, type, who, ioprio_val) + if ret < 0: + return '?err' + def sort_key(key): if key[0] == '?': return -ord(key[1]) diff --git a/iotop/ui.py b/iotop/ui.py index 093dc8e..b1c65d8 100644 --- a/iotop/ui.py +++ b/iotop/ui.py @@ -9,7 +9,7 @@ import struct import sys import time -from iotop.data import find_uids, TaskStatsNetlink, ProcessList +from iotop.data import find_uids, TaskStatsNetlink, ProcessList, ProcessInfo from iotop.version import VERSION # @@ -82,6 +82,7 @@ class IOTopUI(object): self.options = options self.sorting_key = 6 self.sorting_reverse = IOTopUI.sorting_keys[self.sorting_key][1] + self.prompt = None if not self.options.batch: self.win = win self.resize() @@ -139,6 +140,61 @@ class IOTopUI(object): if orig_sorting_key != self.sorting_key: self.sorting_reverse = IOTopUI.sorting_keys[self.sorting_key][1] + # I wonder if switching to urwid for the display would be better here + + def prompt_int(self, prompt, default = None): + self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width) + self.win.addstr(1, 0, prompt) + curses.echo() + ret_int = self.win.getstr(1, len(prompt)) + curses.noecho() + try: return int(ret_int) + except: return None + + def prompt_pid(self): + return self.prompt_int('PID to renice: ') + + def prompt_data(self, ioprio_data = None): + return self.prompt_int('I/O priority data (default: %s): ' % ioprio_data, ioprio_data) + + def prompt_set(self, prompt, set, selected): + prompt_len = len(prompt) + set_len = len(set) + while True: + i = 1 + self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width) + self.win.insstr(1, 0, prompt, curses.A_NORMAL) + str_sum = prompt_len + for item in set: + str = ' %s ' % item + self.win.insstr(1, str_sum, str, curses.A_REVERSE if selected is i else curses.A_NORMAL) + str_sum += len(str) + i = i + 1 + while True: + key = self.win.getch() + if (key == curses.KEY_LEFT or key is ord('l')) and selected > 1: + selected = selected - 1 + break + elif (key == curses.KEY_RIGHT or key is ord('r')) and selected < set_len: + selected = selected + 1 + break + elif key == curses.KEY_ENTER or key is ord('\n') or key is ord('\r'): + return selected + elif key == curses.KEY_CANCEL or key == curses.KEY_CLOSE or key is ord('q') or key is ord('Q'): + return None + + def prompt_class(self, ioprio_class = None): + prompt = 'I/O priority class: ' + classes = ['Real-time', 'Best-effort', 'Idle'] + if ioprio_class is None: ioprio_class = 2 + return self.prompt_set(prompt, classes, ioprio_class) + + def ioprio_to_class_and_data(self, ioprio_str): + if ioprio_str.startswith('rt/'): return (1, int(ioprio_str[3:])) + if ioprio_str.startswith('be/'): return (2, int(ioprio_str[3:])) + if ioprio_str == 'idle': return (3, 0) + return None + def handle_key(self, key): def toggle_accumulated(): self.options.accumulated ^= True @@ -149,6 +205,27 @@ class IOTopUI(object): self.options.processes ^= True self.process_list.clear() self.process_list.refresh_processes() + def ionice(): + pid = self.prompt_pid() + if pid is None: return + process = self.process_list.get_process(pid) + process_ioprio = process.get_ioprio() + if process_ioprio is not None: + (ioprio_class, ioprio_data) = self.ioprio_to_class_and_data(process_ioprio) + else: + (ioprio_class, ioprio_data) = (None, None) + ioprio_class = self.prompt_class(ioprio_class) + if ioprio_class is None: return + if ioprio_class is not 3: + ioprio_data = self.prompt_data(ioprio_data) + if ioprio_data is None: return + else: ioprio_data = 0 + ioprio_set = process.set_ioprio(ioprio_class, ioprio_data) + if '?err' == ioprio_set: + self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width) + self.win.insstr(1, 0, 'Error setting I/O priority!', curses.A_NORMAL) + time.sleep(10) + self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width) key_bindings = { ord('q'): lambda: sys.exit(0), @@ -170,6 +247,8 @@ class IOTopUI(object): toggle_processes, ord('P'): toggle_processes, + ord('i'): + ionice, curses.KEY_LEFT: lambda: self.adjust_sorting_key(-1), curses.KEY_RIGHT: @@ -215,13 +294,14 @@ class IOTopUI(object): processes.sort(key=lambda p: key(p, stats_lambda(p)), reverse=self.sorting_reverse) if not self.options.batch: - del processes[self.height - 2:] + del processes[self.height - 3:] return map(format, processes) def refresh_display(self, first_time, total_read, total_write, duration): summary = 'Total DISK READ: %s | Total DISK WRITE: %s' % ( format_bandwidth(self.options, total_read, duration), format_bandwidth(self.options, total_write, duration)) + status_line = '' if self.options.processes: pid = ' PID' else: @@ -244,7 +324,8 @@ class IOTopUI(object): else: self.win.erase() self.win.addstr(summary[:self.width]) - self.win.hline(1, 0, ord(' ') | curses.A_REVERSE, self.width) + self.win.addstr(status_line) + self.win.hline(2, 0, ord(' ') | curses.A_REVERSE, self.width) remaining_cols = self.width for i in xrange(len(titles)): attr = curses.A_REVERSE @@ -259,7 +340,7 @@ class IOTopUI(object): self.win.addstr(title, attr) for i in xrange(len(lines)): try: - self.win.insstr(i + 2, 0, lines[i].encode('utf-8')) + self.win.insstr(i + 3, 1, lines[i].encode('utf-8')) except curses.error: exc_type, value, traceback = sys.exc_info() value = '%s win:%s i:%d line:%s' % \ -- 1.6.3.3
signature.asc
Description: This is a digitally signed message part