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.
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
Imlib2: $want_imlib2
ALSA mixer: $want_alsa
apcupsd: $want_apcupsd
+ I/O stats: $want_iostats
EOF
(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>
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;
" * ALSA mixer support\n"
#endif /* MIXER_IS_ALSA */
#ifdef APCUPSD
- " * apcupsd\n"
+ " * apcupsd\n"
#endif /* APCUPSD */
+#ifdef IOSTATS
+ " * iostats\n"
+#endif /* IOSTATS */
);
exit(0);
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;
} 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;
}
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) {
#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)) {
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:
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;
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)
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");
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;
/* 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;
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();
}
OBJ_top,
OBJ_top_mem,
OBJ_top_time,
+#ifdef IOSTATS
+ OBJ_top_io,
+#endif
OBJ_tail,
OBJ_head,
OBJ_lines,
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); */
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;
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
*/
}
/* compute each process cpu usage */
- calculate_cpu(p);
+ calculate_stats(p);
}
}
}
}
+#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 *
******************************************/
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,
* ****************************************************************** */
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;
}
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;
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;
}
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
}
#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
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
};
/******************************************
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;
};
/* 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_ */