From 6246306c293e1ab8813e5881bab2c3391f11447a Mon Sep 17 00:00:00 2001 From: Guillaume Chazarain Date: Mon, 30 Mar 2009 21:21:00 +0200 Subject: [PATCH] - Added the --accumulated option to show the accumulated traffic instead of the current bandwidth (dynamically toggled with 'a') - Resist to process dying during the taskstats retrieval - Adjusted column headers --- NEWS | 3 ++- iotop.1 | 7 +++++-- iotop/data.py | 50 ++++++++++++++++++++++++++++++++++---------------- iotop/ui.py | 46 +++++++++++++++++++++++++++++++++++----------- 4 files changed, 76 insertions(+), 30 deletions(-) diff --git a/NEWS b/NEWS index a1d13dc..7efa60d 100644 --- a/NEWS +++ b/NEWS @@ -1,7 +1,8 @@ 0.3 ~~~ o -P is now fully implemented and is dynamically toggled with 'p' -o Show the I/O priority (http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=blob_plain;f=Documentation/block/ioprio.txt;hb=HEAD) +o Show the I/O priority +o Added the --accumulated option 0.2.1 ~~~~~ diff --git a/iotop.1 b/iotop.1 index 6a2af70..bf217ae 100644 --- a/iotop.1 +++ b/iotop.1 @@ -1,5 +1,5 @@ .\" Debian manual page, has been forwarded upstream -.TH IOTOP "1" "September 2008" +.TH IOTOP "1" "April 2009" .SH NAME iotop \- simple top\-like I/O monitor .SH SYNOPSIS @@ -19,7 +19,7 @@ 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 -sorting order, o to toggle the \-\-only option, p to toggle the \-\-processes option or q to quit. Any other key will force a refresh. +sorting order, o to toggle the \-\-only option, p to toggle the \-\-processes option, a to toggle the --accumulated option or q to quit. Any other key will force a refresh. .SH OPTIONS .TP \fB\-\-version\fR @@ -51,6 +51,9 @@ A list of users to monitor (all by default) .TP \fB\-P\fR, \fB\-\-processes\fR Only show processes. Normally iotop shows all threads. +.TP +\fB\-a\fR, \fB\-\-accumulated\fR +Show accumulated I/O instead of bandwidth. In this mode, iotop shows the amount of I/O processes have done since iotop started. .SH SEE ALSO .BR top (1), .BR vmstat (1) diff --git a/iotop/data.py b/iotop/data.py index c2c3692..7b3e0b5 100644 --- a/iotop/data.py +++ b/iotop/data.py @@ -164,16 +164,19 @@ class ThreadInfo(DumpableObject): def __init__(self, tid): self.tid = tid self.mark = True - self.stats_total = Stats.build_all_zero() - self.stats_delta = Stats.build_all_zero() + self.stats_total = None + self.stats_delta = None def get_ioprio(self): return ioprio.get(self.tid) def update_stats(self, stats): + if not self.stats_total: + self.stats_total = stats self.stats_delta = stats.delta(self.stats_total) self.stats_total = stats + class ProcessInfo(DumpableObject): """Stats for a single process (a single line in the output): if options.processes is set, it is a collection of threads, otherwise a single @@ -183,6 +186,9 @@ class ProcessInfo(DumpableObject): self.uid = None self.user = None self.threads = {} # {tid: ThreadInfo} + self.stats_delta = Stats.build_all_zero() + self.stats_accum = Stats.build_all_zero() + self.stats_accum_timestamp = time.time() def is_monitored(self, options): if (options.pids and not options.processes and @@ -271,6 +277,26 @@ class ProcessInfo(DumpableObject): self.threads[tid] = thread return thread + def update_stats(self): + stats_delta = Stats.build_all_zero() + for tid, thread in self.threads.items(): + if thread.mark: + del self.threads[tid] + else: + stats_delta = stats_delta.accumulate(thread.stats_delta) + + nr_threads = len(self.threads) + if not nr_threads: + return False + + stats_delta.blkio_delay_total /= nr_threads + stats_delta.swapin_delay_total /= nr_threads + + self.stats_delta = stats_delta + self.stats_accum = self.stats_accum.accumulate(self.stats_delta) + + return True + class ProcessList(DumpableObject): def __init__(self, taskstats_connection, options): # {pid: ProcessInfo} @@ -332,8 +358,9 @@ class ProcessList(DumpableObject): for tid in self.list_tids(tgid): thread = process.get_thread(tid) stats = self.taskstats_connection.get_single_task_stats(tid) - thread.update_stats(stats) - thread.mark = False + if stats: + thread.update_stats(stats) + thread.mark = False return self.vmstat.delta() @@ -345,19 +372,10 @@ class ProcessList(DumpableObject): total_read_and_write = self.update_process_counts() for pid, process in self.processes.items(): - stats_delta = Stats.build_all_zero() - for tid, thread in process.threads.items(): - if thread.mark: - del process.threads[tid] - else: - stats_delta = stats_delta.accumulate(thread.stats_delta) - nr_threads = len(process.threads) - if nr_threads: - stats_delta.blkio_delay_total /= nr_threads - stats_delta.swapin_delay_total /= nr_threads - process.stats_delta = stats_delta - else: + if not process.update_stats(): del self.processes[pid] return total_read_and_write + def clear(self): + self.processes = {} diff --git a/iotop/ui.py b/iotop/ui.py index 94b4fb0..5fed3b7 100644 --- a/iotop/ui.py +++ b/iotop/ui.py @@ -7,6 +7,7 @@ import pwd import select import struct import sys +import time from iotop.data import find_uids, TaskStatsNetlink, ProcessList from iotop.version import VERSION @@ -17,8 +18,7 @@ from iotop.version import VERSION UNITS = ['B', 'K', 'M', 'G', 'T', 'P', 'E'] -def human_bandwidth(size, duration): - bw = size and float(size) / duration +def human_size(size): for i in xrange(len(UNITS) - 1, 0, -1): base = 1 << (10 * i) if 2 * base < size: @@ -26,20 +26,29 @@ def human_bandwidth(size, duration): else: i = 0 base = 1 - res = '%.2f %s/s' % ((float(bw) / base), UNITS[i]) - return res + return '%.2f %s' % ((float(size) / base), UNITS[i]) + +def human_bandwidth(size, duration): + return human_size(size and float(size) / duration) + '/s' -def human_stats(stats, duration): +def human_stats(process, duration, is_accumulated): # Keep in sync with TaskStatsNetlink.members_offsets and # IOTopUI.get_data(self) def delay2percent(delay): # delay in ns, duration in s return '%.2f %%' % min(99.99, delay / (duration * 10000000.0)) + if is_accumulated: + stats = process.stats_accum + display_format = lambda size, duration: human_size(size) + duration = time.time() - process.stats_accum_timestamp + else: + stats = process.stats_delta + display_format = human_bandwidth io_delay = delay2percent(stats.blkio_delay_total) swapin_delay = delay2percent(stats.swapin_delay_total) - read_bytes = human_bandwidth(stats.read_bytes, duration) + read_bytes = display_format(stats.read_bytes, duration) written_bytes = stats.write_bytes - stats.cancelled_write_bytes written_bytes = max(0, written_bytes) - write_bytes = human_bandwidth(written_bytes, duration) + write_bytes = display_format(written_bytes, duration) return io_delay, swapin_delay, read_bytes, write_bytes # @@ -125,10 +134,14 @@ class IOTopUI(object): self.sorting_reverse = IOTopUI.sorting_keys[self.sorting_key][1] def handle_key(self, key): + def toggle_accumulated(): + self.options.accumulated ^= True + self.process_list.clear() def toggle_only_io(): self.options.only ^= True def toggle_processes(): self.options.processes ^= True + self.process_list.clear() self.process_list.refresh_processes() key_bindings = { ord('q'): @@ -139,6 +152,10 @@ class IOTopUI(object): lambda: self.reverse_sorting(), ord('R'): lambda: self.reverse_sorting(), + ord('a'): + toggle_accumulated, + ord('A'): + toggle_accumulated, ord('o'): toggle_only_io, ord('O'): @@ -162,7 +179,8 @@ class IOTopUI(object): def get_data(self): def format(p): - stats = human_stats(p.stats_delta, self.process_list.duration) + stats = human_stats(p, self.process_list.duration, + self.options.accumulated) io_delay, swapin_delay, read_bytes, write_bytes = stats line = '%5d %4s %-8s %11s %11s %7s %7s ' % ( p.pid, p.get_ioprio(), p.get_user()[:8], read_bytes, @@ -190,8 +208,8 @@ class IOTopUI(object): pid = ' PID' else: pid = ' TID' - titles = [pid, ' PRIO', ' USER', ' DISK READ', ' DISK WRITE', - ' SWAPIN', ' IO', ' COMMAND'] + titles = [pid, ' PRIO', ' USER', ' DISK READ', ' DISK WRITE', + ' SWAPIN', ' IO', ' COMMAND'] lines = self.get_data() if self.options.batch: print summary @@ -207,6 +225,8 @@ class IOTopUI(object): attr = curses.A_REVERSE title = titles[i] if i == self.sorting_key: + title = title[1:] + if i == self.sorting_key: attr |= curses.A_BOLD title += self.sorting_reverse and '>' or '<' self.win.addstr(title, attr) @@ -268,9 +288,10 @@ 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. 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, p to toggle the --processes -option, q to quit, any other key to force a refresh.''' % sys.argv[0] +option, a to toggle the --accumulated option, q to quit, any other key to force a refresh.''' % sys.argv[0] def main(): locale.setlocale(locale.LC_ALL, '') @@ -293,6 +314,9 @@ def main(): parser.add_option('-P', '--processes', action='store_true', dest='processes', default=False, help='only show processes, not all threads') + parser.add_option('-a', '--accumulated', action='store_true', + dest='accumulated', default=False, + help='show accumulated I/O instead of bandwidth') parser.add_option('--profile', action='store_true', dest='profile', default=False, help=optparse.SUPPRESS_HELP) -- 2.11.4.GIT