From 6a8947aadeceb99a554042a086e250f260a895e8 Mon Sep 17 00:00:00 2001 From: Igor Bazhitov Date: Wed, 10 Oct 2012 14:14:49 +0400 Subject: [PATCH] Add 'Actual' bandwidth stats to summary header 'Total' values in the summary header may look confusing to users. They represent actual kernel <-> disk I/O bandwidth, while individual values for processes/threads show process <-> kernel I/O bandwidth. Rename 'Total' to 'Actual' and add old 'Total' status line that sums up all individual process/thread bandwidths. Explain the difference between 'Total' and 'Actual' in the manpage. --- iotop.8 | 8 ++++++++ iotop/data.py | 8 ++++++-- iotop/ui.py | 32 +++++++++++++++++++------------- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/iotop.8 b/iotop.8 index f059189..68d9edd 100644 --- a/iotop.8 +++ b/iotop.8 @@ -15,8 +15,16 @@ options need to be enabled in your Linux kernel build configuration. 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. For each process, its I/O priority (class/level) is shown. +.PP In addition, the total I/O bandwidth read and written during the sampling period is displayed at the top of the interface. +\fBTotal DISK READ\fR and \fBTotal DISK WRITE\fR values represent total read +and write bandwidth between processes and kernel threads on the one side and +kernel block device subsystem on the other. While \fBActual DISK READ\fR and +\fBActual DISK WRITE\fR values represent corresponding bandwidths for actual +disk I/O between kernel block device subsystem and underlying hardware (HDD, SSD, etc.). +Thus \fBTotal\fR and \fBActual\fR values may not be equal at any given moment of time +due to data caching and I/O operations reordering that take place inside Linux kernel. .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, a to toggle the \-\-accumulated option, q to quit or i to change the priority of a thread or a process' thread(s). Any other key will force a refresh. diff --git a/iotop/data.py b/iotop/data.py index 0e43a2c..6dae4b8 100644 --- a/iotop/data.py +++ b/iotop/data.py @@ -433,6 +433,8 @@ class ProcessList(DumpableObject): self.duration = new_timestamp - self.timestamp self.timestamp = new_timestamp + total_read = total_write = 0 + for tgid in self.list_tgids(): process = self.get_process(tgid) if not process: @@ -442,9 +444,11 @@ class ProcessList(DumpableObject): stats = self.taskstats_connection.get_single_task_stats(thread) if stats: thread.update_stats(stats) + delta = thread.stats_delta + total_read += delta.read_bytes + total_write += delta.write_bytes thread.mark = False - - return self.vmstat.delta() + return (total_read, total_write), self.vmstat.delta() def refresh_processes(self): for process in self.processes.values(): diff --git a/iotop/ui.py b/iotop/ui.py index 938d70e..1184ba2 100644 --- a/iotop/ui.py +++ b/iotop/ui.py @@ -150,9 +150,8 @@ class IOTopUI(object): poll.register(sys.stdin.fileno(), select.POLLIN|select.POLLPRI) while self.options.iterations is None or \ iterations < self.options.iterations: - total = self.process_list.refresh_processes() - total_read, total_write = total - self.refresh_display(iterations == 0, total_read, total_write, + total, actual = self.process_list.refresh_processes() + self.refresh_display(iterations == 0, total, actual, self.process_list.duration) if self.options.iterations is not None: iterations += 1 @@ -412,10 +411,15 @@ class IOTopUI(object): del processes[self.height - 2:] return list(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).rjust(14), - format_bandwidth(self.options, total_write, duration).rjust(14)) + def refresh_display(self, first_time, total, actual, duration): + summary = [ + 'Total DISK READ : %s | Total DISK WRITE : %s' % ( + format_bandwidth(self.options, total[0], duration).rjust(14), + format_bandwidth(self.options, total[1], duration).rjust(14)), + 'Actual DISK READ: %s | Actual DISK WRITE: %s' % ( + format_bandwidth(self.options, actual[0], duration).rjust(14), + format_bandwidth(self.options, actual[1], duration).rjust(14)) + ] pid = max(0, (MAX_PID_WIDTH - 3)) * ' ' if self.options.processes: @@ -429,10 +433,11 @@ class IOTopUI(object): titles = [' TIME'] + titles current_time = time.strftime('%H:%M:%S ') lines = [current_time + l for l in lines] - summary = current_time + summary + summary = [current_time + s for s in summary] if self.options.batch: if self.options.quiet <= 2: - print(summary) + for s in summary: + print(s) if self.options.quiet <= int(first_time): print(''.join(titles)) for l in lines: @@ -440,8 +445,9 @@ class IOTopUI(object): sys.stdout.flush() else: self.win.erase() - self.win.addstr(summary[:self.width]) - self.win.hline(1, 0, ord(' ') | curses.A_REVERSE, self.width) + for i, s in enumerate(summary): + self.win.addstr(i, 0, s[:self.width]) + self.win.hline(len(summary), 0, ord(' ') | curses.A_REVERSE, self.width) remaining_cols = self.width for i in range(len(titles)): attr = curses.A_REVERSE @@ -462,11 +468,11 @@ class IOTopUI(object): num_lines = min(len(lines), self.height - 2 - int(bool(status_msg))) for i in range(num_lines): try: - self.win.addstr(i + 2, 0, lines[i]) + self.win.addstr(i + len(summary) + 1, 0, lines[i]) except curses.error: pass if status_msg: - self.win.insstr(self.height - 1, 0, status_msg, curses.A_BOLD) + self.win.insstr(self.height - len(summary), 0, status_msg, curses.A_BOLD) self.win.refresh() def run_iotop_window(win, options): -- 2.11.4.GIT