From 9dc964bb42526d21e1221945ba96a0c87e58d040 Mon Sep 17 00:00:00 2001 From: Benny Baumann Date: Tue, 19 Oct 2021 23:36:31 +0200 Subject: [PATCH] Compress cgroup names based on some heuristics --- Makefile.am | 2 + linux/CGroupUtils.c | 285 +++++++++++++++++++++++++++++++++++++++ linux/CGroupUtils.h | 16 +++ linux/LinuxProcess.c | 7 +- linux/LinuxProcess.h | 1 + linux/LinuxProcessList.c | 20 +++ linux/ProcessField.h | 1 + 7 files changed, 331 insertions(+), 1 deletion(-) create mode 100644 linux/CGroupUtils.c create mode 100644 linux/CGroupUtils.h diff --git a/Makefile.am b/Makefile.am index 7ed500ca..2a9cc29f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -153,6 +153,7 @@ linux_platform_headers = \ generic/gettime.h \ generic/hostname.h \ generic/uname.h \ + linux/CGroupUtils.h \ linux/HugePageMeter.h \ linux/IOPriority.h \ linux/IOPriorityPanel.h \ @@ -174,6 +175,7 @@ linux_platform_sources = \ generic/gettime.c \ generic/hostname.c \ generic/uname.c \ + linux/CGroupUtils.c \ linux/HugePageMeter.c \ linux/IOPriorityPanel.c \ linux/LibSensors.c \ diff --git a/linux/CGroupUtils.c b/linux/CGroupUtils.c new file mode 100644 index 00000000..dd91809a --- /dev/null +++ b/linux/CGroupUtils.c @@ -0,0 +1,285 @@ +/* +htop - CGroupUtils.h +(C) 2021 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "linux/CGroupUtils.h" + +#include "XUtils.h" + + +static bool CGroup_filterName_internal(const char *cgroup, char* buf, size_t bufsize) { + const char* str_slice_suffix = ".slice"; + const char* str_system_slice = "system.slice"; + const char* str_user_slice = "user.slice"; + const char* str_machine_slice = "machine.slice"; + const char* str_user_slice_prefix = "/user-"; + + const char* str_lxc_monitor_prefix = "lxc.monitor."; + const char* str_lxc_payload_prefix = "lxc.payload."; + + const char* str_nspawn_scope_prefix = "machine-"; + const char* str_nspawn_monitor_label = "/supervisor"; + + const char* str_service_suffix = ".service"; + const char* str_scope_suffix = ".scope"; + + while(*cgroup) { + if ('/' == *cgroup) { + while ('/' == *cgroup) + cgroup++; + + if (!bufsize) + return false; + + *buf++ = '/'; + bufsize--; + + continue; + } + + const char* labelStart = cgroup; + const char* nextSlash = strchrnul(labelStart, '/'); + const size_t labelLen = nextSlash - labelStart; + + if (String_startsWith(cgroup, str_system_slice)) { + cgroup += strlen(str_system_slice); + + if (*cgroup && *cgroup != '/') + goto handle_default; + + if (bufsize < 3) + return false; + + *buf++ = '['; + *buf++ = 'S'; + *buf++ = ']'; + bufsize -= 3; + + continue; + } + + if (String_startsWith(cgroup, str_machine_slice)) { + cgroup += strlen(str_machine_slice); + + if (*cgroup && *cgroup != '/') + goto handle_default; + + if (bufsize < 3) + return false; + + *buf++ = '['; + *buf++ = 'M'; + *buf++ = ']'; + bufsize -= 3; + + continue; + } + + if (String_startsWith(cgroup, str_user_slice)) { + cgroup += strlen(str_user_slice); + + if (*cgroup && *cgroup != '/') + goto handle_default; + + if (bufsize < 3) + return false; + + *buf++ = '['; + *buf++ = 'U'; + *buf++ = ']'; + bufsize -= 3; + + if (!String_startsWith(cgroup, str_user_slice_prefix)) + continue; + + const char* userSliceSlash = strchrnul(cgroup + strlen(str_user_slice_prefix), '/'); + const char* sliceSpec = userSliceSlash - strlen(str_slice_suffix); + + if (!String_startsWith(sliceSpec, str_slice_suffix)) + continue; + + const size_t sliceNameLen = sliceSpec - (cgroup + strlen(str_user_slice_prefix)); + + if (bufsize < sliceNameLen + 1) + return false; + + buf[-1] = ':'; + + cgroup += strlen(str_user_slice_prefix); + while(cgroup < sliceSpec) { + *buf++ = *cgroup++; + bufsize--; + } + cgroup = userSliceSlash; + + *buf++ = ']'; + bufsize--; + + continue; + } + + if (labelLen > strlen(str_slice_suffix) && String_startsWith(nextSlash - strlen(str_slice_suffix), str_slice_suffix)) { + const size_t sliceNameLen = labelLen - strlen(str_slice_suffix); + if (bufsize < 2 + sliceNameLen) + return false; + + *buf++ = '['; + bufsize--; + + for(size_t i = sliceNameLen; i; i--) { + *buf++ = *cgroup++; + bufsize--; + } + + *buf++ = ']'; + bufsize--; + + cgroup = nextSlash; + + continue; + } + + if (String_startsWith(cgroup, str_lxc_payload_prefix)) { + const size_t cgroupNameLen = labelLen - strlen(str_lxc_payload_prefix); + if (bufsize < 6 + cgroupNameLen) + return false; + + *buf++ = '['; + *buf++ = 'l'; + *buf++ = 'x'; + *buf++ = 'c'; + *buf++ = ':'; + bufsize -= 5; + + cgroup += strlen(str_lxc_payload_prefix); + while(cgroup < nextSlash) { + *buf++ = *cgroup++; + bufsize--; + } + + *buf++ = ']'; + bufsize--; + + continue; + } + + if (String_startsWith(cgroup, str_lxc_monitor_prefix)) { + const size_t cgroupNameLen = labelLen - strlen(str_lxc_monitor_prefix); + if (bufsize < 6 + cgroupNameLen) + return false; + + *buf++ = '['; + *buf++ = 'L'; + *buf++ = 'X'; + *buf++ = 'C'; + *buf++ = ':'; + bufsize -= 5; + + cgroup += strlen(str_lxc_monitor_prefix); + while(cgroup < nextSlash) { + *buf++ = *cgroup++; + bufsize--; + } + + *buf++ = ']'; + bufsize--; + + continue; + } + + if (labelLen > strlen(str_service_suffix) && String_startsWith(nextSlash - strlen(str_service_suffix), str_service_suffix)) { + const size_t serviceNameLen = labelLen - strlen(str_service_suffix); + + if (String_startsWith(cgroup, "user@")) { + cgroup = nextSlash; + + while(*cgroup == '/') + cgroup++; + + continue; + } + + if (bufsize < serviceNameLen) + return false; + + for(size_t i = serviceNameLen; i; i--) { + *buf++ = *cgroup++; + bufsize--; + } + + cgroup = nextSlash; + + continue; + } + + if (labelLen > strlen(str_scope_suffix) && String_startsWith(nextSlash - strlen(str_scope_suffix), str_scope_suffix)) { + const size_t scopeNameLen = labelLen - strlen(str_scope_suffix); + + if (String_startsWith(cgroup, str_nspawn_scope_prefix)) { + const size_t machineScopeNameLen = scopeNameLen - strlen(str_nspawn_scope_prefix); + if (bufsize < 6 + machineScopeNameLen) + return false; + + const bool is_monitor = String_startsWith(nextSlash, str_nspawn_monitor_label); + + *buf++ = '['; + *buf++ = is_monitor ? 'S' : 's'; + *buf++ = is_monitor ? 'N' : 'n'; + *buf++ = is_monitor ? 'C' : 'c'; + *buf++ = ':'; + bufsize -= 5; + + cgroup += strlen(str_nspawn_scope_prefix); + for(size_t i = machineScopeNameLen; i; i--) { + *buf++ = *cgroup++; + bufsize--; + } + + *buf++ = ']'; + bufsize--; + + cgroup = nextSlash; + + continue; + } + + if (bufsize < 1 + scopeNameLen) + return false; + + *buf++ = '!'; + bufsize--; + + for(size_t i = scopeNameLen; i; i--) { + *buf++ = *cgroup++; + bufsize--; + } + + cgroup = nextSlash; + + continue; + } + +handle_default: + // Default behavior: Copy the full label + cgroup = labelStart; + + if (bufsize < (size_t)(nextSlash - cgroup)) + return false; + + while(cgroup < nextSlash) { + *buf++ = *cgroup++; + bufsize--; + } + } + + return true; +} + +bool CGroup_filterName(const char *cgroup, char* buf, size_t bufsize) { + memset(buf, 0, bufsize); + + return CGroup_filterName_internal(cgroup, buf, bufsize - 1); +} diff --git a/linux/CGroupUtils.h b/linux/CGroupUtils.h new file mode 100644 index 00000000..3df428e4 --- /dev/null +++ b/linux/CGroupUtils.h @@ -0,0 +1,16 @@ +#ifndef HEADER_CGroupUtils +#define HEADER_CGroupUtils +/* +htop - CGroupUtils.h +(C) 2021 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include +#include + + +bool CGroup_filterName(const char *cgroup, char* buf, size_t bufsize); + +#endif /* HEADER_CGroupUtils */ diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index e5f70d77..ba2dbd46 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -81,7 +81,8 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [IO_READ_RATE] = { .name = "IO_READ_RATE", .title = " DISK READ ", .description = "The I/O rate of read(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, [IO_WRITE_RATE] = { .name = "IO_WRITE_RATE", .title = " DISK WRITE ", .description = "The I/O rate of write(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, [IO_RATE] = { .name = "IO_RATE", .title = " DISK R/W ", .description = "Total I/O rate in bytes per second", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, - [CGROUP] = { .name = "CGROUP", .title = "CGROUP ", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, }, + [CGROUP] = { .name = "CGROUP", .title = "CGROUP (raw) ", .description = "Which cgroup the process is in", .flags = PROCESS_FLAG_LINUX_CGROUP, }, + [CCGROUP] = { .name = "CCGROUP", .title = "CGROUP (compressed) ", .description = "Which cgroup the process is in (condensed to essentials)", .flags = PROCESS_FLAG_LINUX_CGROUP, }, [OOM] = { .name = "OOM", .title = " OOM ", .description = "OOM (Out-of-Memory) killer score", .flags = PROCESS_FLAG_LINUX_OOM, .defaultSortDesc = true, }, [IO_PRIORITY] = { .name = "IO_PRIORITY", .title = "IO ", .description = "I/O priority", .flags = PROCESS_FLAG_LINUX_IOPRIO, }, #ifdef HAVE_DELAYACCT @@ -111,6 +112,7 @@ Process* LinuxProcess_new(const Settings* settings) { void Process_delete(Object* cast) { LinuxProcess* this = (LinuxProcess*) cast; Process_done((Process*)cast); + free(this->cgroup_short); free(this->cgroup); #ifdef HAVE_OPENVZ free(this->ctid); @@ -247,6 +249,7 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces case VXID: xSnprintf(buffer, n, "%5u ", lp->vxid); break; #endif case CGROUP: xSnprintf(buffer, n, "%-35.35s ", lp->cgroup ? lp->cgroup : "N/A"); break; + case CCGROUP: xSnprintf(buffer, n, "%-35.35s ", lp->cgroup_short ? lp->cgroup_short : (lp->cgroup ? lp->cgroup : "N/A")); break; case OOM: xSnprintf(buffer, n, "%4u ", lp->oom); break; case IO_PRIORITY: { int klass = IOPriority_class(lp->ioPriority); @@ -370,6 +373,8 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce #endif case CGROUP: return SPACESHIP_NULLSTR(p1->cgroup, p2->cgroup); + case CCGROUP: + return SPACESHIP_NULLSTR(p1->cgroup_short, p2->cgroup_short); case OOM: return SPACESHIP_NUMBER(p1->oom, p2->oom); #ifdef HAVE_DELAYACCT diff --git a/linux/LinuxProcess.h b/linux/LinuxProcess.h index b4e1a8bf..3e5d3804 100644 --- a/linux/LinuxProcess.h +++ b/linux/LinuxProcess.h @@ -89,6 +89,7 @@ typedef struct LinuxProcess_ { unsigned int vxid; #endif char* cgroup; + char* cgroup_short; unsigned int oom; #ifdef HAVE_DELAYACCT unsigned long long int delay_read_time; diff --git a/linux/LinuxProcessList.c b/linux/LinuxProcessList.c index 6b9f988f..74566ab8 100644 --- a/linux/LinuxProcessList.c +++ b/linux/LinuxProcessList.c @@ -45,6 +45,7 @@ in the source distribution for its full text. #include "Process.h" #include "Settings.h" #include "XUtils.h" +#include "linux/CGroupUtils.h" #include "linux/LinuxProcess.h" #include "linux/Platform.h" // needed for GNU/hurd to get PATH_MAX // IWYU pragma: keep @@ -860,6 +861,10 @@ static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t free(process->cgroup); process->cgroup = NULL; } + if (process->cgroup_short) { + free(process->cgroup_short); + process->cgroup_short = NULL; + } return; } char output[PROC_LINE_LENGTH + 1]; @@ -892,7 +897,22 @@ static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t left -= wrote; } fclose(file); + + bool changed = !process->cgroup || !String_eq(process->cgroup, output); + free_and_xStrdup(&process->cgroup, output); + + if (!changed) + return; + + char* cgroup_short = xCalloc(strlen(process->cgroup) + 1, 1); + if (CGroup_filterName(process->cgroup, cgroup_short, strlen(process->cgroup) + 1)) { + free_and_xStrdup(&process->cgroup_short, cgroup_short); + } else { + free(process->cgroup_short); + process->cgroup_short = NULL; + } + free(cgroup_short); } #ifdef HAVE_VSERVER diff --git a/linux/ProcessField.h b/linux/ProcessField.h index 70475929..17cafa96 100644 --- a/linux/ProcessField.h +++ b/linux/ProcessField.h @@ -45,6 +45,7 @@ in the source distribution for its full text. SECATTR = 123, \ AUTOGROUP_ID = 127, \ AUTOGROUP_NICE = 128, \ + CCGROUP = 129, \ // End of list