Support for per-task I/O statistics - $top_io
authorPavel Labath <pavelo@centrum.sk>
Fri, 12 Jun 2009 17:08:44 +0000 (19:08 +0200)
committerPavel Labath <pavelo@centrum.sk>
Fri, 12 Jun 2009 17:39:45 +0000 (19:39 +0200)
Basically, I just added three new process properties (io_read, io_write, io_perc - representing
the amount of I/O done by the process during the update interval) and $top_io, that sorts
processes based on io_perc.

Atm, it's completely #ifdef'd, since it requires kernel support. But that creates some wierd
looking syntax at some places, so it may be better to remove some ifdefs.  It even may be
possible to completely remove the ifdefs (ie. convert them to #ifdef linux) since the code will
compile just fine even if the kernel doesn't support I/O accounting. I'll leave that for someone
else to decide.

configure.ac.in
doc/variables.xml
src/conky.c
src/conky.h
src/linux.c
src/text_object.h
src/top.c
src/top.h

index 9af8792..db80219 100644 (file)
@@ -230,6 +230,25 @@ AM_CONDITIONAL(BUILD_APCUPSD, test x$want_apcupsd = xyes)
 
 
 dnl
+dnl I/O stats
+dnl
+
+AC_ARG_ENABLE([iostats],
+              AC_HELP_STRING([--enable-iostats],
+                             [enable if you want support for per-task I/O statistics @<:@default=no@:>@]),
+              [want_iostats="$enableval"], [want_iostats=no])
+
+if test x$want_iostats = xyes; then
+  if test x"$uname" != xLinux; then
+      AC_MSG_NOTICE([iostats not supported on $uname... disabling])
+      want_apcupsd=no
+  else
+      AC_DEFINE(IOSTATS, 1, [Define if you want support for per-task I/O statistics])
+  fi
+fi
+
+
+dnl
 dnl Math
 dnl
 
@@ -823,4 +842,5 @@ $PACKAGE $VERSION configured successfully:
   Imlib2:           $want_imlib2
   ALSA mixer:       $want_alsa
   apcupsd:          $want_apcupsd
+  I/O stats:        $want_iostats
 EOF
index b8336c5..e620592 100644 (file)
         (number) Basically, processes are ranked from highest to
         lowest in terms of cpu usage, which is what (num)
         represents. The types are: "name", "pid", "cpu", "mem",
-        "mem_res", "mem_vsize", and "time". There can be a max of
-        10 processes listed. 
+        "mem_res", "mem_vsize", "time", "io_perc", "io_read" and
+        "io_write".  There can be a max of 10 processes listed. 
         <para /></listitem>
     </varlistentry>
     <varlistentry>
     <varlistentry>
         <term>
             <command>
+                <option>top_io</option>
+            </command>
+            <option>type, num</option>
+        </term>
+        <listitem>Same as top, except sorted by the amount of I/O the
+            process has done during the update interval
+        <para /></listitem>
+    </varlistentry>
+    <varlistentry>
+        <term>
+            <command>
                 <option>totaldown</option>
             </command>
             <option>(net)</option>
index 3a7f964..970bb8a 100644 (file)
@@ -139,6 +139,9 @@ enum {
        RIGHT_SPACER
 } use_spacer;
 int top_cpu, top_mem, top_time;
+#ifdef IOSTATS
+int top_io;
+#endif
 static unsigned int top_name_width = 15;
 int output_methods;
 enum x_initialiser_state x_initialised = NO;
@@ -232,8 +235,11 @@ static void print_version(void)
                   "  * ALSA mixer support\n"
 #endif /* MIXER_IS_ALSA */
 #ifdef APCUPSD
-                       "  * apcupsd\n"
+                  "  * apcupsd\n"
 #endif /* APCUPSD */
+#ifdef IOSTATS
+                  "  * iostats\n"
+#endif /* IOSTATS */
        );
 
        exit(0);
