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

Attachment: signature.asc
Description: This is a digitally signed message part

Reply via email to