From: Pavel Labath Date: Fri, 12 Jun 2009 17:08:44 +0000 (+0200) Subject: Support for per-task I/O statistics - $top_io X-Git-Url: http://vcs.maemo.org/git/?a=commitdiff_plain;h=c0d1c313e90dd72e0de557fd0ed4d37db1618656;p=monky Support for per-task I/O statistics - $top_io 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. --- diff --git a/configure.ac.in b/configure.ac.in index 9af8792..db80219 100644 --- a/configure.ac.in +++ b/configure.ac.in @@ -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 diff --git a/doc/variables.xml b/doc/variables.xml index b8336c5..e620592 100644 --- a/doc/variables.xml +++ b/doc/variables.xml @@ -2899,8 +2899,8 @@ (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. @@ -2928,6 +2928,17 @@ + + + + + Same as top, except sorted by the amount of I/O the + process has done during the update interval + + + + + diff --git a/src/conky.c b/src/conky.c index 3a7f964..970bb8a 100644 --- a/src/conky.c +++ b/src/conky.c @@ -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"); diff --git a/src/conky.h b/src/conky.h index 78f30d6..f28e462 100644 --- a/src/conky.h +++ b/src/conky.h @@ -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; diff --git a/src/linux.c b/src/linux.c index 6069b2a..d03d0de 100644 --- a/src/linux.c +++ b/src/linux.c @@ -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(); } diff --git a/src/text_object.h b/src/text_object.h index b5d2bcd..0c1c3f4 100644 --- a/src/text_object.h +++ b/src/text_object.h @@ -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, diff --git a/src/top.c b/src/top.c index eccd205..ea56168 100644 --- 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 } diff --git a/src/top.h b/src/top.h index 49639d7..279c43b 100644 --- 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_ */