@@ -936,6 +942,9 @@ static void free_text_objects(struct text_object *root, int internal)
                        case OBJ_top:
                        case OBJ_top_mem:
                        case OBJ_top_time:
+#ifdef IOSTATS
+                       case OBJ_top_io:
+#endif
                                if (info.first_process && !internal) {
                                        free_all_processes();
                                        info.first_process = NULL;
@@ -1127,8 +1136,17 @@ static int parse_top_args(const char *s, const char *arg, struct text_object *ob
        } else if (strcmp(&s[3], "_time") == EQUAL) {
                obj->type = OBJ_top_time;
                top_time = 1;
+#ifdef IOSTATS
+       } else if (strcmp(&s[3], "_io") == EQUAL) {
+               obj->type = OBJ_top_io;
+               top_io = 1;
+#endif
        } else {
+#ifdef IOSTATS
+               ERR("Must be top, top_mem, top_time or top_io");
+#else
                ERR("Must be top, top_mem or top_time");
+#endif
                return 0;
        }
 
@@ -1152,9 +1170,22 @@ static int parse_top_args(const char *s, const char *arg, struct text_object *ob
                        obj->data.top.type = TOP_MEM_RES;
                } else if (strcmp(buf, "mem_vsize") == EQUAL) {
                        obj->data.top.type = TOP_MEM_VSIZE;
+#ifdef IOSTATS
+               } else if (strcmp(buf, "io_read") == EQUAL) {
+                       obj->data.top.type = TOP_READ_BYTES;
+               } else if (strcmp(buf, "io_write") == EQUAL) {
+                       obj->data.top.type = TOP_WRITE_BYTES;
+               } else if (strcmp(buf, "io_perc") == EQUAL) {
+                       obj->data.top.type = TOP_IO_PERC;
+#endif
                } else {
                        ERR("invalid type arg for top");
+#ifdef IOSTATS
+                       ERR("must be one of: name, cpu, pid, mem, time, mem_res, mem_vsize, "
+                                       "io_read, io_write, io_perc");
+#else
                        ERR("must be one of: name, cpu, pid, mem, time, mem_res, mem_vsize");
+#endif
                        return 0;
                }
                if (n < 1 || n > 10) {
@@ -1952,8 +1983,8 @@ static struct text_object *construct_text_object(const char *s,
 #endif /* !__OpenBSD__ */
 
        END
-       /* we have three different types of top (top, top_mem and top_time). To
-        * avoid having almost-same code three times, we have this special
+       /* we have four different types of top (top, top_mem, top_time and top_io). To
+        * avoid having almost-same code four times, we have this special
         * handler. */
        if (strncmp(s, "top", 3) == EQUAL) {
                if (!parse_top_args(s, arg, obj)) {
@@ -5355,8 +5386,8 @@ static void generate_text_internal(char *p, int p_max_size,
                                snprintf(p, p_max_size, "%i", cur->bmpx.bitrate);
                        }
 #endif /* BMPX */
-                       /* we have three different types of top (top, top_mem
-                        * and top_time). To avoid having almost-same code three
+                       /* we have four different types of top (top, top_mem,
+                        * top_time and top_io). To avoid having almost-same code four
                         * times, we have this special handler. */
                        break;
                        case OBJ_top:
@@ -5368,6 +5399,11 @@ static void generate_text_internal(char *p, int p_max_size,
                        case OBJ_top_time:
                                parse_top_args("top_time", obj->data.top.s, obj);
                                if (!needed) needed = cur->time;
+#ifdef IOSTATS
+                       case OBJ_top_io:
+                               parse_top_args("top_io", obj->data.top.s, obj);
+                               if (!needed) needed = cur->io;
+#endif
 
                                if (needed[obj->data.top.num]) {
                                        char *timeval;
@@ -5403,6 +5439,20 @@ static void generate_text_internal(char *p, int p_max_size,
                                                        human_readable(needed[obj->data.top.num]->vsize,
                                                                        p, 255);
                                                        break;
+#ifdef IOSTATS
+                                               case TOP_READ_BYTES:
+                                                       human_readable(needed[obj->data.top.num]->read_bytes / update_interval,
+                                                                       p, 255);
+                                                       break;
+                                               case TOP_WRITE_BYTES:
+                                                       human_readable(needed[obj->data.top.num]->write_bytes / update_interval,
+                                                                       p, 255);
+                                                       break;
+                                               case TOP_IO_PERC:
+                                                       snprintf(p, 7, "%6.2f",
+                                                                       needed[obj->data.top.num]->io_perc);
+                                                       break;
+#endif
                                        }
                                }
                        OBJ(tail)
@@ -7420,6 +7470,9 @@ static void set_default_configurations(void)
        format_human_readable = 1;
        top_mem = 0;
        top_time = 0;
+#ifdef IOSTATS
+       top_io = 0;
+#endif
 #ifdef MPD
        mpd_set_host("localhost");
        mpd_set_port("6600");
index 78f30d6..f28e462 100644 (file)
@@ -265,6 +265,9 @@ struct information {
        struct process *cpu[10];
        struct process *memu[10];
        struct process *time[10];
+#ifdef IOSTATS
+       struct process *io[10];
+#endif
        struct process *first_process;
        unsigned long looped;
        struct entropy_s entropy;
@@ -298,6 +301,9 @@ enum {
 
 /* defined in conky.c, needed by top.c */
 extern int top_cpu, top_mem, top_time;
+#ifdef IOSTATS
+extern int top_io;
+#endif
 
 /* defined in conky.c, needed by top.c */
 extern int cpu_separate;
index 6069b2a..d03d0de 100644 (file)
@@ -2107,7 +2107,11 @@ void get_powerbook_batt_info(char *buffer, size_t n, int i)
 
 void update_top(void)
 {
-       process_find_top(info.cpu, info.memu, info.time);
+       process_find_top(info.cpu, info.memu, info.time
+#ifdef IOSTATS
+                , info.io
+#endif
+                );
        info.first_process = get_first_process();
 }
 
index b5d2bcd..0c1c3f4 100644 (file)
@@ -191,6 +191,9 @@ enum text_object_type {
        OBJ_top,
        OBJ_top_mem,
        OBJ_top_time,
+#ifdef IOSTATS
+       OBJ_top_io,
+#endif
        OBJ_tail,
        OBJ_head,
        OBJ_lines,
index eccd205..ea56168 100644 (file)
--- a/src/top.c
+++ b/src/top.c
@@ -87,6 +87,10 @@ static struct process *new_process(int p)
        process->time_stamp = 0;
        process->previous_user_time = ULONG_MAX;
        process->previous_kernel_time = ULONG_MAX;
+#ifdef IOSTATS
+       process->previous_read_bytes = ULLONG_MAX;
+       process->previous_write_bytes = ULLONG_MAX;
+#endif
        process->counted = 1;
 
        /* process_find_name(process); */
@@ -214,13 +218,85 @@ static int process_parse_stat(struct process *process)
        return 0;
 }
 
+#ifdef IOSTATS
+static int process_parse_io(struct process *process)
+{
+       static const char *read_bytes_str="read_bytes:";
+       static const char *write_bytes_str="write_bytes:";
+
+       char line[BUFFER_LEN] = { 0 }, filename[BUFFER_LEN];
+       int ps;
+       int rc;
+       char *pos, *endpos;
+       unsigned long long read_bytes, write_bytes;
+
+       snprintf(filename, sizeof(filename), PROCFS_TEMPLATE_IO, process->pid);
+
+       ps = open(filename, O_RDONLY);
+       if (ps < 0) {
+               /* The process must have finished in the last few jiffies!
+                * Or, the kernel doesn't support I/O accounting.
+                */
+               return 1;
+       }
+
+       rc = read(ps, line, sizeof(line));
+       close(ps);
+       if (rc < 0) {
+               return 1;
+       }
+
+       pos = strstr(line, read_bytes_str);
+       if (pos == NULL) {
+               /* these should not happen (unless the format of the file changes) */
+               return 1;
+       }
+       pos += strlen(read_bytes_str);
+       process->read_bytes = strtoull(pos, &endpos, 10);
+       if (endpos == pos) {
+               return 1;
+       }
+
+       pos = strstr(line, write_bytes_str);
+       if (pos == NULL) {
+               return 1;
+       }
+       pos += strlen(write_bytes_str);
+       process->write_bytes = strtoull(pos, &endpos, 10);
+       if (endpos == pos) {
+               return 1;
+       }
+
+       if (process->previous_read_bytes == ULLONG_MAX) {
+               process->previous_read_bytes = process->read_bytes;
+       }
+       if (process->previous_write_bytes == ULLONG_MAX) {
+               process->previous_write_bytes = process->write_bytes;
+       }
+
+       /* store the difference of the byte counts */
+       read_bytes = process->read_bytes - process->previous_read_bytes;
+       write_bytes = process->write_bytes - process->previous_write_bytes;
+
+       /* backup the counts for next time around */
+       process->previous_read_bytes = process->read_bytes;
+       process->previous_write_bytes = process->write_bytes;
+
+       /* store only the difference here... */
+       process->read_bytes = read_bytes;
+       process->write_bytes = write_bytes;
+
+       return 0;
+}
+#endif
+
 /******************************************
  * Get process structure for process pid  *
  ******************************************/
 
 /* This function seems to hog all of the CPU time.
  * I can't figure out why - it doesn't do much. */
-static int calculate_cpu(struct process *process)
+static int calculate_stats(struct process *process)
 {
        int rc;
 
@@ -230,6 +306,12 @@ static int calculate_cpu(struct process *process)
                return 1;
        /* rc = process_parse_statm(process); if (rc) return 1; */
 
+#ifdef IOSTATS
+       rc = process_parse_io(process);
+       if (rc)
+               return 1;
+#endif
+
        /*
         * Check name against the exclusion list
         */
@@ -274,7 +356,7 @@ static int update_process_table(void)
                        }
 
                        /* compute each process cpu usage */
-                       calculate_cpu(p);
+                       calculate_stats(p);
                }
        }
 
@@ -395,6 +477,22 @@ inline static void calc_cpu_each(unsigned long long total)
        }
 }
 
+#ifdef IOSTATS
+static void calc_io_each(void)
+{
+       struct process *p;
+       unsigned long long sum = 0;
+       
+       for (p = first_process; p; p = p->next)
+               sum += p->read_bytes + p->write_bytes;
+
+       if(sum == 0)
+               sum = 1; /* to avoid having NANs if no I/O occured */
+       for (p = first_process; p; p = p->next)
+               p->io_perc = 100.0 * (p->read_bytes + p->write_bytes) / (float) sum;
+}
+#endif
+
 /******************************************
  * Find the top processes                                *
  ******************************************/
@@ -446,6 +544,20 @@ static int compare_time(struct process *a, struct process *b)
        return b->total_cpu_time - a->total_cpu_time;
 }
 
+#ifdef IOSTATS
+/* I/O comparision function for insert_sp_element */
+static int compare_io(struct process *a, struct process *b)
+{
+       if (a->io_perc < b->io_perc) {
+               return 1;
+       } else if (a->io_perc > b->io_perc) {
+               return -1;
+       } else {
+               return 0;
+       }
+}
+#endif
+
 /* insert this process into the list in a sorted fashion,
  * or destroy it if it doesn't fit on the list */
 static int insert_sp_element(struct sorted_process *sp_cur,
@@ -524,15 +636,26 @@ static void sp_acopy(struct sorted_process *sp_head, struct process **ar, int ma
  * ****************************************************************** */
 
 void process_find_top(struct process **cpu, struct process **mem,
-               struct process **ptime)
+               struct process **ptime
+#ifdef IOSTATS
+               , struct process **io
+#endif
+               )
 {
        struct sorted_process *spc_head = NULL, *spc_tail = NULL, *spc_cur = NULL;
        struct sorted_process *spm_head = NULL, *spm_tail = NULL, *spm_cur = NULL;
        struct sorted_process *spt_head = NULL, *spt_tail = NULL, *spt_cur = NULL;
+#ifdef IOSTATS
+       struct sorted_process *spi_head = NULL, *spi_tail = NULL, *spi_cur = NULL;
+#endif
        struct process *cur_proc = NULL;
        unsigned long long total = 0;
 
-       if (!top_cpu && !top_mem && !top_time) {
+       if (!top_cpu && !top_mem && !top_time
+#ifdef IOSTATS
+                       && !top_io
+#endif
+          ) {
                return;
        }
 
@@ -540,6 +663,9 @@ void process_find_top(struct process **cpu, struct process **mem,
        update_process_table();         /* update the table with process list */
        calc_cpu_each(total);           /* and then the percentage for each task */
        process_cleanup();                      /* cleanup list from exited processes */
+#ifdef IOSTATS
+       calc_io_each();                 /* percentage of I/O for each task */
+#endif
 
        cur_proc = first_process;
 
@@ -559,6 +685,13 @@ void process_find_top(struct process **cpu, struct process **mem,
                        insert_sp_element(spt_cur, &spt_head, &spt_tail, MAX_SP,
                                &compare_time);
                }
+#ifdef IOSTATS
+               if (top_io) {
+                       spi_cur = malloc_sp(cur_proc);
+                       insert_sp_element(spi_cur, &spi_head, &spi_tail, MAX_SP,
+                               &compare_io);
+               }
+#endif
                cur_proc = cur_proc->next;
        }
 
@@ -568,4 +701,8 @@ void process_find_top(struct process **cpu, struct process **mem,
                sp_acopy(spm_head, mem, MAX_SP);
        if (top_time)
                sp_acopy(spt_head, ptime, MAX_SP);
+#ifdef IOSTATS
+       if (top_io)
+               sp_acopy(spi_head, io,MAX_SP);
+#endif
 }
index 49639d7..279c43b 100644 (file)
--- a/src/top.h
+++ b/src/top.h
@@ -70,6 +70,7 @@
 
 #define PROCFS_TEMPLATE "/proc/%d/stat"
 #define PROCFS_TEMPLATE_MEM "/proc/%d/statm"
+#define PROCFS_TEMPLATE_IO "/proc/%d/io"
 #define PROCFS_CMDLINE_TEMPLATE "/proc/%d/cmdline"
 #define MAX_SP 10      // number of elements to sort
 
@@ -80,7 +81,12 @@ enum top_field {
        TOP_MEM,
        TOP_TIME,
        TOP_MEM_RES,
-       TOP_MEM_VSIZE
+       TOP_MEM_VSIZE,
+#ifdef IOSTATS
+       TOP_READ_BYTES,
+       TOP_WRITE_BYTES,
+       TOP_IO_PERC
+#endif
 };
 
 /******************************************
@@ -103,6 +109,13 @@ struct process {
        unsigned long total_cpu_time;
        unsigned int vsize;
        unsigned int rss;
+#ifdef IOSTATS
+       unsigned long long read_bytes;
+       unsigned long long previous_read_bytes;
+       unsigned long long write_bytes;
+       unsigned long long previous_write_bytes;
+       float io_perc;
+#endif
        unsigned int time_stamp;
        unsigned int counted;
        unsigned int changed;
@@ -116,6 +129,10 @@ struct sorted_process {
 };
 
 /* Pointer to head of process list */
-void process_find_top(struct process **, struct process **, struct process **);
+void process_find_top(struct process **, struct process **, struct process **
+#ifdef IOSTATS
+               , struct process **
+#endif
+               );
 
 #endif /* _top_h_ */