diff --git a/ChangeLog b/ChangeLog index b4ecb17e..11feffef 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,10 @@ What's new in version 0.7.1 +* Add support for Linux per-process IO statistics, + enabled with the --enable-taskstats flag, which + requires a kernel compiled with taskstats support. + (thanks to Tobias Oetiker) * Add Unicode support, enabled with the --enable-unicode flag, which requires libncursesw. (thanks to Sergej Pupykin) diff --git a/Process.c b/Process.c index 0b551ebb..95bfbade 100644 --- a/Process.c +++ b/Process.c @@ -49,6 +49,9 @@ typedef enum ProcessField_ { #ifdef HAVE_OPENVZ VEID, VPID, #endif + #ifdef HAVE_TASKSTATS + RCHAR, WCHAR, SYSCR, SYSCW, RBYTES, WBYTES, CNCLWB, IO_READ_RATE, IO_WRITE_RATE, + #endif LAST_PROCESSFIELD } ProcessField; @@ -121,6 +124,19 @@ typedef struct Process_ { unsigned int veid; unsigned int vpid; #endif + #ifdef HAVE_TASKSTATS + unsigned long long io_rchar; + unsigned long long io_wchar; + unsigned long long io_syscr; + unsigned long long io_syscw; + unsigned long long io_read_bytes; + unsigned long long io_write_bytes; + unsigned long long io_cancelled_write_bytes; + double io_rate_read_bps; + unsigned long long io_rate_read_time; + double io_rate_write_bps; + unsigned long long io_rate_write_time; + #endif } Process; }*/ @@ -134,7 +150,10 @@ char* PROCESS_CLASS = "Process"; char *Process_fieldNames[] = { "", "PID", "Command", "STATE", "PPID", "PGRP", "SESSION", "TTY_NR", "TPGID", "FLAGS", "MINFLT", "CMINFLT", "MAJFLT", "CMAJFLT", "UTIME", "STIME", "CUTIME", "CSTIME", "PRIORITY", "NICE", "ITREALVALUE", "STARTTIME", "VSIZE", "RSS", "RLIM", "STARTCODE", "ENDCODE", "STARTSTACK", "KSTKESP", "KSTKEIP", "SIGNAL", "BLOCKED", "SIGIGNORE", "SIGCATCH", "WCHAN", "NSWAP", "CNSWAP", "EXIT_SIGNAL", "PROCESSOR", "M_SIZE", "M_RESIDENT", "M_SHARE", "M_TRS", "M_DRS", "M_LRS", "M_DT", "ST_UID", "PERCENT_CPU", "PERCENT_MEM", "USER", "TIME", "NLWP", "TGID", #ifdef HAVE_OPENVZ -"VEID", "VPID", + "VEID", "VPID", +#endif +#ifdef HAVE_TASKSTATS + "RCHAR", "WCHAR", "SYSCR", "SYSCW", "RBYTES", "WBYTES", "CNCLWB", "IO_READ_RATE", "IO_WRITE_RATE", #endif "*** report bug! ***" }; @@ -142,7 +161,7 @@ char *Process_fieldNames[] = { static int Process_getuid = -1; Process* Process_new(struct ProcessList_ *pl) { - Process* this = malloc(sizeof(Process)); + Process* this = calloc(sizeof(Process), 1); Object_setClass(this, PROCESS_CLASS); ((Object*)this)->display = Process_display; ((Object*)this)->delete = Process_delete; @@ -160,6 +179,19 @@ Process* Process_new(struct ProcessList_ *pl) { Process* Process_clone(Process* this) { Process* clone = malloc(sizeof(Process)); + #if HAVE_TASKSTATS + this->io_rchar = 0; + this->io_wchar = 0; + this->io_syscr = 0; + this->io_syscw = 0; + this->io_read_bytes = 0; + this->io_rate_read_bps = 0; + this->io_rate_read_time = 0; + this->io_write_bytes = 0; + this->io_rate_write_bps = 0; + this->io_rate_write_time = 0; + this->io_cancelled_write_bytes = 0; + #endif memcpy(clone, this, sizeof(Process)); this->comm = NULL; this->pid = 0; @@ -400,11 +432,22 @@ void Process_writeField(Process* this, RichString* str, ProcessField field) { case VEID: snprintf(buffer, n, "%5u ", this->veid); break; case VPID: snprintf(buffer, n, "%5u ", this->vpid); break; #endif + #ifdef HAVE_TASKSTATS + case RCHAR: snprintf(buffer, n, "%10llu ", this->io_rchar); break; + case WCHAR: snprintf(buffer, n, "%10llu ", this->io_wchar); break; + case SYSCR: snprintf(buffer, n, "%10llu ", this->io_syscr); break; + case SYSCW: snprintf(buffer, n, "%10llu ", this->io_syscw); break; + case RBYTES: snprintf(buffer, n, "%10llu ", this->io_read_bytes); break; + case WBYTES: snprintf(buffer, n, "%10llu ", this->io_write_bytes); break; + case CNCLWB: snprintf(buffer, n, "%10llu ", this->io_cancelled_write_bytes); break; + case IO_READ_RATE: Process_printLargeNumber(this, str, this->io_rate_read_bps / 1024); return; + case IO_WRITE_RATE: Process_printLargeNumber(this, str, this->io_rate_write_bps / 1024); return; + #endif + default: snprintf(buffer, n, "- "); } RichString_append(str, attr, buffer); - return; } int Process_pidCompare(const void* v1, const void* v2) { @@ -423,6 +466,7 @@ int Process_compare(const void* v1, const void* v2) { p2 = (Process*)v1; p1 = (Process*)v2; } + long long diff; switch (pl->sortKey) { case PID: return (p1->pid - p2->pid); @@ -470,10 +514,23 @@ int Process_compare(const void* v1, const void* v2) { case VPID: return (p1->vpid - p2->vpid); #endif + #ifdef HAVE_TASKSTATS + case RCHAR: diff = p2->io_rchar - p1->io_rchar; goto test_diff; + case WCHAR: diff = p2->io_wchar - p1->io_wchar; goto test_diff; + case SYSCR: diff = p2->io_syscr - p1->io_syscr; goto test_diff; + case SYSCW: diff = p2->io_syscw - p1->io_syscw; goto test_diff; + case RBYTES: diff = p2->io_read_bytes - p1->io_read_bytes; goto test_diff; + case WBYTES: diff = p2->io_write_bytes - p1->io_write_bytes; goto test_diff; + case CNCLWB: diff = p2->io_cancelled_write_bytes - p1->io_cancelled_write_bytes; goto test_diff; + case IO_READ_RATE: diff = p2->io_rate_read_bps - p1->io_rate_read_bps; goto test_diff; + case IO_WRITE_RATE: diff = p2->io_rate_write_bps - p1->io_rate_write_bps; goto test_diff; + #endif + default: return (p1->pid - p2->pid); } - + test_diff: + return (diff > 0) ? 1 : (diff < 0 ? -1 : 0); } char* Process_printField(ProcessField field) { @@ -509,6 +566,17 @@ char* Process_printField(ProcessField field) { case VEID: return " VEID "; case VPID: return " VPID "; #endif + #ifdef HAVE_TASKSTATS + case RCHAR: return " RD_CHAR "; + case WCHAR: return " WR_CHAR "; + case SYSCR: return " RD_SYSC "; + case SYSCW: return " WR_SYSC "; + case RBYTES: return " IO_RD "; + case WBYTES: return " IO_WR "; + case CNCLWB: return " IO_CANCEL "; + case IO_READ_RATE: return " IORR "; + case IO_WRITE_RATE: return " IOWR "; + #endif default: return "- "; } } diff --git a/Process.h b/Process.h index b107073d..57133380 100644 --- a/Process.h +++ b/Process.h @@ -51,6 +51,9 @@ typedef enum ProcessField_ { #ifdef HAVE_OPENVZ VEID, VPID, #endif + #ifdef HAVE_TASKSTATS + RCHAR, WCHAR, SYSCR, SYSCW, RBYTES, WBYTES, CNCLWB, IO_READ_RATE, IO_WRITE_RATE, + #endif LAST_PROCESSFIELD } ProcessField; @@ -123,6 +126,19 @@ typedef struct Process_ { unsigned int veid; unsigned int vpid; #endif + #ifdef HAVE_TASKSTATS + unsigned long long io_rchar; + unsigned long long io_wchar; + unsigned long long io_syscr; + unsigned long long io_syscw; + unsigned long long io_read_bytes; + unsigned long long io_write_bytes; + unsigned long long io_cancelled_write_bytes; + double io_rate_read_bps; + unsigned long long io_rate_read_time; + double io_rate_write_bps; + unsigned long long io_rate_write_time; + #endif } Process; diff --git a/ProcessList.c b/ProcessList.c index 04ac6f3e..e0d0939c 100644 --- a/ProcessList.c +++ b/ProcessList.c @@ -499,6 +499,50 @@ bool ProcessList_readStatusFile(ProcessList* this, Process* proc, char* dirname, return true; } +#ifdef HAVE_TASKSTATS +void ProcessList_readIoFile(ProcessList* this, Process* proc, char* dirname, char* name) { + char iofilename[MAX_NAME+1]; + iofilename[MAX_NAME] = '\0'; + + char buffer[256]; + buffer[255] = '\0'; + + snprintf(iofilename, MAX_NAME, "%s/%s/io", dirname, name); + FILE* io = ProcessList_fopen(this, iofilename, "r"); + if (io) { + struct timeval tv; + gettimeofday(&tv,NULL); + unsigned long long now = tv.tv_sec*1000+tv.tv_usec/1000; + + unsigned long long last_read = proc->io_read_bytes; + unsigned long long last_write = proc->io_write_bytes; + while (!feof(io)) { + char* ok = fgets(buffer, 255, io); + if (!ok) + break; + if (ProcessList_read(this, buffer, "rchar: %llu", &proc->io_rchar)) continue; + if (ProcessList_read(this, buffer, "wchar: %llu", &proc->io_wchar)) continue; + if (ProcessList_read(this, buffer, "syscr: %llu", &proc->io_syscr)) continue; + if (ProcessList_read(this, buffer, "syscw: %llu", &proc->io_syscw)) continue; + if (ProcessList_read(this, buffer, "read_bytes: %llu", &proc->io_read_bytes)) { + proc->io_rate_read_bps = + ((double)(proc->io_read_bytes - last_read))/(((double)(now - proc->io_rate_read_time))/1000); + proc->io_rate_read_time = now; + continue; + } + if (ProcessList_read(this, buffer, "write_bytes: %llu", &proc->io_write_bytes)) { + proc->io_rate_write_bps = + ((double)(proc->io_write_bytes - last_write))/(((double)(now - proc->io_rate_write_time))/1000); + proc->io_rate_write_time = now; + continue; + } + ProcessList_read(this, buffer, "cancelled_write_bytes: %llu", &proc->io_cancelled_write_bytes); + } + fclose(io); + } +} +#endif + bool ProcessList_processEntries(ProcessList* this, char* dirname, Process* parent, float period) { DIR* dir; struct dirent* entry; @@ -547,6 +591,10 @@ bool ProcessList_processEntries(ProcessList* this, char* dirname, Process* paren } } + #ifdef HAVE_TASKSTATS + ProcessList_readIoFile(this, process, dirname, name); + #endif + if (showUserlandThreads && (!parent || pid != parent->pid)) { char subdirname[MAX_NAME+1]; snprintf(subdirname, MAX_NAME, "%s/%s/task", dirname, name); diff --git a/ProcessList.h b/ProcessList.h index 34bdc46e..a13d3998 100644 --- a/ProcessList.h +++ b/ProcessList.h @@ -165,6 +165,10 @@ void ProcessList_sort(ProcessList* this); bool ProcessList_readStatusFile(ProcessList* this, Process* proc, char* dirname, char* name); +#ifdef HAVE_TASKSTATS +void ProcessList_readIoFile(ProcessList* this, Process* proc, char* dirname, char* name); +#endif + bool ProcessList_processEntries(ProcessList* this, char* dirname, Process* parent, float period); void ProcessList_scan(ProcessList* this); diff --git a/configure.ac b/configure.ac index f82eff9d..a6c1bdc7 100644 --- a/configure.ac +++ b/configure.ac @@ -69,6 +69,11 @@ if test "x$enable_openvz" = xyes; then AC_DEFINE(HAVE_OPENVZ, 1, [Define if openvz support enabled.]) fi +AC_ARG_ENABLE(taskstats, [AC_HELP_STRING([--enable-taskstats], [enable per-task IO Stats (taskstats kernel sup required)])], ,enable_taskstats="no") +if test "x$enable_taskstats" = xyes; then + AC_DEFINE(HAVE_TASKSTATS, 1, [Define if taskstats support enabled.]) +fi + AC_ARG_ENABLE(unicode, [AC_HELP_STRING([--enable-unicode], [enable Unicode support])], ,enable_unicode="no") if test "x$enable_unicode" = xyes; then AC_CHECK_LIB([ncursesw], [refresh], [], [missing_libraries="$missing_libraries libncursesw"])