From a6822e98434cf7da6fab033898094976d881ee0f Mon Sep 17 00:00:00 2001 From: Daniel Lange Date: Fri, 4 Feb 2022 11:23:02 +0100 Subject: New upstream version 3.1.2 --- .github/workflows/build_release.yml | 51 ++++++ ChangeLog | 15 ++ ColorsPanel.c | 2 +- CommandLine.c | 90 ++++++---- CommandLine.h | 5 + Compat.c | 2 +- DiskIOMeter.c | 6 + DisplayOptionsPanel.c | 2 +- Header.c | 2 +- HeaderOptionsPanel.c | 2 +- InfoScreen.c | 2 +- Makefile.am | 2 + NetworkIOMeter.c | 6 + OpenFilesScreen.c | 53 +++++- Process.c | 116 ++++++------ Process.h | 43 +++-- ProcessList.c | 24 ++- ScreenManager.c | 9 +- configure.ac | 2 +- darwin/DarwinProcess.c | 22 +-- darwin/Platform.c | 4 +- darwin/Platform.h | 7 +- dragonflybsd/DragonFlyBSDProcess.c | 4 +- dragonflybsd/DragonFlyBSDProcessList.c | 41 ++--- dragonflybsd/Platform.c | 3 +- dragonflybsd/Platform.h | 7 +- freebsd/FreeBSDProcess.c | 4 +- freebsd/FreeBSDProcessList.c | 23 +-- freebsd/Platform.c | 4 +- freebsd/Platform.h | 7 +- generic/gettime.c | 8 +- htop.1.in | 42 ++++- linux/CGroupUtils.c | 317 +++++++++++++++++++++++++++++++++ linux/CGroupUtils.h | 16 ++ linux/LinuxProcess.c | 17 +- linux/LinuxProcess.h | 4 + linux/LinuxProcessList.c | 95 +++++++--- linux/Platform.c | 254 ++++++++++---------------- linux/Platform.h | 6 +- linux/ProcessField.h | 1 + netbsd/NetBSDProcess.c | 6 +- netbsd/NetBSDProcessList.c | 29 +-- netbsd/Platform.c | 3 +- netbsd/Platform.h | 7 +- openbsd/OpenBSDProcess.c | 6 +- openbsd/OpenBSDProcessList.c | 21 +-- openbsd/Platform.c | 3 +- openbsd/Platform.h | 7 +- pcp/PCPProcess.c | 4 +- pcp/PCPProcessList.c | 31 +++- pcp/Platform.c | 25 +-- pcp/Platform.h | 5 +- solaris/Platform.c | 3 +- solaris/Platform.h | 7 +- solaris/SolarisProcess.c | 4 +- solaris/SolarisProcessList.c | 19 +- unsupported/Platform.c | 3 +- unsupported/Platform.h | 7 +- unsupported/UnsupportedProcess.c | 4 +- unsupported/UnsupportedProcessList.c | 2 +- 60 files changed, 1056 insertions(+), 460 deletions(-) create mode 100644 .github/workflows/build_release.yml create mode 100644 linux/CGroupUtils.c create mode 100644 linux/CGroupUtils.h diff --git a/.github/workflows/build_release.yml b/.github/workflows/build_release.yml new file mode 100644 index 0000000..8c87915 --- /dev/null +++ b/.github/workflows/build_release.yml @@ -0,0 +1,51 @@ +name: Build Source Release + +# Trigger whenever a release is created +on: + release: + types: + - created + +jobs: + build: + name: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + with: + submodules: true + + - name: archive + id: archive + run: | + VERSION=${{ github.event.release.tag_name }} + PKGNAME="htop-$VERSION" + SHASUM=$PKGNAME.tar.xz.sha256 + autoreconf -i + mkdir -p /tmp/$PKGNAME + mv * /tmp/$PKGNAME + mv /tmp/$PKGNAME . + TARBALL=$PKGNAME.tar.xz + tar cJf $TARBALL $PKGNAME + sha256sum $TARBALL > $SHASUM + echo "::set-output name=tarball::$TARBALL" + echo "::set-output name=shasum::$SHASUM" + - name: upload tarball + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ./${{ steps.archive.outputs.tarball }} + asset_name: ${{ steps.archive.outputs.tarball }} + asset_content_type: application/gzip + + - name: upload shasum + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ./${{ steps.archive.outputs.shasum }} + asset_name: ${{ steps.archive.outputs.shasum }} + asset_content_type: text/plain diff --git a/ChangeLog b/ChangeLog index 6f0a7da..a7f92bb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +What's new in version 3.1.2 + +* Bugfix for crash when storing modified settings at exit +* Generate xz-compressed source tarball (with configure) using github actions +* Allow -u UID with numerical value as argument +* Added documentation for obsolete/state libraries/program files highlighting +* Some obsolete/stale library highlighting refinements +* Column width issues resolved +* Dynamic UID column sizing improved +* Discard stale information from Disk and Network I/O meters +* Refined Linux kernel thread detection +* Reworked process state handling +* New CCGROUP column showing abbreviated cgroup name +* New OFFSET column in the list of open files screen + What's new in version 3.1.1 * Update license headers to explicitly say GPLv2+ diff --git a/ColorsPanel.c b/ColorsPanel.c index 0ea2158..50188f6 100644 --- a/ColorsPanel.c +++ b/ColorsPanel.c @@ -52,7 +52,7 @@ static HandlerResult ColorsPanel_eventHandler(Panel* super, int ch) { HandlerResult result = IGNORED; int mark; - switch(ch) { + switch (ch) { case 0x0a: case 0x0d: case KEY_ENTER: diff --git a/CommandLine.c b/CommandLine.c index a352e4e..b2ad06e 100644 --- a/CommandLine.c +++ b/CommandLine.c @@ -11,6 +11,7 @@ in the source distribution for its full text. #include "CommandLine.h" #include +#include #include #include #include @@ -85,9 +86,9 @@ typedef struct CommandLineSettings_ { bool readonly; } CommandLineSettings; -static CommandLineSettings parseArguments(const char* program, int argc, char** argv) { +static CommandLineStatus parseArguments(const char* program, int argc, char** argv, CommandLineSettings* flags) { - CommandLineSettings flags = { + *flags = (CommandLineSettings) { .pidMatchList = NULL, .commFilter = NULL, .userId = (uid_t)-1, // -1 is guaranteed to be an invalid uid_t (see setreuid(2)) @@ -130,10 +131,10 @@ static CommandLineSettings parseArguments(const char* program, int argc, char** switch (opt) { case 'h': printHelpFlag(program); - exit(0); + return STATUS_OK_EXIT; case 'V': printVersionFlag(program); - exit(0); + return STATUS_OK_EXIT; case 's': assert(optarg); /* please clang analyzer, cause optarg can be NULL in the 'u' case */ if (String_eq(optarg, "help")) { @@ -142,29 +143,29 @@ static CommandLineSettings parseArguments(const char* program, int argc, char** const char* description = Process_fields[j].description; if (name) printf("%19s %s\n", name, description); } - exit(0); + return STATUS_OK_EXIT; } - flags.sortKey = 0; + flags->sortKey = 0; for (int j = 1; j < LAST_PROCESSFIELD; j++) { if (Process_fields[j].name == NULL) continue; if (String_eq(optarg, Process_fields[j].name)) { - flags.sortKey = j; + flags->sortKey = j; break; } } - if (flags.sortKey == 0) { + if (flags->sortKey == 0) { fprintf(stderr, "Error: invalid column \"%s\".\n", optarg); - exit(1); + return STATUS_ERROR_EXIT; } break; case 'd': - if (sscanf(optarg, "%16d", &(flags.delay)) == 1) { - if (flags.delay < 1) flags.delay = 1; - if (flags.delay > 100) flags.delay = 100; + if (sscanf(optarg, "%16d", &(flags->delay)) == 1) { + if (flags->delay < 1) flags->delay = 1; + if (flags->delay > 100) flags->delay = 100; } else { fprintf(stderr, "Error: invalid delay value \"%s\".\n", optarg); - exit(1); + return STATUS_ERROR_EXIT; } break; case 'u': @@ -176,26 +177,30 @@ static CommandLineSettings parseArguments(const char* program, int argc, char** } if (!username) { - flags.userId = geteuid(); - } else if (!Action_setUserOnly(username, &(flags.userId))) { - fprintf(stderr, "Error: invalid user \"%s\".\n", username); - exit(1); + flags->userId = geteuid(); + } else if (!Action_setUserOnly(username, &(flags->userId))) { + for (const char *itr = username; *itr; ++itr) + if (!isdigit((unsigned char)*itr)) { + fprintf(stderr, "Error: invalid user \"%s\".\n", username); + return STATUS_ERROR_EXIT; + } + flags->userId = atol(username); } break; } case 'C': - flags.useColors = false; + flags->useColors = false; break; case 'M': #ifdef HAVE_GETMOUSE - flags.enableMouse = false; + flags->enableMouse = false; #endif break; case 'U': - flags.allowUnicode = false; + flags->allowUnicode = false; break; case 't': - flags.treeView = true; + flags->treeView = true; break; case 'p': { assert(optarg); /* please clang analyzer, cause optarg can be NULL in the 'u' case */ @@ -203,14 +208,14 @@ static CommandLineSettings parseArguments(const char* program, int argc, char** char* saveptr; const char* pid = strtok_r(argCopy, ",", &saveptr); - if (!flags.pidMatchList) { - flags.pidMatchList = Hashtable_new(8, false); + if (!flags->pidMatchList) { + flags->pidMatchList = Hashtable_new(8, false); } while(pid) { unsigned int num_pid = atoi(pid); - // deepcode ignore CastIntegerToAddress: we just want a non-NUll pointer here - Hashtable_put(flags.pidMatchList, num_pid, (void *) 1); + // deepcode ignore CastIntegerToAddress: we just want a non-NULL pointer here + Hashtable_put(flags->pidMatchList, num_pid, (void *) 1); pid = strtok_r(NULL, ",", &saveptr); } free(argCopy); @@ -219,7 +224,7 @@ static CommandLineSettings parseArguments(const char* program, int argc, char** } case 'F': { assert(optarg); - free_and_xStrdup(&flags.commFilter, optarg); + free_and_xStrdup(&flags->commFilter, optarg); break; } case 'H': { @@ -229,28 +234,30 @@ static CommandLineSettings parseArguments(const char* program, int argc, char** delay = argv[optind++]; } if (delay) { - if (sscanf(delay, "%16d", &(flags.highlightDelaySecs)) == 1) { - if (flags.highlightDelaySecs < 1) - flags.highlightDelaySecs = 1; + if (sscanf(delay, "%16d", &(flags->highlightDelaySecs)) == 1) { + if (flags->highlightDelaySecs < 1) + flags->highlightDelaySecs = 1; } else { fprintf(stderr, "Error: invalid highlight delay value \"%s\".\n", delay); - exit(1); + return STATUS_ERROR_EXIT; } } - flags.highlightChanges = true; + flags->highlightChanges = true; break; } case 128: - flags.readonly = true; + flags->readonly = true; break; - default: - if (Platform_getLongOption(opt, argc, argv) == false) - exit(1); - break; + default: { + CommandLineStatus status; + if ((status = Platform_getLongOption(opt, argc, argv)) != STATUS_OK) + return status; + break; + } } } - return flags; + return STATUS_OK; } static void CommandLine_delay(ProcessList* pl, unsigned long millisec) { @@ -283,12 +290,17 @@ int CommandLine_run(const char* name, int argc, char** argv) { else setlocale(LC_CTYPE, ""); - CommandLineSettings flags = parseArguments(name, argc, argv); + CommandLineStatus status = STATUS_OK; + CommandLineSettings flags = { 0 }; + + if ((status = parseArguments(name, argc, argv, &flags)) != STATUS_OK) + return status != STATUS_OK_EXIT ? 1 : 0; if (flags.readonly) Settings_enableReadonly(); - Platform_init(); + if (!Platform_init()) + return 1; Process_setupColumnWidths(); diff --git a/CommandLine.h b/CommandLine.h index 99579f5..fbdede8 100644 --- a/CommandLine.h +++ b/CommandLine.h @@ -8,6 +8,11 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +typedef enum { + STATUS_OK, + STATUS_ERROR_EXIT, + STATUS_OK_EXIT +} CommandLineStatus; int CommandLine_run(const char* name, int argc, char** argv); diff --git a/Compat.c b/Compat.c index 2d06c48..8c88138 100644 --- a/Compat.c +++ b/Compat.c @@ -44,7 +44,7 @@ int Compat_faccessat(int dirfd, // Fallback to stat(2)/lstat(2) depending on flags struct stat statinfo; - if(flags) { + if (flags) { ret = lstat(pathname, &statinfo); } else { ret = stat(pathname, &statinfo); diff --git a/DiskIOMeter.c b/DiskIOMeter.c index c5f1476..11fb791 100644 --- a/DiskIOMeter.c +++ b/DiskIOMeter.c @@ -81,6 +81,12 @@ static void DiskIOMeter_updateValues(Meter* this) { cached_msTimeSpend_total = data.totalMsTimeSpend; } + if (passedTimeInMs > 30000) { + // Triggers for the first initialization and + // when there was a long time we did not collect updates + hasData = false; + } + this->values[0] = cached_utilisation_diff; this->total = MAXIMUM(this->values[0], 100.0); /* fix total after (initial) spike */ diff --git a/DisplayOptionsPanel.c b/DisplayOptionsPanel.c index 516fb50..8212120 100644 --- a/DisplayOptionsPanel.c +++ b/DisplayOptionsPanel.c @@ -107,7 +107,7 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager* Panel_add(super, (Object*) CheckItem_newByRef("Show custom thread names", &(settings->showThreadNames))); Panel_add(super, (Object*) CheckItem_newByRef("Show program path", &(settings->showProgramPath))); Panel_add(super, (Object*) CheckItem_newByRef("Highlight program \"basename\"", &(settings->highlightBaseName))); - Panel_add(super, (Object*) CheckItem_newByRef("Highlight out-dated/removed programs", &(settings->highlightDeletedExe))); + Panel_add(super, (Object*) CheckItem_newByRef("Highlight out-dated/removed programs (red) / libraries (yellow)", &(settings->highlightDeletedExe))); Panel_add(super, (Object*) CheckItem_newByRef("Merge exe, comm and cmdline in Command", &(settings->showMergedCommand))); Panel_add(super, (Object*) CheckItem_newByRef("- Try to find comm in cmdline (when Command is merged)", &(settings->findCommInCmdline))); Panel_add(super, (Object*) CheckItem_newByRef("- Try to strip exe from cmdline (when Command is merged)", &(settings->stripExeFromCmdline))); diff --git a/Header.c b/Header.c index 1652520..c557a45 100644 --- a/Header.c +++ b/Header.c @@ -145,7 +145,7 @@ void Header_writeBackToSettings(const Header* this) { const Vector* vec = this->columns[col]; int len = Vector_size(vec); - colSettings->names = len ? xCalloc(len, sizeof(char*)) : NULL; + colSettings->names = len ? xCalloc(len + 1, sizeof(char*)) : NULL; colSettings->modes = len ? xCalloc(len, sizeof(int)) : NULL; colSettings->len = len; diff --git a/HeaderOptionsPanel.c b/HeaderOptionsPanel.c index d8148df..22bcd09 100644 --- a/HeaderOptionsPanel.c +++ b/HeaderOptionsPanel.c @@ -35,7 +35,7 @@ static HandlerResult HeaderOptionsPanel_eventHandler(Panel* super, int ch) { HandlerResult result = IGNORED; int mark; - switch(ch) { + switch (ch) { case 0x0a: case 0x0d: case KEY_ENTER: diff --git a/InfoScreen.c b/InfoScreen.c index a62b7c0..f431f79 100644 --- a/InfoScreen.c +++ b/InfoScreen.c @@ -135,7 +135,7 @@ void InfoScreen_run(InfoScreen* this) { continue; } - switch(ch) { + switch (ch) { case ERR: continue; case KEY_F(3): diff --git a/Makefile.am b/Makefile.am index 7ed500c..2a9cc29 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/NetworkIOMeter.c b/NetworkIOMeter.c index dcba78d..6c429c3 100644 --- a/NetworkIOMeter.c +++ b/NetworkIOMeter.c @@ -85,6 +85,12 @@ static void NetworkIOMeter_updateValues(Meter* this) { cached_txp_total = data.packetsTransmitted; } + if (passedTimeInMs > 30000) { + // Triggers for the first initialization and + // when there was a long time we did not collect updates + hasData = false; + } + this->values[0] = cached_rxb_diff; this->values[1] = cached_txb_diff; if (cached_rxb_diff + cached_txb_diff > this->total) { diff --git a/OpenFilesScreen.c b/OpenFilesScreen.c index e0bede0..34367eb 100644 --- a/OpenFilesScreen.c +++ b/OpenFilesScreen.c @@ -10,12 +10,14 @@ in the source distribution for its full text. #include "OpenFilesScreen.h" #include +#include #include #include #include #include #include #include +#include #include "Macros.h" #include "Panel.h" @@ -25,7 +27,7 @@ in the source distribution for its full text. typedef struct OpenFiles_Data_ { - char* data[7]; + char* data[8]; } OpenFiles_Data; typedef struct OpenFiles_ProcessData_ { @@ -55,6 +57,8 @@ static size_t getIndexForType(char type) { return 5; case 't': return 6; + case 'o': + return 7; } /* should never reach here */ @@ -74,7 +78,7 @@ OpenFilesScreen* OpenFilesScreen_new(const Process* process) { } else { this->pid = process->pid; } - return (OpenFilesScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, " FD TYPE MODE DEVICE SIZE NODE NAME"); + return (OpenFilesScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, " FD TYPE MODE DEVICE SIZE OFFSET NODE NAME"); } void OpenFilesScreen_delete(Object* this) { @@ -115,13 +119,14 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) { close(fdnull); char buffer[32] = {0}; xSnprintf(buffer, sizeof(buffer), "%d", pid); - execlp("lsof", "lsof", "-P", "-p", buffer, "-F", NULL); + execlp("lsof", "lsof", "-P", "-o", "-p", buffer, "-F", NULL); exit(127); } close(fdpair[1]); OpenFiles_Data* item = &(pdata->data); OpenFiles_FileData* fdata = NULL; + bool lsofIncludesFileSize = false; FILE* fd = fdopen(fdpair[0], "r"); if (!fd) { @@ -155,8 +160,17 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) { case 't': /* file's type */ { size_t index = getIndexForType(cmd); - free(item->data[index]); - item->data[index] = xStrdup(line + 1); + free_and_xStrdup(&item->data[index], line + 1); + break; + } + case 'o': /* file's offset */ + { + size_t index = getIndexForType(cmd); + if (String_startsWith(line + 1, "0t")) { + free_and_xStrdup(&item->data[index], line + 3); + } else { + free_and_xStrdup(&item->data[index], line + 1); + } break; } case 'c': /* process command name */ @@ -166,7 +180,6 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) { case 'k': /* link count */ case 'l': /* file's lock status */ case 'L': /* process login name */ - case 'o': /* file's offset */ case 'p': /* process ID */ case 'P': /* protocol name */ case 'R': /* parent process ID */ @@ -175,6 +188,10 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) { /* ignore */ break; } + + if (cmd == 's') + lsofIncludesFileSize = true; + free(line); } fclose(fd); @@ -191,6 +208,25 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) { pdata->error = WEXITSTATUS(wstatus); } + /* We got all information we need; no post-processing needed */ + if (lsofIncludesFileSize) + return pdata; + + /* On linux, `lsof -o -F` omits SIZE, so add it back. */ + /* On macOS, `lsof -o -F` includes SIZE, so this block isn't needed. If no open files have a filesize, this will still run, unfortunately. */ + size_t fileSizeIndex = getIndexForType('s'); + for (fdata = pdata->files; fdata != NULL; fdata = fdata->next) { + item = &fdata->data; + const char* filename = getDataForType(item, 'n'); + + struct stat st; + if (stat(filename, &st) == 0) { + char fileSizeBuf[21]; /* 20 (long long) + 1 (NULL) */ + xSnprintf(fileSizeBuf, sizeof(fileSizeBuf), "%"PRIu64, st.st_size); /* st.st_size is long long on macOS, long on linux */ + free_and_xStrdup(&item->data[fileSizeIndex], fileSizeBuf); + } + } + return pdata; } @@ -213,14 +249,15 @@ static void OpenFilesScreen_scan(InfoScreen* this) { while (fdata) { OpenFiles_Data* data = &fdata->data; size_t lenN = strlen(getDataForType(data, 'n')); - size_t sizeEntry = 5 + 7 + 4 + 10 + 10 + 10 + lenN + 7 /*spaces*/ + 1 /*null*/; + size_t sizeEntry = 5 + 7 + 4 + 10 + 10 + 10 + 10 + lenN + 8 /*spaces*/ + 1 /*null*/; char entry[sizeEntry]; - xSnprintf(entry, sizeof(entry), "%5.5s %-7.7s %-4.4s %-10.10s %10.10s %10.10s %s", + xSnprintf(entry, sizeof(entry), "%5.5s %-7.7s %-4.4s %-10.10s %10.10s %10.10s %10.10s %s", getDataForType(data, 'f'), getDataForType(data, 't'), getDataForType(data, 'a'), getDataForType(data, 'D'), getDataForType(data, 's'), + getDataForType(data, 'o'), getDataForType(data, 'i'), getDataForType(data, 'n')); InfoScreen_addLine(this, entry); diff --git a/Process.c b/Process.c index f0fc67f..6be36b7 100644 --- a/Process.c +++ b/Process.c @@ -41,17 +41,33 @@ static const char* const kthreadID = "KTHREAD"; static uid_t Process_getuid = (uid_t)-1; -int Process_pidDigits = 7; +int Process_pidDigits = PROCESS_MIN_PID_DIGITS; +int Process_uidDigits = PROCESS_MIN_UID_DIGITS; void Process_setupColumnWidths() { int maxPid = Platform_getMaxPid(); if (maxPid == -1) return; + if (maxPid < (int)pow(10, PROCESS_MIN_PID_DIGITS)) { + Process_pidDigits = PROCESS_MIN_PID_DIGITS; + return; + } + Process_pidDigits = ceil(log10(maxPid)); assert(Process_pidDigits <= PROCESS_MAX_PID_DIGITS); } +void Process_setUidColumnWidth(uid_t maxUid) { + if (maxUid < (uid_t)pow(10, PROCESS_MIN_UID_DIGITS)) { + Process_uidDigits = PROCESS_MIN_UID_DIGITS; + return; + } + + Process_uidDigits = ceil(log10(maxUid)); + assert(Process_uidDigits <= PROCESS_MAX_UID_DIGITS); +} + void Process_printBytes(RichString* str, unsigned long long number, bool coloring) { char buffer[16]; int len; @@ -399,7 +415,7 @@ void Process_makeCommandStr(Process* this) { * - a user thread and showThreadNames is not set */ if (Process_isKernelThread(this)) return; - if (this->state == 'Z' && !this->mergedCommand.str) + if (this->state == ZOMBIE && !this->mergedCommand.str) return; if (Process_isUserlandThread(this) && settings->showThreadNames && (showThreadNames == mc->prevShowThreadNames)) return; @@ -733,6 +749,28 @@ void Process_printPercentage(float val, char* buffer, int n, int* attr) { } } +static inline char processStateChar(ProcessState state) { + switch (state) { + case UNKNOWN: return '?'; + case RUNNABLE: return 'U'; + case RUNNING: return 'R'; + case QUEUED: return 'Q'; + case WAITING: return 'W'; + case UNINTERRUPTIBLE_WAIT: return 'D'; + case BLOCKED: return 'B'; + case PAGING: return 'P'; + case STOPPED: return 'T'; + case TRACED: return 't'; + case ZOMBIE: return 'Z'; + case DEFUNCT: return 'X'; + case IDLE: return 'I'; + case SLEEPING: return 'S'; + default: + assert(0); + return '!'; + } +} + void Process_writeField(const Process* this, RichString* str, ProcessField field) { char buffer[256]; size_t n = sizeof(buffer); @@ -867,25 +905,35 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field case SESSION: xSnprintf(buffer, n, "%*d ", Process_pidDigits, this->session); break; case STARTTIME: xSnprintf(buffer, n, "%s", this->starttime_show); break; case STATE: - xSnprintf(buffer, n, "%c ", this->state); + xSnprintf(buffer, n, "%c ", processStateChar(this->state)); switch (this->state) { -#ifdef HTOP_NETBSD - case 'P': -#else - case 'R': -#endif + case RUNNABLE: + case RUNNING: + case TRACED: attr = CRT_colors[PROCESS_RUN_STATE]; break; - case 'D': + + case BLOCKED: + case DEFUNCT: + case STOPPED: + case ZOMBIE: attr = CRT_colors[PROCESS_D_STATE]; break; - case 'I': - case 'S': + + case QUEUED: + case WAITING: + case UNINTERRUPTIBLE_WAIT: + case IDLE: + case SLEEPING: attr = CRT_colors[PROCESS_SHADOW]; break; + + case UNKNOWN: + case PAGING: + break; } break; - case ST_UID: xSnprintf(buffer, n, "%5d ", this->st_uid); break; + case ST_UID: xSnprintf(buffer, n, "%*d ", Process_uidDigits, this->st_uid); break; case TIME: Process_printTime(str, this->time, coloring); return; case TGID: if (this->tgid == this->pid) @@ -908,11 +956,11 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field attr = CRT_colors[PROCESS_SHADOW]; if (this->user) { - Process_printLeftAlignedField(str, attr, this->user, 9); + Process_printLeftAlignedField(str, attr, this->user, 10); return; } - xSnprintf(buffer, n, "%-9d ", this->st_uid); + xSnprintf(buffer, n, "%-10d ", this->st_uid); break; default: if (DynamicColumn_writeField(this, str, field)) @@ -1056,44 +1104,6 @@ int Process_compare(const void* v1, const void* v2) { return (Settings_getActiveDirection(settings) == 1) ? result : -result; } -static uint8_t stateCompareValue(char state) { - switch (state) { - - case 'S': - return 10; - - case 'I': - return 9; - - case 'X': - return 8; - - case 'Z': - return 7; - - case 't': - return 6; - - case 'T': - return 5; - - case 'L': - return 4; - - case 'D': - return 3; - - case 'R': - return 2; - - case '?': - return 1; - - default: - return 0; - } -} - int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key) { int r; @@ -1148,7 +1158,7 @@ int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField r = SPACESHIP_NUMBER(p1->starttime_ctime, p2->starttime_ctime); return r != 0 ? r : SPACESHIP_NUMBER(p1->pid, p2->pid); case STATE: - return SPACESHIP_NUMBER(stateCompareValue(p1->state), stateCompareValue(p2->state)); + return SPACESHIP_NUMBER(p1->state, p2->state); case ST_UID: return SPACESHIP_NUMBER(p1->st_uid, p2->st_uid); case TIME: diff --git a/Process.h b/Process.h index 4080d98..26f6434 100644 --- a/Process.h +++ b/Process.h @@ -60,6 +60,26 @@ typedef enum ProcessField_ { LAST_PROCESSFIELD } ProcessField; +/* Core process states (shared by platforms) + * NOTE: The enum has an ordering that is important! + * See processStateChar in process.c for ProcessSate -> letter mapping */ +typedef enum ProcessState_ { + UNKNOWN = 1, + RUNNABLE, + RUNNING, + QUEUED, + WAITING, + UNINTERRUPTIBLE_WAIT, + BLOCKED, + PAGING, + STOPPED, + TRACED, + ZOMBIE, + DEFUNCT, + IDLE, + SLEEPING +} ProcessState; + struct Settings_; /* Holds information about regions of the cmdline that should be @@ -202,20 +222,8 @@ typedef struct Process_ { /* Number of major faults the process has made which have required loading a memory page from disk */ unsigned long int majflt; - /* - * Process state (platform dependent): - * D - Waiting - * I - Idle - * L - Acquiring lock - * R - Running - * S - Sleeping - * T - Stopped (on a signal) - * X - Dead - * Z - Zombie - * t - Tracing stop - * ? - Unknown - */ - char state; + /* Process state enum field (platform dependent) */ + ProcessState state; /* Whether the process was updated during the current scan */ bool updated; @@ -278,8 +286,12 @@ void Process_writeField(const Process* this, RichString* str, ProcessField field int Process_compare(const void* v1, const void* v2); void Process_delete(Object* cast); extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD]; +#define PROCESS_MIN_PID_DIGITS 5 #define PROCESS_MAX_PID_DIGITS 19 +#define PROCESS_MIN_UID_DIGITS 5 +#define PROCESS_MAX_UID_DIGITS 19 extern int Process_pidDigits; +extern int Process_uidDigits; typedef Process* (*Process_New)(const struct Settings_*); typedef void (*Process_WriteField)(const Process*, RichString*, ProcessField); @@ -337,6 +349,9 @@ static inline bool Process_isThread(const Process* this) { void Process_setupColumnWidths(void); +/* Sets the size of the UID column based on the passed UID */ +void Process_setUidColumnWidth(uid_t maxUid); + /* Takes number in bytes (base 1024). Prints 6 columns. */ void Process_printBytes(RichString* str, unsigned long long number, bool coloring); diff --git a/ProcessList.c b/ProcessList.c index 1ce6b76..c4c759d 100644 --- a/ProcessList.c +++ b/ProcessList.c @@ -105,13 +105,19 @@ static const char* alignedProcessFieldTitle(const ProcessList* this, ProcessFiel if (!title) return "- "; - if (!Process_fields[field].pidColumn) - return title; + if (Process_fields[field].pidColumn) { + static char titleBuffer[PROCESS_MAX_PID_DIGITS + sizeof(" ")]; + xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_pidDigits, title); + return titleBuffer; + } - static char titleBuffer[PROCESS_MAX_PID_DIGITS + /* space */ 1 + /* null-terminator */ + 1]; - xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_pidDigits, title); + if (field == ST_UID) { + static char titleBuffer[PROCESS_MAX_UID_DIGITS + sizeof(" ")]; + xSnprintf(titleBuffer, sizeof(titleBuffer), "%*s ", Process_uidDigits, title); + return titleBuffer; + } - return titleBuffer; + return title; } void ProcessList_printHeader(const ProcessList* this, RichString* header) { @@ -626,10 +632,15 @@ void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) { ProcessList_goThroughEntries(this, false); + uid_t maxUid = 0; for (int i = Vector_size(this->processes) - 1; i >= 0; i--) { Process* p = (Process*) Vector_get(this->processes, i); Process_makeCommandStr(p); + // keep track of the highest UID for column scaling + if (p->st_uid > maxUid) + maxUid = p->st_uid; + if (p->tombStampMs > 0) { // remove tombed process if (this->monotonicMs >= p->tombStampMs) { @@ -647,6 +658,9 @@ void ProcessList_scan(ProcessList* this, bool pauseProcessUpdate) { } } + // Set UID column width based on max UID. + Process_setUidColumnWidth(maxUid); + if (this->settings->treeView) { // Clear out the hashtable to avoid any left-over processes from previous build // diff --git a/ScreenManager.c b/ScreenManager.c index 54dec64..96e9c47 100644 --- a/ScreenManager.c +++ b/ScreenManager.c @@ -88,7 +88,7 @@ void ScreenManager_resize(ScreenManager* this) { Panel_move(panel, lastX, y1_header); } -static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTimeout, bool* redraw, bool* rescan, bool* timedOut) { +static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTimeout, bool* redraw, bool* rescan, bool* timedOut, bool *force_redraw) { ProcessList* pl = this->header->pl; Platform_gettime_realtime(&pl->realtime, &pl->realtimeMs); @@ -103,6 +103,7 @@ static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTi if (*rescan) { *oldTime = newTime; + int oldUidDigits = Process_uidDigits; // scan processes first - some header values are calculated there ProcessList_scan(pl, this->state->pauseProcessUpdate); // always update header, especially to avoid gaps in graph meters @@ -111,6 +112,10 @@ static void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTi ProcessList_sort(pl); *sortTimeout = 1; } + // force redraw if the number of UID digits was changed + if (Process_uidDigits != oldUidDigits) { + *force_redraw = true; + } *redraw = true; } if (*redraw) { @@ -153,7 +158,7 @@ void ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey) { while (!quit) { if (this->header) { - checkRecalculation(this, &oldTime, &sortTimeout, &redraw, &rescan, &timedOut); + checkRecalculation(this, &oldTime, &sortTimeout, &redraw, &rescan, &timedOut, &force_redraw); } if (redraw || force_redraw) { diff --git a/configure.ac b/configure.ac index 1b72222..0e69096 100644 --- a/configure.ac +++ b/configure.ac @@ -6,7 +6,7 @@ # ---------------------------------------------------------------------- AC_PREREQ([2.69]) -AC_INIT([htop], [3.1.1], [htop@groups.io], [], [https://htop.dev/]) +AC_INIT([htop], [3.1.2], [htop@groups.io], [], [https://htop.dev/]) AC_CONFIG_SRCDIR([htop.c]) AC_CONFIG_AUX_DIR([build-aux]) diff --git a/darwin/DarwinProcess.c b/darwin/DarwinProcess.c index 62b6651..95ae960 100644 --- a/darwin/DarwinProcess.c +++ b/darwin/DarwinProcess.c @@ -37,11 +37,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, }, [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, }, [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, }, - [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, }, + [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, }, [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, }, [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, }, [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, }, - [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, + [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, }, [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, }, [TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, }, @@ -331,7 +331,7 @@ void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, proc->nice = ep->p_nice; proc->priority = ep->p_priority; - proc->state = (ep->p_stat == SZOMB) ? 'Z' : '?'; + proc->state = (ep->p_stat == SZOMB) ? ZOMBIE : UNKNOWN; /* Make sure the updated flag is set */ proc->updated = true; @@ -386,7 +386,7 @@ void DarwinProcess_scanThreads(DarwinProcess* dp) { return; } - if (proc->state == 'Z') { + if (proc->state == ZOMBIE) { return; } @@ -430,15 +430,15 @@ void DarwinProcess_scanThreads(DarwinProcess* dp) { vm_deallocate(mach_task_self(), (vm_address_t) thread_list, sizeof(thread_port_array_t) * thread_count); mach_port_deallocate(mach_task_self(), port); - char state = '?'; + /* Taken from: https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/mach/thread_info.h#L129 */ switch (run_state) { - case TH_STATE_RUNNING: state = 'R'; break; - case TH_STATE_STOPPED: state = 'S'; break; - case TH_STATE_WAITING: state = 'W'; break; - case TH_STATE_UNINTERRUPTIBLE: state = 'U'; break; - case TH_STATE_HALTED: state = 'H'; break; + case TH_STATE_RUNNING: proc->state = RUNNING; break; + case TH_STATE_STOPPED: proc->state = STOPPED; break; + case TH_STATE_WAITING: proc->state = WAITING; break; + case TH_STATE_UNINTERRUPTIBLE: proc->state = UNINTERRUPTIBLE_WAIT; break; + case TH_STATE_HALTED: proc->state = BLOCKED; break; + default: proc->state = UNKNOWN; } - proc->state = state; } diff --git a/darwin/Platform.c b/darwin/Platform.c index 3f596a3..152f617 100644 --- a/darwin/Platform.c +++ b/darwin/Platform.c @@ -126,7 +126,7 @@ static double Platform_nanosecondsPerMachTick = 1.0; static double Platform_nanosecondsPerSchedulerTick = -1; -void Platform_init(void) { +bool Platform_init(void) { Platform_nanosecondsPerMachTick = Platform_calculateNanosecondsPerMachTick(); // Determine the number of scheduler clock ticks per second @@ -139,6 +139,8 @@ void Platform_init(void) { const double nanos_per_sec = 1e9; Platform_nanosecondsPerSchedulerTick = nanos_per_sec / scheduler_ticks_per_sec; + + return true; } // Converts ticks in the Mach "timebase" to nanoseconds. diff --git a/darwin/Platform.h b/darwin/Platform.h index c03a9b4..fe75db0 100644 --- a/darwin/Platform.h +++ b/darwin/Platform.h @@ -19,6 +19,7 @@ in the source distribution for its full text. #include "NetworkIOMeter.h" #include "ProcessLocksScreen.h" #include "SignalsPanel.h" +#include "CommandLine.h" #include "darwin/DarwinProcess.h" #include "generic/gettime.h" #include "generic/hostname.h" @@ -33,7 +34,7 @@ extern const unsigned int Platform_numberOfSignals; extern const MeterClass* const Platform_meterTypes[]; -void Platform_init(void); +bool Platform_init(void); // Converts ticks in the Mach "timebase" to nanoseconds. // See `mach_timebase_info`, as used to define the `Platform_nanosecondsPerMachTick` constant. @@ -87,8 +88,8 @@ static inline void Platform_getRelease(char** string) { static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { } -static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) { - return false; +static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) { + return STATUS_ERROR_EXIT; } static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) { diff --git a/dragonflybsd/DragonFlyBSDProcess.c b/dragonflybsd/DragonFlyBSDProcess.c index a091cc6..ceb346c 100644 --- a/dragonflybsd/DragonFlyBSDProcess.c +++ b/dragonflybsd/DragonFlyBSDProcess.c @@ -37,11 +37,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, }, [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, }, [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, }, - [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, }, + [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, }, [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, }, [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, }, [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, }, - [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, + [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, }, [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, }, [TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, }, diff --git a/dragonflybsd/DragonFlyBSDProcessList.c b/dragonflybsd/DragonFlyBSDProcessList.c index e44c164..86586a8 100644 --- a/dragonflybsd/DragonFlyBSDProcessList.c +++ b/dragonflybsd/DragonFlyBSDProcessList.c @@ -79,8 +79,8 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, H size_t sizeof_cp_time_array = sizeof(unsigned long) * CPUSTATES; len = 2; sysctlnametomib("kern.cp_time", MIB_kern_cp_time, &len); - dfpl->cp_time_o = xCalloc(cpus, sizeof_cp_time_array); - dfpl->cp_time_n = xCalloc(cpus, sizeof_cp_time_array); + dfpl->cp_time_o = xCalloc(CPUSTATES, sizeof(unsigned long)); + dfpl->cp_time_n = xCalloc(CPUSTATES, sizeof(unsigned long)); len = sizeof_cp_time_array; // fetch initial single (or average) CPU clicks from kernel @@ -542,60 +542,61 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { } // would be nice if we could store multiple states in proc->state (as enum) and have writeField render them + /* Taken from: https://github.com/DragonFlyBSD/DragonFlyBSD/blob/c163a4d7ee9c6857ee4e04a3a2cbb50c3de29da1/sys/sys/proc_common.h */ switch (kproc->kp_stat) { - case SIDL: proc->state = 'I'; isIdleProcess = true; break; + case SIDL: proc->state = IDLE; isIdleProcess = true; break; case SACTIVE: switch (kproc->kp_lwp.kl_stat) { case LSSLEEP: if (kproc->kp_lwp.kl_flags & LWP_SINTR) // interruptible wait short/long if (kproc->kp_lwp.kl_slptime >= MAXSLP) { - proc->state = 'I'; + proc->state = IDLE; isIdleProcess = true; } else { - proc->state = 'S'; + proc->state = SLEEPING; } else if (kproc->kp_lwp.kl_tdflags & TDF_SINTR) // interruptible lwkt wait - proc->state = 'S'; + proc->state = SLEEPING; else if (kproc->kp_paddr) // uninterruptible wait - proc->state = 'D'; + proc->state = UNINTERRUPTIBLE_WAIT; else // uninterruptible lwkt wait - proc->state = 'B'; + proc->state = UNINTERRUPTIBLE_WAIT; break; case LSRUN: if (kproc->kp_lwp.kl_stat == LSRUN) { if (!(kproc->kp_lwp.kl_tdflags & (TDF_RUNNING | TDF_RUNQ))) - proc->state = 'Q'; + proc->state = QUEUED; else - proc->state = 'R'; + proc->state = RUNNING; } break; case LSSTOP: - proc->state = 'T'; + proc->state = STOPPED; break; default: - proc->state = 'A'; + proc->state = PAGING; break; } break; - case SSTOP: proc->state = 'T'; break; - case SZOMB: proc->state = 'Z'; break; - case SCORE: proc->state = 'C'; break; - default: proc->state = '?'; + case SSTOP: proc->state = STOPPED; break; + case SZOMB: proc->state = ZOMBIE; break; + case SCORE: proc->state = BLOCKED; break; + default: proc->state = UNKNOWN; } if (kproc->kp_flags & P_SWAPPEDOUT) - proc->state = 'W'; + proc->state = SLEEPING; if (kproc->kp_flags & P_TRACED) - proc->state = 'T'; + proc->state = TRACED; if (kproc->kp_flags & P_JAILED) - proc->state = 'J'; + proc->state = TRACED; if (Process_isKernelThread(proc)) super->kernelThreads++; super->totalTasks++; - if (proc->state == 'R') + if (proc->state == RUNNING) super->runningTasks++; proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); diff --git a/dragonflybsd/Platform.c b/dragonflybsd/Platform.c index bb603cb..4941445 100644 --- a/dragonflybsd/Platform.c +++ b/dragonflybsd/Platform.c @@ -105,8 +105,9 @@ const MeterClass* const Platform_meterTypes[] = { NULL }; -void Platform_init(void) { +bool Platform_init(void) { /* no platform-specific setup needed */ + return true; } void Platform_done(void) { diff --git a/dragonflybsd/Platform.h b/dragonflybsd/Platform.h index f3f2ec5..281a7ee 100644 --- a/dragonflybsd/Platform.h +++ b/dragonflybsd/Platform.h @@ -23,6 +23,7 @@ in the source distribution for its full text. #include "Process.h" #include "ProcessLocksScreen.h" #include "SignalsPanel.h" +#include "CommandLine.h" #include "generic/gettime.h" #include "generic/hostname.h" #include "generic/uname.h" @@ -36,7 +37,7 @@ extern const unsigned int Platform_numberOfSignals; extern const MeterClass* const Platform_meterTypes[]; -void Platform_init(void); +bool Platform_init(void); void Platform_done(void); @@ -78,8 +79,8 @@ static inline void Platform_getRelease(char** string) { static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { } -static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) { - return false; +static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) { + return STATUS_ERROR_EXIT; } static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) { diff --git a/freebsd/FreeBSDProcess.c b/freebsd/FreeBSDProcess.c index 3fa55ae..345edff 100644 --- a/freebsd/FreeBSDProcess.c +++ b/freebsd/FreeBSDProcess.c @@ -36,11 +36,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, }, [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, }, [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, }, - [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, }, + [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, }, [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, }, [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, }, [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, }, - [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, + [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, }, [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, .defaultSortDesc = true, }, [TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, }, diff --git a/freebsd/FreeBSDProcessList.c b/freebsd/FreeBSDProcessList.c index 9bbfccb..507f480 100644 --- a/freebsd/FreeBSDProcessList.c +++ b/freebsd/FreeBSDProcessList.c @@ -109,8 +109,8 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, H size_t sizeof_cp_time_array = sizeof(unsigned long) * CPUSTATES; len = 2; sysctlnametomib("kern.cp_time", MIB_kern_cp_time, &len); - fpl->cp_time_o = xCalloc(cpus, sizeof_cp_time_array); - fpl->cp_time_n = xCalloc(cpus, sizeof_cp_time_array); + fpl->cp_time_o = xCalloc(CPUSTATES, sizeof(unsigned long)); + fpl->cp_time_n = xCalloc(CPUSTATES, sizeof(unsigned long)); len = sizeof_cp_time_array; // fetch initial single (or average) CPU clicks from kernel @@ -578,15 +578,16 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { proc->nice = PRIO_MAX + 1 + kproc->ki_pri.pri_level - PRI_MIN_IDLE; } + /* Taken from: https://github.com/freebsd/freebsd-src/blob/1ad2d87778970582854082bcedd2df0394fd4933/sys/sys/proc.h#L851 */ switch (kproc->ki_stat) { - case SIDL: proc->state = 'I'; break; - case SRUN: proc->state = 'R'; break; - case SSLEEP: proc->state = 'S'; break; - case SSTOP: proc->state = 'T'; break; - case SZOMB: proc->state = 'Z'; break; - case SWAIT: proc->state = 'D'; break; - case SLOCK: proc->state = 'L'; break; - default: proc->state = '?'; + case SIDL: proc->state = IDLE; break; + case SRUN: proc->state = RUNNING; break; + case SSLEEP: proc->state = SLEEPING; break; + case SSTOP: proc->state = STOPPED; break; + case SZOMB: proc->state = ZOMBIE; break; + case SWAIT: proc->state = WAITING; break; + case SLOCK: proc->state = BLOCKED; break; + default: proc->state = UNKNOWN; } if (Process_isKernelThread(proc)) @@ -595,7 +596,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); super->totalTasks++; - if (proc->state == 'R') + if (proc->state == RUNNING) super->runningTasks++; proc->updated = true; } diff --git a/freebsd/Platform.c b/freebsd/Platform.c index 9c88b40..6c82bc2 100644 --- a/freebsd/Platform.c +++ b/freebsd/Platform.c @@ -127,8 +127,9 @@ const MeterClass* const Platform_meterTypes[] = { NULL }; -void Platform_init(void) { +bool Platform_init(void) { /* no platform-specific setup needed */ + return true; } void Platform_done(void) { @@ -284,6 +285,7 @@ bool Platform_getDiskIO(DiskIOData* data) { if (devstat_checkversion(NULL) < 0) return false; + // use static to plug memory leak; see #841 static struct devinfo info = { 0 }; struct statinfo current = { .dinfo = &info }; diff --git a/freebsd/Platform.h b/freebsd/Platform.h index e99232e..a1aa31b 100644 --- a/freebsd/Platform.h +++ b/freebsd/Platform.h @@ -19,6 +19,7 @@ in the source distribution for its full text. #include "Process.h" #include "ProcessLocksScreen.h" #include "SignalsPanel.h" +#include "CommandLine.h" #include "generic/gettime.h" #include "generic/hostname.h" #include "generic/uname.h" @@ -32,7 +33,7 @@ extern const unsigned int Platform_numberOfSignals; extern const MeterClass* const Platform_meterTypes[]; -void Platform_init(void); +bool Platform_init(void); void Platform_done(void); @@ -78,8 +79,8 @@ static inline void Platform_getRelease(char** string) { static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { } -static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) { - return false; +static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) { + return STATUS_ERROR_EXIT; } static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) { diff --git a/generic/gettime.c b/generic/gettime.c index 975457f..b7c4885 100644 --- a/generic/gettime.c +++ b/generic/gettime.c @@ -49,9 +49,13 @@ void Generic_gettime_monotonic(uint64_t* msec) { else *msec = 0; -#else +#else /* lower resolution gettimeofday() should be always available */ -# error "No monotonic clock available" + struct timeval tv; + if (gettimeofday(&tv, NULL) == 0) + *msec = ((uint64_t)tv.tv_sec * 1000) + ((uint64_t)tv.tv_usec / 1000); + else + *msec = 0; #endif } diff --git a/htop.1.in b/htop.1.in index 9c66b56..49c4a52 100644 --- a/htop.1.in +++ b/htop.1.in @@ -62,7 +62,7 @@ Show only the given PIDs Sort by this column (use \-\-sort\-key help for a column list). This will force a list view unless you specify -t at the same time. .TP -\fB\-u \-\-user=USERNAME\fR +\fB\-u \-\-user=USERNAME|UID\fR Show only the processes of a given user .TP \fB\-U \-\-no-unicode\fR @@ -285,19 +285,31 @@ is active, the executable path (/proc/[pid]/exe) and the command name (/proc/[pid]/comm) are also shown merged with the command line, if available. The program basename is highlighted if set in the configuration. Additional -highlighting can be configured for stale executables (cf. Exe column below). +highlighting can be configured for stale executables (cf. EXE column below). .TP -.B Comm +.B COMM The command name of the process obtained from /proc/[pid]/comm, if readable. .TP -.B Exe +.B EXE The abbreviated basename of the executable of the process, obtained from /proc/[pid]/exe, if readable. htop is able to read this file on linux for ALL the processes only if it has the capability CAP_SYS_PTRACE or root privileges. The basename is marked in red if the executable used to run the process has -been replaced or deleted on disk since the process started. This additional -markup can be configured. +been replaced or deleted on disk since the process started. The information is +obtained by processing the contents of /proc/[pid]/exe. + +Furthermore the basename is marked in yellow if any library is reported as having +been replaced or deleted on disk since it was last loaded. The information is +obtained by processing the contents of /proc/[pid]/maps. + +When deciding the color the replacement of the main executable always takes +precedence over replacement of any other library. If only the memory map indicates +a replacement of the main executable, this will show as if any other library had +been replaced or deleted. + +This additional color markup can be configured in the "Display Options" section of +the setup screen. .TP .B PID The process ID. @@ -476,7 +488,23 @@ The I/O rate of write(2) in bytes per second, for the process. The I/O rate, IO_READ_RATE + IO_WRITE_RATE (see above). .TP .B CGROUP -Which cgroup the process is in. +Which cgroup the process is in. For a shortened view see the CCGROUP column below. +.TP +.B CCGROUP +Shortened view of the cgroup name that the process is in. +This performs some pattern-based replacements to shorten the displayed string and thus condense the information. + \fB/*.slice\fR is shortened to \fB/[*]\fR (exceptions below) + \fB/system.slice\fR is shortened to \fB/[S]\fR + \fB/user.slice\fR is shortened to \fB/[U]\fR + \fB/user-*.slice\fR is shortened to \fB/[U:*]\fR (directly preceeding \fB/[U]\fR before dropped) + \fB/machine.slice\fR is shortened to \fB/[M]\fR + \fB/machine-*.scope\fR is shortened to \fB/[SNC:*]\fR (SNC: systemd nspawn container), uppercase for the monitor + \fB/lxc.monitor.*\fR is shortened to \fB/[LXC:*]\fR + \fB/lxc.payload.*\fR is shortened to \fB/[lxc:*]\fR + \fB/*.scope\fR is shortened to \fB/!*\fR + \fB/*.service\fR is shortened to \fB/*\fR (suffix removed) + +Encountered escape sequences (e.g. from systemd) inside the cgroup name are not decoded. .TP .B OOM OOM killer score. diff --git a/linux/CGroupUtils.c b/linux/CGroupUtils.c new file mode 100644 index 0000000..6f3b6fe --- /dev/null +++ b/linux/CGroupUtils.c @@ -0,0 +1,317 @@ +/* +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" + + +typedef struct StrBuf_state { + char *buf; + size_t size; + size_t pos; +} StrBuf_state; + +typedef bool (*StrBuf_putc_t)(StrBuf_state* p, char c); + +static bool StrBuf_putc_count(StrBuf_state* p, ATTR_UNUSED char c) { + p->pos++; + return true; +} + +static bool StrBuf_putc_write(StrBuf_state* p, char c) { + if (p->pos >= p->size) + return false; + + p->buf[p->pos] = c; + p->pos++; + return true; +} + +static bool StrBuf_putsn(StrBuf_state* p, StrBuf_putc_t w, const char* s, size_t count) { + while (count--) + if (!w(p, *s++)) + return false; + + return true; +} + +static bool StrBuf_putsz(StrBuf_state* p, StrBuf_putc_t w, const char* s) { + while (*s) + if (!w(p, *s++)) + return false; + + return true; +} + +static bool Label_checkEqual(const char* labelStart, size_t labelLen, const char* expected) { + return labelLen == strlen(expected) && String_startsWith(labelStart, expected); +} + +static bool Label_checkPrefix(const char* labelStart, size_t labelLen, const char* expected) { + return labelLen > strlen(expected) && String_startsWith(labelStart, expected); +} + +static bool Label_checkSuffix(const char* labelStart, size_t labelLen, const char* expected) { + return labelLen > strlen(expected) && String_startsWith(labelStart + labelLen - strlen(expected), expected); +} + +static bool CGroup_filterName_internal(const char *cgroup, StrBuf_state* s, StrBuf_putc_t w) { + 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_legacy = "lxc.monitor"; + const char* str_lxc_payload_legacy = "lxc.payload"; + 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_nspawn_payload_label = "/payload"; + + const char* str_service_suffix = ".service"; + const char* str_scope_suffix = ".scope"; + + while (*cgroup) { + if ('/' == *cgroup) { + while ('/' == *cgroup) + cgroup++; + + if (!w(s, '/')) + return false; + + continue; + } + + const char* labelStart = cgroup; + const char* nextSlash = strchrnul(labelStart, '/'); + const size_t labelLen = nextSlash - labelStart; + + if (Label_checkEqual(labelStart, labelLen, str_system_slice)) { + cgroup = nextSlash; + + if (!StrBuf_putsz(s, w, "[S]")) + return false; + + continue; + } + + if (Label_checkEqual(labelStart, labelLen, str_machine_slice)) { + cgroup = nextSlash; + + if (!StrBuf_putsz(s, w, "[M]")) + return false; + + continue; + } + + if (Label_checkEqual(labelStart, labelLen, str_user_slice)) { + cgroup = nextSlash; + + if (!StrBuf_putsz(s, w, "[U]")) + return false; + + 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)); + + s->pos--; + if (!w(s, ':')) + return false; + + if (!StrBuf_putsn(s, w, cgroup + strlen(str_user_slice_prefix), sliceNameLen)) + return false; + + if (!w(s, ']')) + return false; + + cgroup = userSliceSlash; + + continue; + } + + if (Label_checkSuffix(labelStart, labelLen, str_slice_suffix)) { + const size_t sliceNameLen = labelLen - strlen(str_slice_suffix); + + if (!w(s, '[')) + return false; + + if (!StrBuf_putsn(s, w, cgroup, sliceNameLen)) + return false; + + if (!w(s, ']')) + return false; + + cgroup = nextSlash; + + continue; + } + + if (Label_checkPrefix(labelStart, labelLen, str_lxc_payload_prefix)) { + const size_t cgroupNameLen = labelLen - strlen(str_lxc_payload_prefix); + + if (!StrBuf_putsz(s, w, "[lxc:")) + return false; + + if (!StrBuf_putsn(s, w, cgroup + strlen(str_lxc_payload_prefix), cgroupNameLen)) + return false; + + if (!w(s, ']')) + return false; + + cgroup = nextSlash; + + continue; + } + + if (Label_checkPrefix(labelStart, labelLen, str_lxc_monitor_prefix)) { + const size_t cgroupNameLen = labelLen - strlen(str_lxc_monitor_prefix); + + if (!StrBuf_putsz(s, w, "[LXC:")) + return false; + + if (!StrBuf_putsn(s, w, cgroup + strlen(str_lxc_monitor_prefix), cgroupNameLen)) + return false; + + if (!w(s, ']')) + return false; + + cgroup = nextSlash; + + continue; + } + + // LXC legacy cgroup naming + if (Label_checkEqual(labelStart, labelLen, str_lxc_monitor_legacy) || + Label_checkEqual(labelStart, labelLen, str_lxc_payload_legacy)) { + bool isMonitor = Label_checkEqual(labelStart, labelLen, str_lxc_monitor_legacy); + + labelStart = nextSlash; + while (*labelStart == '/') + labelStart++; + + nextSlash = strchrnul(labelStart, '/'); + if (nextSlash - labelStart > 0) { + if (!StrBuf_putsz(s, w, isMonitor ? "[LXC:" : "[lxc:")) + return false; + + if (!StrBuf_putsn(s, w, labelStart, nextSlash - labelStart)) + return false; + + if (!w(s, ']')) + return false; + + cgroup = nextSlash; + continue; + } + + labelStart = cgroup; + nextSlash = labelStart + labelLen; + } + + if (Label_checkSuffix(labelStart, labelLen, str_service_suffix)) { + const size_t serviceNameLen = labelLen - strlen(str_service_suffix); + + if (String_startsWith(cgroup, "user@")) { + cgroup = nextSlash; + + while(*cgroup == '/') + cgroup++; + + continue; + } + + if (!StrBuf_putsn(s, w, cgroup, serviceNameLen)) + return false; + + cgroup = nextSlash; + + continue; + } + + if (Label_checkSuffix(labelStart, labelLen, str_scope_suffix)) { + const size_t scopeNameLen = labelLen - strlen(str_scope_suffix); + + if (Label_checkPrefix(labelStart, scopeNameLen, str_nspawn_scope_prefix)) { + const size_t machineScopeNameLen = scopeNameLen - strlen(str_nspawn_scope_prefix); + + const bool is_monitor = String_startsWith(nextSlash, str_nspawn_monitor_label); + + if (!StrBuf_putsz(s, w, is_monitor ? "[SNC:" : "[snc:")) + return false; + + if (!StrBuf_putsn(s, w, cgroup + strlen(str_nspawn_scope_prefix), machineScopeNameLen)) + return false; + + if (!w(s, ']')) + return false; + + cgroup = nextSlash; + if (String_startsWith(nextSlash, str_nspawn_monitor_label)) + cgroup += strlen(str_nspawn_monitor_label); + else if (String_startsWith(nextSlash, str_nspawn_payload_label)) + cgroup += strlen(str_nspawn_payload_label); + + continue; + } + + if (!w(s, '!')) + return false; + + if (!StrBuf_putsn(s, w, cgroup, scopeNameLen)) + return false; + + cgroup = nextSlash; + + continue; + } + + // Default behavior: Copy the full label + cgroup = labelStart; + + if (!StrBuf_putsn(s, w, cgroup, labelLen)) + return false; + + cgroup = nextSlash; + } + + return true; +} + +char* CGroup_filterName(const char *cgroup) { + StrBuf_state s = { + .buf = NULL, + .size = 0, + .pos = 0, + }; + + if (!CGroup_filterName_internal(cgroup, &s, StrBuf_putc_count)) { + return NULL; + } + + s.buf = xCalloc(s.pos + 1, sizeof(char)); + s.size = s.pos; + s.pos = 0; + + if (!CGroup_filterName_internal(cgroup, &s, StrBuf_putc_write)) { + free(s.buf); + return NULL; + } + + s.buf[s.size] = '\0'; + return s.buf; +} diff --git a/linux/CGroupUtils.h b/linux/CGroupUtils.h new file mode 100644 index 0000000..db2df7f --- /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 + + +char* CGroup_filterName(const char *cgroup); + +#endif /* HEADER_CGroupUtils */ diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index 49ae541..ba2dbd4 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -56,11 +56,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [M_TRS] = { .name = "M_TRS", .title = " CODE ", .description = "Size of the text segment of the process", .flags = 0, .defaultSortDesc = true, }, [M_DRS] = { .name = "M_DRS", .title = " DATA ", .description = "Size of the data segment plus stack usage of the process", .flags = 0, .defaultSortDesc = true, }, [M_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process (calculated from memory maps)", .flags = PROCESS_FLAG_LINUX_LRS_FIX, .defaultSortDesc = true, }, - [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, }, + [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, }, [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, }, [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, }, [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, }, - [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, + [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, }, [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, .defaultSortDesc = true, }, [TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, }, @@ -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 @@ -93,7 +94,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [M_SWAP] = { .name = "M_SWAP", .title = " SWAP ", .description = "Size of the process's swapped pages", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, }, [M_PSSWP] = { .name = "M_PSSWP", .title = " PSSWP ", .description = "shows proportional swap share of this mapping, unlike \"Swap\", this does not take into account swapped out page of underlying shmem objects", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, }, [CTXT] = { .name = "CTXT", .title = " CTXT ", .description = "Context switches (incremental sum of voluntary_ctxt_switches and nonvoluntary_ctxt_switches)", .flags = PROCESS_FLAG_LINUX_CTXT, .defaultSortDesc = true, }, - [SECATTR] = { .name = "SECATTR", .title = " Security Attribute ", .description = "Security attribute of the process (e.g. SELinux or AppArmor)", .flags = PROCESS_FLAG_LINUX_SECATTR, }, + [SECATTR] = { .name = "SECATTR", .title = "Security Attribute ", .description = "Security attribute of the process (e.g. SELinux or AppArmor)", .flags = PROCESS_FLAG_LINUX_SECATTR, }, [PROC_COMM] = { .name = "COMM", .title = "COMM ", .description = "comm string of the process from /proc/[pid]/comm", .flags = 0, }, [PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process from /proc/[pid]/exe", .flags = 0, }, [CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, }, @@ -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); @@ -246,7 +248,8 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces #ifdef HAVE_VSERVER case VXID: xSnprintf(buffer, n, "%5u ", lp->vxid); break; #endif - case CGROUP: xSnprintf(buffer, n, "%-10s ", lp->cgroup ? lp->cgroup : ""); break; + 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); @@ -277,7 +280,7 @@ static void LinuxProcess_writeField(const Process* this, RichString* str, Proces } xSnprintf(buffer, n, "%5lu ", lp->ctxt_diff); break; - case SECATTR: snprintf(buffer, n, "%-30s ", lp->secattr ? lp->secattr : "?"); break; + case SECATTR: snprintf(buffer, n, "%-30.30s ", lp->secattr ? lp->secattr : "?"); break; case AUTOGROUP_ID: if (lp->autogroup_id != -1) { xSnprintf(buffer, n, "%4ld ", lp->autogroup_id); @@ -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 577b903..3e5d380 100644 --- a/linux/LinuxProcess.h +++ b/linux/LinuxProcess.h @@ -48,6 +48,9 @@ typedef struct LinuxProcess_ { long m_drs; long m_lrs; + /* Process flags */ + unsigned long int flags; + /* Data read (in bytes) */ unsigned long long io_rchar; @@ -86,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 dbbc57d..3bfe7db 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 @@ -62,6 +63,10 @@ in the source distribution for its full text. #define O_PATH 010000000 // declare for ancient glibc versions #endif +/* Not exposed yet. Defined at include/linux/sched.h */ +#ifndef PF_KTHREAD +#define PF_KTHREAD 0x00200000 +#endif static long long btime = -1; @@ -310,6 +315,22 @@ static inline unsigned long long LinuxProcessList_adjustTime(unsigned long long return t * 100 / jiffy; } +/* Taken from: https://github.com/torvalds/linux/blob/64570fbc14f8d7cb3fe3995f20e26bc25ce4b2cc/fs/proc/array.c#L120 */ +static inline ProcessState LinuxProcessList_getProcessState(char state) { + switch (state) { + case 'S': return SLEEPING; + case 'X': return DEFUNCT; + case 'Z': return ZOMBIE; + case 't': return TRACED; + case 'T': return STOPPED; + case 'D': return UNINTERRUPTIBLE_WAIT; + case 'R': return RUNNING; + case 'P': return BLOCKED; + case 'I': return IDLE; + default: return UNKNOWN; + } +} + static bool LinuxProcessList_readStatFile(Process* process, openat_arg_t procFd, char* command, size_t commLen) { LinuxProcess* lp = (LinuxProcess*) process; @@ -335,7 +356,7 @@ static bool LinuxProcessList_readStatFile(Process* process, openat_arg_t procFd, location = end + 2; /* (3) state - %c */ - process->state = location[0]; + process->state = LinuxProcessList_getProcessState(location[0]); location += 2; /* (4) ppid - %d */ @@ -358,8 +379,9 @@ static bool LinuxProcessList_readStatFile(Process* process, openat_arg_t procFd, process->tpgid = strtol(location, &location, 10); location += 1; - /* Skip (9) flags - %u */ - location = strchr(location, ' ') + 1; + /* (9) flags - %u */ + lp->flags = strtoul(location, &location, 10); + location += 1; /* (10) minflt - %lu */ process->minflt = strtoull(location, &location, 10); @@ -654,6 +676,11 @@ static void LinuxProcessList_readMaps(LinuxProcess* process, openat_arg_t procFd if (String_startsWith(readptr, "/memfd:")) continue; + /* Virtualbox maps /dev/zero for memory allocation. That results in + * false positive, so ignore. */ + if (String_eq(readptr, "/dev/zero (deleted)\n")) + continue; + if (strstr(readptr, " (deleted)\n")) { proc->usesDeletedLib = true; if (!calcSize) @@ -834,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]; @@ -846,9 +877,16 @@ static void LinuxProcessList_readCGroupFile(LinuxProcess* process, openat_arg_t if (!ok) break; - char* group = strchr(buffer, ':'); - if (!group) - break; + char* group = buffer; + for (size_t i = 0; i < 2; i++) { + group = strchrnul(group, ':'); + if (!*group) + break; + group++; + } + + char* eol = strchrnul(group, '\n'); + *eol = '\0'; if (at != output) { *at = ';'; @@ -859,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 = CGroup_filterName(process->cgroup); + if (cgroup_short) { + free_and_xStrdup(&process->cgroup_short, cgroup_short); + free(cgroup_short); + } else { + free(process->cgroup_short); + process->cgroup_short = NULL; + } } #ifdef HAVE_VSERVER @@ -1089,17 +1142,9 @@ delayacct_failure: static bool LinuxProcessList_readCmdlineFile(Process* process, openat_arg_t procFd) { char command[4096 + 1]; // max cmdline length on Linux ssize_t amtRead = xReadfileat(procFd, "cmdline", command, sizeof(command)); - if (amtRead < 0) + if (amtRead <= 0) return false; - if (amtRead == 0) { - if (process->state != 'Z') { - process->isKernelThread = true; - } - Process_updateCmdline(process, NULL, 0, 0); - return true; - } - int tokenEnd = 0; int tokenStart = 0; int lastChar = 0; @@ -1467,6 +1512,10 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ if (! LinuxProcessList_readStatFile(proc, procFd, statCommand, sizeof(statCommand))) goto errorReadingProcess; + if (lp->flags & PF_KTHREAD) { + proc->isKernelThread = true; + } + if (tty_nr != proc->tty_nr && this->ttyDrivers) { free(proc->tty_name); proc->tty_name = LinuxProcessList_updateTtyDevice(this->ttyDrivers, proc->tty_nr); @@ -1498,17 +1547,21 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ } #endif - if (! LinuxProcessList_readCmdlineFile(proc, procFd)) { - goto errorReadingProcess; + if (proc->isKernelThread) { + Process_updateCmdline(proc, NULL, 0, 0); + } else if (!LinuxProcessList_readCmdlineFile(proc, procFd)) { + Process_updateCmdline(proc, statCommand, 0, strlen(statCommand)); } Process_fillStarttimeBuffer(proc); ProcessList_add(pl, proc); } else { - if (settings->updateProcessNames && proc->state != 'Z') { - if (! LinuxProcessList_readCmdlineFile(proc, procFd)) { - goto errorReadingProcess; + if (settings->updateProcessNames && proc->state != ZOMBIE) { + if (proc->isKernelThread) { + Process_updateCmdline(proc, NULL, 0, 0); + } else if (!LinuxProcessList_readCmdlineFile(proc, procFd)) { + Process_updateCmdline(proc, statCommand, 0, strlen(statCommand)); } } } @@ -1544,7 +1597,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_ } if (!proc->cmdline && statCommand[0] && - (proc->state == 'Z' || Process_isKernelThread(proc) || settings->showThreadNames)) { + (proc->state == ZOMBIE || Process_isKernelThread(proc) || settings->showThreadNames)) { Process_updateCmdline(proc, statCommand, 0, strlen(statCommand)); } diff --git a/linux/Platform.c b/linux/Platform.c index e305e7f..93953e8 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -611,133 +611,85 @@ bool Platform_getNetworkIO(NetworkIOData* data) { // Linux battery reading by Ian P. Hands (iphands@gmail.com, ihands@redhat.com). -#define MAX_BATTERIES 64 #define PROC_BATTERY_DIR PROCDIR "/acpi/battery" #define PROC_POWERSUPPLY_DIR PROCDIR "/acpi/ac_adapter" +#define PROC_POWERSUPPLY_ACSTATE_FILE PROC_POWERSUPPLY_DIR "/AC/state" #define SYS_POWERSUPPLY_DIR "/sys/class/power_supply" // ---------------------------------------- // READ FROM /proc // ---------------------------------------- -static unsigned long int parseBatInfo(const char* fileName, const unsigned short int lineNum, const unsigned short int wordNum) { - const char batteryPath[] = PROC_BATTERY_DIR; - DIR* batteryDir = opendir(batteryPath); +static double Platform_Battery_getProcBatInfo(void) { + DIR* batteryDir = opendir(PROC_BATTERY_DIR); if (!batteryDir) - return 0; - - char* batteries[MAX_BATTERIES]; - unsigned int nBatteries = 0; - memset(batteries, 0, MAX_BATTERIES * sizeof(char*)); + return NAN; - while (nBatteries < MAX_BATTERIES) { - const struct dirent* dirEntry = readdir(batteryDir); - if (!dirEntry) - break; + uint64_t totalFull = 0; + uint64_t totalRemain = 0; + struct dirent* dirEntry = NULL; + while ((dirEntry = readdir(batteryDir))) { const char* entryName = dirEntry->d_name; if (!String_startsWith(entryName, "BAT")) continue; - batteries[nBatteries] = xStrdup(entryName); - nBatteries++; - } - closedir(batteryDir); + char filePath[256]; + char bufInfo[1024] = {0}; + xSnprintf(filePath, sizeof(filePath), "%s/%s/info", PROC_BATTERY_DIR, entryName); + ssize_t r = xReadfile(filePath, bufInfo, sizeof(bufInfo)); + if (r < 0) + continue; - unsigned long int total = 0; - for (unsigned int i = 0; i < nBatteries; i++) { - char infoPath[30]; - xSnprintf(infoPath, sizeof infoPath, "%s%s/%s", batteryPath, batteries[i], fileName); + char bufState[1024] = {0}; + xSnprintf(filePath, sizeof(filePath), "%s/%s/state", PROC_BATTERY_DIR, entryName); + r = xReadfile(filePath, bufState, sizeof(bufState)); + if (r < 0) + continue; - FILE* file = fopen(infoPath, "r"); - if (!file) - break; + const char* line; + + //Getting total charge for all batteries + char* buf = bufInfo; + while ((line = strsep(&buf, "\n")) != NULL) { + char field[100] = {0}; + int val = 0; + if (2 != sscanf(line, "%99[^:]:%d", field, &val)) + continue; - char* line = NULL; - for (unsigned short int j = 0; j < lineNum; j++) { - free(line); - line = String_readLine(file); - if (!line) + if (String_eq(field, "last full capacity")) { + totalFull += val; break; + } } - fclose(file); - - if (!line) - break; - - char* foundNumStr = String_getToken(line, wordNum); - const unsigned long int foundNum = atoi(foundNumStr); - free(foundNumStr); - free(line); + //Getting remaining charge for all batteries + buf = bufState; + while ((line = strsep(&buf, "\n")) != NULL) { + char field[100] = {0}; + int val = 0; + if (2 != sscanf(line, "%99[^:]:%d", field, &val)) + continue; - total += foundNum; + if (String_eq(field, "remaining capacity")) { + totalRemain += val; + break; + } + } } - for (unsigned int i = 0; i < nBatteries; i++) - free(batteries[i]); + closedir(batteryDir); - return total; + return totalFull > 0 ? ((double) totalRemain * 100.0) / (double) totalFull : NAN; } static ACPresence procAcpiCheck(void) { - ACPresence isOn = AC_ERROR; - const char* power_supplyPath = PROC_POWERSUPPLY_DIR; - DIR* dir = opendir(power_supplyPath); - if (!dir) + char buffer[1024] = {0}; + ssize_t r = xReadfile(PROC_POWERSUPPLY_ACSTATE_FILE, buffer, sizeof(buffer)); + if (r < 1) return AC_ERROR; - for (;;) { - const struct dirent* dirEntry = readdir(dir); - if (!dirEntry) - break; - - const char* entryName = dirEntry->d_name; - - if (entryName[0] != 'A') - continue; - - char statePath[256]; - xSnprintf(statePath, sizeof(statePath), "%s/%s/state", power_supplyPath, entryName); - FILE* file = fopen(statePath, "r"); - if (!file) { - isOn = AC_ERROR; - continue; - } - char* line = String_readLine(file); - - fclose(file); - - if (!line) - continue; - - char* isOnline = String_getToken(line, 2); - free(line); - - if (String_eq(isOnline, "on-line")) - isOn = AC_PRESENT; - else - isOn = AC_ABSENT; - free(isOnline); - if (isOn == AC_PRESENT) - break; - } - - closedir(dir); - - return isOn; -} - -static double Platform_Battery_getProcBatInfo(void) { - const unsigned long int totalFull = parseBatInfo("info", 3, 4); - if (totalFull == 0) - return NAN; - - const unsigned long int totalRemain = parseBatInfo("state", 5, 3); - if (totalRemain == 0) - return NAN; - - return totalRemain * 100.0 / (double) totalFull; + return String_eq(buffer, "on-line") ? AC_PRESENT : AC_ABSENT; } static void Platform_Battery_getProcData(double* percent, ACPresence* isOnAC) { @@ -750,7 +702,6 @@ static void Platform_Battery_getProcData(double* percent, ACPresence* isOnAC) { // ---------------------------------------- static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) { - *percent = NAN; *isOnAC = AC_ERROR; @@ -758,68 +709,52 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) { if (!dir) return; - unsigned long int totalFull = 0; - unsigned long int totalRemain = 0; - - for (;;) { - const struct dirent* dirEntry = readdir(dir); - if (!dirEntry) - break; + uint64_t totalFull = 0; + uint64_t totalRemain = 0; + struct dirent* dirEntry = NULL; + while ((dirEntry = readdir(dir))) { const char* entryName = dirEntry->d_name; - char filePath[256]; - - xSnprintf(filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/type", entryName); - char type[8]; - ssize_t r = xReadfile(filePath, type, sizeof(type)); - if (r < 3) - continue; - - if (type[0] == 'B' && type[1] == 'a' && type[2] == 't') { + if (String_startsWith(entryName, "BAT")) { + char buffer[1024] = {0}; + char filePath[256]; xSnprintf(filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/uevent", entryName); - char buffer[1024]; - r = xReadfile(filePath, buffer, sizeof(buffer)); - if (r < 0) { - closedir(dir); - return; - } + ssize_t r = xReadfile(filePath, buffer, sizeof(buffer)); + if (r < 0) + continue; - char* buf = buffer; - const char* line; bool full = false; bool now = false; - int fullSize = 0; - double capacityLevel = NAN; - #define match(str,prefix) \ - (String_startsWith(str,prefix) ? (str) + strlen(prefix) : NULL) + double fullCharge = 0; + double capacityLevel = NAN; + const char* line; + char* buf = buffer; while ((line = strsep(&buf, "\n")) != NULL) { - const char* ps = match(line, "POWER_SUPPLY_"); - if (!ps) + char field[100] = {0}; + int val = 0; + if (2 != sscanf(line, "POWER_SUPPLY_%99[^=]=%d", field, &val)) continue; - const char* capacity = match(ps, "CAPACITY="); - if (capacity) - capacityLevel = atoi(capacity) / 100.0; - const char* energy = match(ps, "ENERGY_"); - if (!energy) - energy = match(ps, "CHARGE_"); - if (!energy) + + if (String_eq(field, "CAPACITY")) { + capacityLevel = val / 100.0; continue; - const char* value = (!full) ? match(energy, "FULL=") : NULL; - if (value) { - fullSize = atoi(value); - totalFull += fullSize; + } + + if (String_eq(field, "ENERGY_FULL") || String_eq(field, "CHARGE_FULL")) { + fullCharge = val; + totalFull += fullCharge; full = true; if (now) break; continue; } - value = (!now) ? match(energy, "NOW=") : NULL; - if (value) { - totalRemain += atoi(value); + + if (String_eq(field, "ENERGY_NOW") || String_eq(field, "CHARGE_NOW")) { + totalRemain += val; now = true; if (full) break; @@ -827,23 +762,21 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) { } } - #undef match - if (!now && full && !isnan(capacityLevel)) - totalRemain += (capacityLevel * fullSize); + totalRemain += capacityLevel * fullCharge; - } else if (entryName[0] == 'A') { + } else if (String_startsWith(entryName, "AC")) { + char buffer[2] = {0}; if (*isOnAC != AC_ERROR) continue; - xSnprintf(filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/online", entryName); - - char buffer[2]; + char filePath[256]; + xSnprintf(filePath, sizeof(filePath), SYS_POWERSUPPLY_DIR "/%s/online", entryName); - r = xReadfile(filePath, buffer, sizeof(buffer)); + ssize_t r = xReadfile(filePath, buffer, sizeof(buffer)); if (r < 1) { - closedir(dir); - return; + *isOnAC = AC_ERROR; + continue; } if (buffer[0] == '0') @@ -852,6 +785,7 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) { *isOnAC = AC_PRESENT; } } + closedir(dir); *percent = totalFull > 0 ? ((double) totalRemain * 100.0) / (double) totalFull : NAN; @@ -901,7 +835,7 @@ void Platform_longOptionsUsage(const char* name) #endif } -bool Platform_getLongOption(int opt, int argc, char** argv) { +CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv) { #ifndef HAVE_LIBCAP (void) argc; (void) argv; @@ -924,16 +858,16 @@ bool Platform_getLongOption(int opt, int argc, char** argv) { Platform_capabilitiesMode = CAP_MODE_STRICT; } else { fprintf(stderr, "Error: invalid capabilities mode \"%s\".\n", mode); - exit(1); + return STATUS_ERROR_EXIT; } - return true; + return STATUS_OK; } #endif default: break; } - return false; + return STATUS_ERROR_EXIT; } #ifdef HAVE_LIBCAP @@ -1022,20 +956,22 @@ static int dropCapabilities(enum CapMode mode) { } #endif -void Platform_init(void) { +bool Platform_init(void) { #ifdef HAVE_LIBCAP if (dropCapabilities(Platform_capabilitiesMode) < 0) - exit(1); + return false; #endif if (access(PROCDIR, R_OK) != 0) { fprintf(stderr, "Error: could not read procfs (compiled to look in %s).\n", PROCDIR); - exit(1); + return false; } #ifdef HAVE_SENSORS_SENSORS_H LibSensors_init(); #endif + + return true; } void Platform_done(void) { diff --git a/linux/Platform.h b/linux/Platform.h index 9c4e65d..2e2fb3e 100644 --- a/linux/Platform.h +++ b/linux/Platform.h @@ -27,6 +27,7 @@ in the source distribution for its full text. #include "ProcessLocksScreen.h" #include "RichString.h" #include "SignalsPanel.h" +#include "CommandLine.h" #include "generic/gettime.h" #include "generic/hostname.h" #include "generic/uname.h" @@ -45,8 +46,7 @@ extern const unsigned int Platform_numberOfSignals; extern const MeterClass* const Platform_meterTypes[]; -void Platform_init(void); - +bool Platform_init(void); void Platform_done(void); void Platform_setBindings(Htop_Action* keys); @@ -100,7 +100,7 @@ static inline void Platform_getRelease(char** string) { void Platform_longOptionsUsage(const char* name); -bool Platform_getLongOption(int opt, int argc, char** argv); +CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv); static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) { Generic_gettime_realtime(tv, msec); diff --git a/linux/ProcessField.h b/linux/ProcessField.h index 7047592..17cafa9 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 diff --git a/netbsd/NetBSDProcess.c b/netbsd/NetBSDProcess.c index 1597ed3..8d2b4c4 100644 --- a/netbsd/NetBSDProcess.c +++ b/netbsd/NetBSDProcess.c @@ -138,7 +138,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { }, [ST_UID] = { .name = "ST_UID", - .title = " UID ", + .title = "UID", .description = "User ID of the process owner", .flags = 0, }, @@ -165,7 +165,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { }, [USER] = { .name = "USER", - .title = "USER ", + .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, @@ -211,7 +211,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { }; Process* NetBSDProcess_new(const Settings* settings) { - NetBSDProcess* this = xCalloc(sizeof(NetBSDProcess), 1); + NetBSDProcess* this = xCalloc(1, sizeof(NetBSDProcess)); Object_setClass(this, Class(NetBSDProcess)); Process_init(&this->super, settings); return &this->super; diff --git a/netbsd/NetBSDProcessList.c b/netbsd/NetBSDProcessList.c index 379a491..ab0f0b7 100644 --- a/netbsd/NetBSDProcessList.c +++ b/netbsd/NetBSDProcessList.c @@ -331,31 +331,33 @@ static void NetBSDProcessList_scanProcs(NetBSDProcessList* this) { int nlwps = 0; const struct kinfo_lwp* klwps = kvm_getlwps(this->kd, kproc->p_pid, kproc->p_paddr, sizeof(struct kinfo_lwp), &nlwps); + /* TODO: According to the link below, SDYING should be a regarded state */ + /* Taken from: https://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/sys/proc.h */ switch (kproc->p_realstat) { - case SIDL: proc->state = 'I'; break; + case SIDL: proc->state = IDLE; break; case SACTIVE: // We only consider the first LWP with a one of the below states. for (int j = 0; j < nlwps; j++) { if (klwps) { switch (klwps[j].l_stat) { - case LSONPROC: proc->state = 'P'; break; - case LSRUN: proc->state = 'R'; break; - case LSSLEEP: proc->state = 'S'; break; - case LSSTOP: proc->state = 'T'; break; - default: proc->state = '?'; + case LSONPROC: proc->state = RUNNING; break; + case LSRUN: proc->state = RUNNABLE; break; + case LSSLEEP: proc->state = SLEEPING; break; + case LSSTOP: proc->state = STOPPED; break; + default: proc->state = UNKNOWN; } - if (proc->state != '?') + if (proc->state != UNKNOWN) break; } else { - proc->state = '?'; + proc->state = UNKNOWN; break; } } break; - case SSTOP: proc->state = 'T'; break; - case SZOMB: proc->state = 'Z'; break; - case SDEAD: proc->state = 'D'; break; - default: proc->state = '?'; + case SSTOP: proc->state = STOPPED; break; + case SZOMB: proc->state = ZOMBIE; break; + case SDEAD: proc->state = DEFUNCT; break; + default: proc->state = UNKNOWN; } if (Process_isKernelThread(proc)) { @@ -365,8 +367,7 @@ static void NetBSDProcessList_scanProcs(NetBSDProcessList* this) { } this->super.totalTasks++; - // SRUN ('R') means runnable, not running - if (proc->state == 'P') { + if (proc->state == RUNNING) { this->super.runningTasks++; } proc->updated = true; diff --git a/netbsd/Platform.c b/netbsd/Platform.c index ac81464..3b27548 100644 --- a/netbsd/Platform.c +++ b/netbsd/Platform.c @@ -174,8 +174,9 @@ const MeterClass* const Platform_meterTypes[] = { NULL }; -void Platform_init(void) { +bool Platform_init(void) { /* no platform-specific setup needed */ + return true; } void Platform_done(void) { diff --git a/netbsd/Platform.h b/netbsd/Platform.h index 1d1115e..e650bcd 100644 --- a/netbsd/Platform.h +++ b/netbsd/Platform.h @@ -24,6 +24,7 @@ in the source distribution for its full text. #include "Process.h" #include "ProcessLocksScreen.h" #include "SignalsPanel.h" +#include "CommandLine.h" #include "generic/gettime.h" #include "generic/hostname.h" #include "generic/uname.h" @@ -42,7 +43,7 @@ extern const unsigned int Platform_numberOfSignals; extern const MeterClass* const Platform_meterTypes[]; -void Platform_init(void); +bool Platform_init(void); void Platform_done(void); @@ -82,8 +83,8 @@ static inline void Platform_getRelease(char** string) { static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { } -static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) { - return false; +static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) { + return STATUS_ERROR_EXIT; } static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) { diff --git a/openbsd/OpenBSDProcess.c b/openbsd/OpenBSDProcess.c index 52dcb0e..ac3def3 100644 --- a/openbsd/OpenBSDProcess.c +++ b/openbsd/OpenBSDProcess.c @@ -136,7 +136,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { }, [ST_UID] = { .name = "ST_UID", - .title = " UID ", + .title = "UID", .description = "User ID of the process owner", .flags = 0, }, @@ -163,7 +163,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { }, [USER] = { .name = "USER", - .title = "USER ", + .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, @@ -203,7 +203,7 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { }; Process* OpenBSDProcess_new(const Settings* settings) { - OpenBSDProcess* this = xCalloc(sizeof(OpenBSDProcess), 1); + OpenBSDProcess* this = xCalloc(1, sizeof(OpenBSDProcess)); Object_setClass(this, Class(OpenBSDProcess)); Process_init(&this->super, settings); return &this->super; diff --git a/openbsd/OpenBSDProcessList.c b/openbsd/OpenBSDProcessList.c index 476af61..af7879e 100644 --- a/openbsd/OpenBSDProcessList.c +++ b/openbsd/OpenBSDProcessList.c @@ -345,15 +345,16 @@ static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) { proc->user = UsersTable_getRef(this->super.usersTable, proc->st_uid); } + /* Taken from: https://github.com/openbsd/src/blob/6a38af0976a6870911f4b2de19d8da28723a5778/sys/sys/proc.h#L420 */ switch (kproc->p_stat) { - case SIDL: proc->state = 'I'; break; - case SRUN: proc->state = 'P'; break; - case SSLEEP: proc->state = 'S'; break; - case SSTOP: proc->state = 'T'; break; - case SZOMB: proc->state = 'Z'; break; - case SDEAD: proc->state = 'D'; break; - case SONPROC: proc->state = 'R'; break; - default: proc->state = '?'; + case SIDL: proc->state = IDLE; break; + case SRUN: proc->state = RUNNABLE; break; + case SSLEEP: proc->state = SLEEPING; break; + case SSTOP: proc->state = STOPPED; break; + case SZOMB: proc->state = ZOMBIE; break; + case SDEAD: proc->state = DEFUNCT; break; + case SONPROC: proc->state = RUNNING; break; + default: proc->state = UNKNOWN; } if (Process_isKernelThread(proc)) { @@ -363,7 +364,7 @@ static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) { } this->super.totalTasks++; - if (proc->state == 'R') { + if (proc->state == RUNNING) { this->super.runningTasks++; } @@ -388,7 +389,7 @@ static void kernelCPUTimesToHtop(const u_int64_t* times, CPUData* cpu) { unsigned long long sysAllTime = times[CP_INTR] + times[CP_SYS]; - // XXX Not sure if CP_SPIN should be added to sysAllTime. + // XXX Not sure if CP_SPIN should be added to sysAllTime. // See https://github.com/openbsd/src/commit/531d8034253fb82282f0f353c086e9ad827e031c #ifdef CP_SPIN sysAllTime += times[CP_SPIN]; diff --git a/openbsd/Platform.c b/openbsd/Platform.c index b941ba7..15467e1 100644 --- a/openbsd/Platform.c +++ b/openbsd/Platform.c @@ -121,8 +121,9 @@ const MeterClass* const Platform_meterTypes[] = { NULL }; -void Platform_init(void) { +bool Platform_init(void) { /* no platform-specific setup needed */ + return true; } void Platform_done(void) { diff --git a/openbsd/Platform.h b/openbsd/Platform.h index b6823b5..fd6a657 100644 --- a/openbsd/Platform.h +++ b/openbsd/Platform.h @@ -20,6 +20,7 @@ in the source distribution for its full text. #include "Process.h" #include "ProcessLocksScreen.h" #include "SignalsPanel.h" +#include "CommandLine.h" #include "generic/gettime.h" #include "generic/hostname.h" #include "generic/uname.h" @@ -34,7 +35,7 @@ extern const unsigned int Platform_numberOfSignals; extern const MeterClass* const Platform_meterTypes[]; -void Platform_init(void); +bool Platform_init(void); void Platform_done(void); @@ -76,8 +77,8 @@ static inline void Platform_getRelease(char** string) { static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { } -static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) { - return false; +static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) { + return STATUS_ERROR_EXIT; } static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) { diff --git a/pcp/PCPProcess.c b/pcp/PCPProcess.c index a983a96..e686d51 100644 --- a/pcp/PCPProcess.c +++ b/pcp/PCPProcess.c @@ -53,11 +53,11 @@ const ProcessFieldData Process_fields[] = { [M_DRS] = { .name = "M_DRS", .title = " DATA ", .description = "Size of the data segment plus stack usage of the process", .flags = 0, .defaultSortDesc = true, }, [M_LRS] = { .name = "M_LRS", .title = " LIB ", .description = "The library size of the process (unused since Linux 2.6; always 0)", .flags = 0, .defaultSortDesc = true, }, [M_DT] = { .name = "M_DT", .title = " DIRTY ", .description = "Size of the dirty pages of the process (unused since Linux 2.6; always 0)", .flags = 0, .defaultSortDesc = true, }, - [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, }, + [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, }, [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, }, [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, }, [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, }, - [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, + [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, }, [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, .defaultSortDesc = true, }, [TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, }, diff --git a/pcp/PCPProcessList.c b/pcp/PCPProcessList.c index 554e43d..cae097f 100644 --- a/pcp/PCPProcessList.c +++ b/pcp/PCPProcessList.c @@ -142,10 +142,27 @@ static inline char Metric_instance_char(int metric, int pid, int offset, char fa return fallback; } +static inline ProcessState PCPProcessList_getProcessState(char state) { + switch (state) { + case '?': return UNKNOWN; + case 'R': return RUNNING; + case 'W': return WAITING; + case 'D': return UNINTERRUPTIBLE_WAIT; + case 'P': return PAGING; + case 'T': return STOPPED; + case 't': return TRACED; + case 'Z': return ZOMBIE; + case 'X': return DEFUNCT; + case 'I': return IDLE; + case 'S': return SLEEPING; + default: return UNKNOWN; + } +} + static void PCPProcessList_updateID(Process* process, int pid, int offset) { process->tgid = Metric_instance_u32(PCP_PROC_TGID, pid, offset, 1); process->ppid = Metric_instance_u32(PCP_PROC_PPID, pid, offset, 1); - process->state = Metric_instance_char(PCP_PROC_STATE, pid, offset, '?'); + process->state = PCPProcessList_getProcessState(Metric_instance_char(PCP_PROC_STATE, pid, offset, '?')); } static void PCPProcessList_updateInfo(Process* process, int pid, int offset, char* command, size_t commLen) { @@ -283,7 +300,7 @@ static void PCPProcessList_updateUsername(Process* process, int pid, int offset, static void PCPProcessList_updateCmdline(Process* process, int pid, int offset, const char* comm) { pmAtomValue value; if (!PCPMetric_instance(PCP_PROC_PSARGS, pid, offset, &value, PM_TYPE_STRING)) { - if (process->state != 'Z') + if (process->state != ZOMBIE) process->isKernelThread = true; Process_updateCmdline(process, NULL, 0, 0); return; @@ -351,7 +368,7 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period, if (preExisting && hideKernelThreads && Process_isKernelThread(proc)) { proc->updated = true; proc->show = false; - if (proc->state == 'R') + if (proc->state == RUNNING) pl->runningTasks++; pl->kernelThreads++; pl->totalTasks++; @@ -360,7 +377,7 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period, if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) { proc->updated = true; proc->show = false; - if (proc->state == 'R') + if (proc->state == RUNNING) pl->runningTasks++; pl->userlandThreads++; pl->totalTasks++; @@ -398,7 +415,7 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period, PCPProcessList_updateCmdline(proc, pid, offset, command); Process_fillStarttimeBuffer(proc); ProcessList_add(pl, proc); - } else if (settings->updateProcessNames && proc->state != 'Z') { + } else if (settings->updateProcessNames && proc->state != ZOMBIE) { PCPProcessList_updateCmdline(proc, pid, offset, command); } @@ -420,7 +437,7 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period, if (settings->flags & PROCESS_FLAG_LINUX_AUTOGROUP) PCPProcessList_readAutogroup(pp, pid, offset); - if (proc->state == 'Z' && !proc->cmdline && command[0]) { + if (proc->state == ZOMBIE && !proc->cmdline && command[0]) { Process_updateCmdline(proc, command, 0, strlen(command)); } else if (Process_isThread(proc)) { if ((settings->showThreadNames || Process_isKernelThread(proc)) && command[0]) { @@ -439,7 +456,7 @@ static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period, (hideUserlandThreads && Process_isUserlandThread(proc))); pl->totalTasks++; - if (proc->state == 'R') + if (proc->state == RUNNING) pl->runningTasks++; proc->updated = true; } diff --git a/pcp/Platform.c b/pcp/Platform.c index cd7b1f4..150660a 100644 --- a/pcp/Platform.c +++ b/pcp/Platform.c @@ -257,7 +257,7 @@ size_t Platform_addMetric(PCPMetric id, const char* name) { /* global state from the environment and command line arguments */ pmOptions opts; -void Platform_init(void) { +bool Platform_init(void) { const char* source; if (opts.context == PM_CONTEXT_ARCHIVE) { source = opts.archives[0]; @@ -277,12 +277,12 @@ void Platform_init(void) { } if (sts < 0) { fprintf(stderr, "Cannot setup PCP metric source: %s\n", pmErrStr(sts)); - exit(1); + return false; } /* setup timezones and other general startup preparation completion */ if (pmGetContextOptions(sts, &opts) < 0 || opts.errors) { pmflush(); - exit(1); + return false; } pcp = xCalloc(1, sizeof(Platform)); @@ -309,7 +309,8 @@ void Platform_init(void) { sts = pmLookupName(pcp->totalMetrics, pcp->names, pcp->pmids); if (sts < 0) { fprintf(stderr, "Error: cannot lookup metric names: %s\n", pmErrStr(sts)); - exit(1); + Platform_done(); + return false; } for (size_t i = 0; i < pcp->totalMetrics; i++) { @@ -361,6 +362,8 @@ void Platform_init(void) { Platform_getRelease(0); Platform_getMaxCPU(); Platform_getMaxPid(); + + return true; } void Platform_dynamicColumnsDone(Hashtable* columns) { @@ -697,16 +700,16 @@ void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { " --timezone=TZ set reporting timezone\n"); } -bool Platform_getLongOption(int opt, ATTR_UNUSED int argc, char** argv) { +CommandLineStatus Platform_getLongOption(int opt, ATTR_UNUSED int argc, char** argv) { /* libpcp export without a header definition */ extern void __pmAddOptHost(pmOptions*, char*); switch (opt) { case PLATFORM_LONGOPT_HOST: /* --host=HOSTSPEC */ if (argv[optind][0] == '\0') - return false; + return STATUS_ERROR_EXIT; __pmAddOptHost(&opts, optarg); - return true; + return STATUS_OK; case PLATFORM_LONGOPT_HOSTZONE: /* --hostzone */ if (opts.timezone) { @@ -715,23 +718,23 @@ bool Platform_getLongOption(int opt, ATTR_UNUSED int argc, char** argv) { } else { opts.tzflag = 1; } - return true; + return STATUS_OK; case PLATFORM_LONGOPT_TIMEZONE: /* --timezone=TZ */ if (argv[optind][0] == '\0') - return false; + return STATUS_ERROR_EXIT; if (opts.tzflag) { pmprintf("%s: at most one of -Z and -z allowed\n", pmGetProgname()); opts.errors++; } else { opts.timezone = optarg; } - return true; + return STATUS_OK; default: break; } - return false; + return STATUS_ERROR_EXIT; } void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) { diff --git a/pcp/Platform.h b/pcp/Platform.h index 37a8799..ad38cbb 100644 --- a/pcp/Platform.h +++ b/pcp/Platform.h @@ -34,6 +34,7 @@ in the source distribution for its full text. #include "ProcessLocksScreen.h" #include "RichString.h" #include "SignalsPanel.h" +#include "CommandLine.h" #include "pcp/PCPDynamicColumn.h" #include "pcp/PCPDynamicMeter.h" @@ -67,7 +68,7 @@ extern const unsigned int Platform_numberOfSignals; extern const MeterClass* const Platform_meterTypes[]; -void Platform_init(void); +bool Platform_init(void); void Platform_done(void); @@ -126,7 +127,7 @@ enum { void Platform_longOptionsUsage(const char* name); -bool Platform_getLongOption(int opt, int argc, char** argv); +CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv); extern pmOptions opts; diff --git a/solaris/Platform.c b/solaris/Platform.c index eabbab5..cdc79ae 100644 --- a/solaris/Platform.c +++ b/solaris/Platform.c @@ -122,8 +122,9 @@ const MeterClass* const Platform_meterTypes[] = { NULL }; -void Platform_init(void) { +bool Platform_init(void) { /* no platform-specific setup needed */ + return true; } void Platform_done(void) { diff --git a/solaris/Platform.h b/solaris/Platform.h index a60b978..b140788 100644 --- a/solaris/Platform.h +++ b/solaris/Platform.h @@ -35,6 +35,7 @@ in the source distribution for its full text. #include "NetworkIOMeter.h" #include "ProcessLocksScreen.h" #include "SignalsPanel.h" +#include "CommandLine.h" #include "generic/gettime.h" #include "generic/hostname.h" #include "generic/uname.h" @@ -59,7 +60,7 @@ extern const ProcessField Platform_defaultFields[]; extern const MeterClass* const Platform_meterTypes[]; -void Platform_init(void); +bool Platform_init(void); void Platform_done(void); @@ -105,8 +106,8 @@ static inline void Platform_getRelease(char** string) { static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { } -static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) { - return false; +static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) { + return STATUS_ERROR_EXIT; } static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) { diff --git a/solaris/SolarisProcess.c b/solaris/SolarisProcess.c index c1f4958..b0f6d94 100644 --- a/solaris/SolarisProcess.c +++ b/solaris/SolarisProcess.c @@ -39,11 +39,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, }, [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, }, [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, }, - [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, }, + [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, }, [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, }, [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, }, [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, }, - [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, + [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, }, [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, }, [TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, }, diff --git a/solaris/SolarisProcessList.c b/solaris/SolarisProcessList.c index 33c6477..71e85fc 100644 --- a/solaris/SolarisProcessList.c +++ b/solaris/SolarisProcessList.c @@ -363,6 +363,19 @@ static void SolarisProcessList_updateCwd(pid_t pid, Process* proc) { free_and_xStrdup(&proc->procCwd, target); } +/* Taken from: https://docs.oracle.com/cd/E19253-01/817-6223/6mlkidlom/index.html#tbl-sched-state */ +static inline ProcessState SolarisProcessList_getProcessState(char state) { + switch (state) { + case 'S': return SLEEPING; + case 'R': return RUNNABLE; + case 'O': return RUNNING; + case 'Z': return ZOMBIE; + case 'T': return STOPPED; + case 'I': return IDLE; + default: return UNKNOWN; + } +} + /* NOTE: the following is a callback function of type proc_walk_f * and MUST conform to the appropriate definition in order * to work. See libproc(3LIB) on a Solaris or Illumos @@ -402,7 +415,7 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, proc->priority = _lwpsinfo->pr_pri; proc->nice = _lwpsinfo->pr_nice - NZERO; proc->processor = _lwpsinfo->pr_onpro; - proc->state = _lwpsinfo->pr_sname; + proc->state = SolarisProcessList_getProcessState(_lwpsinfo->pr_sname); // NOTE: This 'percentage' is a 16-bit BINARY FRACTIONS where 1.0 = 0x8000 // Source: https://docs.oracle.com/cd/E19253-01/816-5174/proc-4/index.html // (accessed on 18 November 2017) @@ -462,11 +475,11 @@ static int SolarisProcessList_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, if (proc->isKernelThread && !pl->settings->hideKernelThreads) { pl->kernelThreads += proc->nlwp; pl->totalTasks += proc->nlwp + 1; - if (proc->state == 'O') { + if (proc->state == RUNNING) { pl->runningTasks++; } } else if (!proc->isKernelThread) { - if (proc->state == 'O') { + if (proc->state == RUNNING) { pl->runningTasks++; } if (pl->settings->hideUserlandThreads) { diff --git a/unsupported/Platform.c b/unsupported/Platform.c index 9be56ea..e55de4a 100644 --- a/unsupported/Platform.c +++ b/unsupported/Platform.c @@ -68,8 +68,9 @@ const MeterClass* const Platform_meterTypes[] = { static const char Platform_unsupported[] = "unsupported"; -void Platform_init(void) { +bool Platform_init(void) { /* no platform-specific setup needed */ + return true; } void Platform_done(void) { diff --git a/unsupported/Platform.h b/unsupported/Platform.h index ace7aae..7f4ad9a 100644 --- a/unsupported/Platform.h +++ b/unsupported/Platform.h @@ -15,6 +15,7 @@ in the source distribution for its full text. #include "NetworkIOMeter.h" #include "ProcessLocksScreen.h" #include "SignalsPanel.h" +#include "CommandLine.h" #include "generic/gettime.h" #include "unsupported/UnsupportedProcess.h" @@ -27,7 +28,7 @@ extern const ProcessField Platform_defaultFields[]; extern const MeterClass* const Platform_meterTypes[]; -void Platform_init(void); +bool Platform_init(void); void Platform_done(void); @@ -65,8 +66,8 @@ void Platform_getRelease(char** string); static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { } -static inline bool Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) { - return false; +static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) { + return STATUS_ERROR_EXIT; } static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) { diff --git a/unsupported/UnsupportedProcess.c b/unsupported/UnsupportedProcess.c index af88a3e..b1f63c6 100644 --- a/unsupported/UnsupportedProcess.c +++ b/unsupported/UnsupportedProcess.c @@ -34,11 +34,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { [PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, }, [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, }, [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, }, - [ST_UID] = { .name = "ST_UID", .title = " UID ", .description = "User ID of the process owner", .flags = 0, }, + [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, }, [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = "CPU% ", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, }, [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, }, [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, }, - [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, + [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, }, [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, }, [TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, }, diff --git a/unsupported/UnsupportedProcessList.c b/unsupported/UnsupportedProcessList.c index 470c65b..b64de41 100644 --- a/unsupported/UnsupportedProcessList.c +++ b/unsupported/UnsupportedProcessList.c @@ -57,7 +57,7 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { proc->updated = true; - proc->state = 'R'; + proc->state = RUNNING; proc->isKernelThread = false; proc->isUserlandThread = false; proc->show = true; /* Reflected in proc->settings-> "hideXXX" really */ -- cgit v1.2.3