From b7b66b76a5f01ac570d537d27a7e156af248d6f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carvalho?= Date: Mon, 4 Dec 2017 00:15:29 -0200 Subject: [PATCH] Adds support for linux delay accounting (#667) Adds support for showing columns with linux delay accounting. This information can be read from the netlink interface, and thus we set up a socket to read from that when initializing the LinuxProcessList (LinuxProcessList_initNetlinkSocket). After that, for each process we call LinuxProcessList_readDelayAcctData, which sends a message thru the socket after setting up a callback to get the answer from the Kernel. That callback sets the process total delay time attribute. We then set the delay percent as the percentage of time process cpu time since last scan. --- configure.ac | 11 ++++ htop.1.in | 9 ++++ linux/LinuxProcess.c | 44 ++++++++++++++- linux/LinuxProcess.h | 20 ++++++- linux/LinuxProcessList.c | 112 +++++++++++++++++++++++++++++++++++++++ linux/LinuxProcessList.h | 15 ++++++ 6 files changed, 209 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index f8af8a96..4b266535 100644 --- a/configure.ac +++ b/configure.ac @@ -256,6 +256,17 @@ then AC_DEFINE(HAVE_SETUID_ENABLED, 1, [Define if setuid support should be enabled.]) fi +AC_ARG_ENABLE(delayacct, [AS_HELP_STRING([--enable-delayacct], [enable linux delay accounting])],, enable_delayacct="no") +if test "x$enable_delayacct" = xyes +then + PKG_CHECK_MODULES(LIBNL3, libnl-3.0, [], [missing_libraries="$missing_libraries libnl-3"]) + PKG_CHECK_MODULES(LIBNL3GENL, libnl-genl-3.0, [], [missing_libraries="$missing_libraries libnl-genl-3"]) + CFLAGS+=" $LIBNL3_CFLAGS $LIBNL3GENL_CFLAGS" + LIBS+=" $LIBNL3_LIBS $LIBNL3GENL_LIBS" + AC_DEFINE(HAVE_DELAYACCT, 1, [Define if delay accounting support should be enabled.]) +fi + + # Bail out on errors. # ---------------------------------------------------------------------- if test ! -z "$missing_libraries"; then diff --git a/htop.1.in b/htop.1.in index 4388be12..cb9adb07 100644 --- a/htop.1.in +++ b/htop.1.in @@ -370,6 +370,15 @@ The I/O scheduling class followed by the priority if the class supports it: \fBB\fR for Best-effort \fBid\fR for Idle .TP +.B PERCENT_CPU_DELAY (CPUD%) +The percentage of time spent waiting for a CPU (while runnable). Requires CAP_NET_ADMIN. +.TP +.B PERCENT_IO_DELAY (IOD%) +The percentage of time spent waiting for the completion of synchronous block I/O. Requires CAP_NET_ADMIN. +.TP +.B PERCENT_SWAP_DELAY (SWAPD%) +The percentage of time spent swapping in pages. Requires CAP_NET_ADMIN. +.TP .B All other flags Currently unsupported (always displays '-'). diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index bb9e99bb..72408cfa 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -81,7 +81,12 @@ typedef enum LinuxProcessFields { #endif OOM = 114, IO_PRIORITY = 115, - LAST_PROCESSFIELD = 116, + #ifdef HAVE_DELAYACCT + PERCENT_CPU_DELAY = 116, + PERCENT_IO_DELAY = 117, + PERCENT_SWAP_DELAY = 118, + #endif + LAST_PROCESSFIELD = 119, } LinuxProcessField; #include "IOPriority.h" @@ -125,6 +130,15 @@ typedef struct LinuxProcess_ { #endif unsigned int oom; char* ttyDevice; + #ifdef HAVE_DELAYACCT + unsigned long long int delay_read_time; + unsigned long long cpu_delay_total; + unsigned long long blkio_delay_total; + unsigned long long swapin_delay_total; + float cpu_delay_percent; + float blkio_delay_percent; + float swapin_delay_percent; + #endif } LinuxProcess; #ifndef Process_isKernelThread @@ -215,6 +229,11 @@ ProcessFieldData Process_fields[] = { #endif [OOM] = { .name = "OOM", .title = " OOM ", .description = "OOM (Out-of-Memory) killer score", .flags = PROCESS_FLAG_LINUX_OOM, }, [IO_PRIORITY] = { .name = "IO_PRIORITY", .title = "IO ", .description = "I/O priority", .flags = PROCESS_FLAG_LINUX_IOPRIO, }, +#ifdef HAVE_DELAYACCT + [PERCENT_CPU_DELAY] = { .name = "PERCENT_CPU_DELAY", .title = "CPUD% ", .description = "CPU delay %", .flags = 0, }, + [PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = "IOD% ", .description = "Block I/O delay %", .flags = 0, }, + [PERCENT_SWAP_DELAY] = { .name = "PERCENT_SWAP_DELAY", .title = "SWAPD% ", .description = "Swapin delay %", .flags = 0, }, +#endif [LAST_PROCESSFIELD] = { .name = "*** report bug! ***", .title = NULL, .description = NULL, .flags = 0, }, }; @@ -287,6 +306,16 @@ bool LinuxProcess_setIOPriority(LinuxProcess* this, IOPriority ioprio) { return (LinuxProcess_updateIOPriority(this) == ioprio); } +#ifdef HAVE_DELAYACCT +void LinuxProcess_printDelay(float delay_percent, char* buffer, int n) { + if (delay_percent == -1LL) { + xSnprintf(buffer, n, " N/A "); + } else { + xSnprintf(buffer, n, "%4.1f ", delay_percent); + } +} +#endif + void LinuxProcess_writeField(Process* this, RichString* str, ProcessField field) { LinuxProcess* lp = (LinuxProcess*) this; bool coloring = this->settings->highlightMegabytes; @@ -360,6 +389,11 @@ void LinuxProcess_writeField(Process* this, RichString* str, ProcessField field) } break; } + #ifdef HAVE_DELAYACCT + case PERCENT_CPU_DELAY: LinuxProcess_printDelay(lp->cpu_delay_percent, buffer, n); break; + case PERCENT_IO_DELAY: LinuxProcess_printDelay(lp->blkio_delay_percent, buffer, n); break; + case PERCENT_SWAP_DELAY: LinuxProcess_printDelay(lp->swapin_delay_percent, buffer, n); break; + #endif default: Process_writeField((Process*)this, str, field); return; @@ -421,6 +455,14 @@ long LinuxProcess_compare(const void* v1, const void* v2) { #endif case OOM: return (p2->oom - p1->oom); + #ifdef HAVE_DELAYACCT + case PERCENT_CPU_DELAY: + return (p2->cpu_delay_percent > p1->cpu_delay_percent ? 1 : -1); + case PERCENT_IO_DELAY: + return (p2->blkio_delay_percent > p1->blkio_delay_percent ? 1 : -1); + case PERCENT_SWAP_DELAY: + return (p2->swapin_delay_percent > p1->swapin_delay_percent ? 1 : -1); + #endif case IO_PRIORITY: return LinuxProcess_effectiveIOPriority(p1) - LinuxProcess_effectiveIOPriority(p2); default: diff --git a/linux/LinuxProcess.h b/linux/LinuxProcess.h index b42808e1..9400d7be 100644 --- a/linux/LinuxProcess.h +++ b/linux/LinuxProcess.h @@ -73,7 +73,12 @@ typedef enum LinuxProcessFields { #endif OOM = 114, IO_PRIORITY = 115, - LAST_PROCESSFIELD = 116, + #ifdef HAVE_DELAYACCT + PERCENT_CPU_DELAY = 116, + PERCENT_IO_DELAY = 117, + PERCENT_SWAP_DELAY = 118, + #endif + LAST_PROCESSFIELD = 119, } LinuxProcessField; #include "IOPriority.h" @@ -117,6 +122,15 @@ typedef struct LinuxProcess_ { #endif unsigned int oom; char* ttyDevice; + #ifdef HAVE_DELAYACCT + unsigned long long int delay_read_time; + unsigned long long cpu_delay_total; + unsigned long long blkio_delay_total; + unsigned long long swapin_delay_total; + float cpu_delay_percent; + float blkio_delay_percent; + float swapin_delay_percent; + #endif } LinuxProcess; #ifndef Process_isKernelThread @@ -152,6 +166,10 @@ IOPriority LinuxProcess_updateIOPriority(LinuxProcess* this); bool LinuxProcess_setIOPriority(LinuxProcess* this, IOPriority ioprio); +#ifdef HAVE_DELAYACCT +void LinuxProcess_printDelay(float delay_percent, char* buffer, int n); +#endif + void LinuxProcess_writeField(Process* this, RichString* str, ProcessField field); long LinuxProcess_compare(const void* v1, const void* v2); diff --git a/linux/LinuxProcessList.c b/linux/LinuxProcessList.c index 4b585e30..6f2631af 100644 --- a/linux/LinuxProcessList.c +++ b/linux/LinuxProcessList.c @@ -27,6 +27,16 @@ in the source distribution for its full text. #include #include +#ifdef HAVE_DELAYACCT +#include +#include +#include +#include +#include +#include +#include +#endif + /*{ #include "ProcessList.h" @@ -72,6 +82,10 @@ typedef struct LinuxProcessList_ { CPUData* cpus; TtyDriver* ttyDrivers; + #ifdef HAVE_DELAYACCT + struct nl_sock *netlink_socket; + int netlink_family; + #endif } LinuxProcessList; #ifndef PROCDIR @@ -192,6 +206,21 @@ static void LinuxProcessList_initTtyDrivers(LinuxProcessList* this) { this->ttyDrivers = ttyDrivers; } +#ifdef HAVE_DELAYACCT + +static void LinuxProcessList_initNetlinkSocket(LinuxProcessList* this) { + this->netlink_socket = nl_socket_alloc(); + if (this->netlink_socket == NULL) { + return; + } + if (nl_connect(this->netlink_socket, NETLINK_GENERIC) < 0) { + return; + } + this->netlink_family = genl_ctrl_resolve(this->netlink_socket, TASKSTATS_GENL_NAME); +} + +#endif + ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidWhiteList, uid_t userId) { LinuxProcessList* this = xCalloc(1, sizeof(LinuxProcessList)); ProcessList* pl = &(this->super); @@ -199,6 +228,10 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidWhiteList, ui LinuxProcessList_initTtyDrivers(this); + #ifdef HAVE_DELAYACCT + LinuxProcessList_initNetlinkSocket(this); + #endif + // Update CPU count: FILE* file = fopen(PROCSTATFILE, "r"); if (file == NULL) { @@ -234,6 +267,12 @@ void ProcessList_delete(ProcessList* pl) { } free(this->ttyDrivers); } + #ifdef HAVE_DELAYACCT + if (this->netlink_socket) { + nl_close(this->netlink_socket); + nl_socket_free(this->netlink_socket); + } + #endif free(this); } @@ -552,6 +591,75 @@ static void LinuxProcessList_readOomData(LinuxProcess* process, const char* dirn fclose(file); } +#ifdef HAVE_DELAYACCT + +static int handleNetlinkMsg(struct nl_msg *nlmsg, void *linuxProcess) { + struct nlmsghdr *nlhdr; + struct nlattr *nlattrs[TASKSTATS_TYPE_MAX + 1]; + struct nlattr *nlattr; + struct taskstats *stats; + int rem; + unsigned long long int timeDelta; + LinuxProcess* lp = (LinuxProcess*) linuxProcess; + + nlhdr = nlmsg_hdr(nlmsg); + + if (genlmsg_parse(nlhdr, 0, nlattrs, TASKSTATS_TYPE_MAX, NULL) < 0) { + return NL_SKIP; + } + + if ((nlattr = nlattrs[TASKSTATS_TYPE_AGGR_PID]) || (nlattr = nlattrs[TASKSTATS_TYPE_NULL])) { + stats = nla_data(nla_next(nla_data(nlattr), &rem)); + assert(lp->super.pid == stats->ac_pid); + timeDelta = (stats->ac_etime*1000 - lp->delay_read_time); + #define BOUNDS(x) isnan(x) ? 0.0 : (x > 100) ? 100.0 : x; + #define DELTAPERC(x,y) BOUNDS((float) (x - y) / timeDelta * 100); + lp->cpu_delay_percent = DELTAPERC(stats->cpu_delay_total, lp->cpu_delay_total); + lp->blkio_delay_percent = DELTAPERC(stats->blkio_delay_total, lp->blkio_delay_total); + lp->swapin_delay_percent = DELTAPERC(stats->swapin_delay_total, lp->swapin_delay_total); + #undef DELTAPERC + #undef BOUNDS + lp->swapin_delay_total = stats->swapin_delay_total; + lp->blkio_delay_total = stats->blkio_delay_total; + lp->cpu_delay_total = stats->cpu_delay_total; + lp->delay_read_time = stats->ac_etime*1000; + } + return NL_OK; +} + +static void LinuxProcessList_readDelayAcctData(LinuxProcessList* this, LinuxProcess* process) { + struct nl_msg *msg; + + if (nl_socket_modify_cb(this->netlink_socket, NL_CB_VALID, NL_CB_CUSTOM, handleNetlinkMsg, process) < 0) { + return; + } + + if (! (msg = nlmsg_alloc())) { + return; + } + + if (! genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, this->netlink_family, 0, NLM_F_REQUEST, TASKSTATS_CMD_GET, TASKSTATS_VERSION)) { + nlmsg_free(msg); + } + + if (nla_put_u32(msg, TASKSTATS_CMD_ATTR_PID, process->super.pid) < 0) { + nlmsg_free(msg); + } + + if (nl_send_sync(this->netlink_socket, msg) < 0) { + process->swapin_delay_percent = -1LL; + process->blkio_delay_percent = -1LL; + process->cpu_delay_percent = -1LL; + return; + } + + if (nl_recvmsgs_default(this->netlink_socket) < 0) { + return; + } +} + +#endif + static void setCommand(Process* process, const char* command, int len) { if (process->comm && process->commLen >= len) { strncpy(process->comm, command, len + 1); @@ -750,6 +858,10 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, const char* } } + #ifdef HAVE_DELAYACCT + LinuxProcessList_readDelayAcctData(this, lp); + #endif + #ifdef HAVE_CGROUP if (settings->flags & PROCESS_FLAG_LINUX_CGROUP) LinuxProcessList_readCGroupFile(lp, dirname, name); diff --git a/linux/LinuxProcessList.h b/linux/LinuxProcessList.h index ce39f805..5005220a 100644 --- a/linux/LinuxProcessList.h +++ b/linux/LinuxProcessList.h @@ -9,6 +9,9 @@ Released under the GNU GPL, see the COPYING file in the source distribution for its full text. */ +#ifdef HAVE_DELAYACCT +#endif + #include "ProcessList.h" @@ -53,6 +56,10 @@ typedef struct LinuxProcessList_ { CPUData* cpus; TtyDriver* ttyDrivers; + #ifdef HAVE_DELAYACCT + struct nl_sock *netlink_socket; + int netlink_family; + #endif } LinuxProcessList; #ifndef PROCDIR @@ -80,6 +87,10 @@ typedef struct LinuxProcessList_ { #define CLAMP(x,low,high) (((x)>(high))?(high):(((x)<(low))?(low):(x))) #endif +#ifdef HAVE_DELAYACCT + +#endif + ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidWhiteList, uid_t userId); void ProcessList_delete(ProcessList* pl); @@ -101,6 +112,10 @@ void ProcessList_delete(ProcessList* pl); #endif +#ifdef HAVE_DELAYACCT + +#endif + void ProcessList_goThroughEntries(ProcessList* super); #endif