diff --git a/Makefile.am b/Makefile.am index f9b9a7d4..c66aa46c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -328,6 +328,29 @@ myhtopplatheaders = $(solaris_platform_headers) myhtopplatsources = $(solaris_platform_sources) endif +# Performance Co-Pilot (PCP) +# -------------------------- + +pcp_platform_headers = \ + pcp/PCPProcess.h \ + pcp/PCPProcessList.h \ + pcp/Platform.h \ + pcp/ProcessField.h \ + linux/PressureStallMeter.h \ + linux/ZramMeter.h \ + linux/ZramStats.h + +if HTOP_PCP +myhtopplatsources = \ + pcp/PCPProcess.c \ + pcp/PCPProcessList.c \ + pcp/Platform.c \ + linux/PressureStallMeter.c \ + linux/ZramMeter.c + +myhtopplatheaders = $(pcp_platform_headers) +endif + # Unsupported # ----------- diff --git a/configure.ac b/configure.ac index 78250621..f9cfa914 100644 --- a/configure.ac +++ b/configure.ac @@ -95,6 +95,31 @@ esac # ---------------------------------------------------------------------- +# ---------------------------------------------------------------------- +# Checks for a PCP-based htop build. (https://pcp.io) +# ---------------------------------------------------------------------- + +AC_ARG_ENABLE([pcp], + [AS_HELP_STRING([--enable-pcp], + [build a pcp htop binary @<:@default=no@:>@])], + [], + [enable_pcp=no]) +case "$enable_pcp" in + no) + ;; + yes) + AC_CHECK_HEADERS([pcp/pmapi.h], [my_htop_platform=pcp], + [AC_MSG_ERROR([can not find PCP header file])]) + AC_SEARCH_LIBS([pmNewContext], [pcp], [], [AC_MSG_ERROR([can not find pcp library])]) + AC_DEFINE([HTOP_PCP], [1], [Define if building pcp htop binary.]) + ;; + *) + AC_MSG_ERROR([bad value '$enable_static' for --enable-static option]) + ;; +esac + +# ---------------------------------------------------------------------- + # ---------------------------------------------------------------------- # Checks for generic header files. @@ -608,6 +633,7 @@ AM_CONDITIONAL([HTOP_DRAGONFLYBSD], [test "$my_htop_platform" = dragonflybsd]) AM_CONDITIONAL([HTOP_OPENBSD], [test "$my_htop_platform" = openbsd]) AM_CONDITIONAL([HTOP_DARWIN], [test "$my_htop_platform" = darwin]) AM_CONDITIONAL([HTOP_SOLARIS], [test "$my_htop_platform" = solaris]) +AM_CONDITIONAL([HTOP_PCP], [test "$my_htop_platform" = pcp]) AM_CONDITIONAL([HTOP_UNSUPPORTED], [test "$my_htop_platform" = unsupported]) AC_SUBST(my_htop_platform) diff --git a/pcp/PCPProcess.c b/pcp/PCPProcess.c new file mode 100644 index 00000000..7e8f59ae --- /dev/null +++ b/pcp/PCPProcess.c @@ -0,0 +1,517 @@ +/* +htop - PCPProcess.c +(C) 2014 Hisham H. Muhammad +(C) 2020 htop dev team +(C) 2020-2021 Red Hat, Inc. All Rights Reserved. +Released under the GNU GPLv2, see the COPYING file +in the source distribution for its full text. +*/ + +#include "PCPProcess.h" + +#include +#include +#include +#include +#include +#include + +#include "CRT.h" +#include "Process.h" +#include "ProvideCurses.h" +#include "XUtils.h" + +/* Used to identify kernel threads in Comm column */ +static const char *const kthreadID = "KTHREAD"; + +const ProcessFieldData Process_fields[] = { + [0] = { .name = "", .title = NULL, .description = NULL, .flags = 0, }, + [PID] = { .name = "PID", .title = "PID", .description = "Process/thread ID", .flags = 0, .pidColumn = true, }, + [COMM] = { .name = "Command", .title = "Command ", .description = "Command line", .flags = 0, }, + [STATE] = { .name = "STATE", .title = "S ", .description = "Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging, I idle)", .flags = 0, }, + [PPID] = { .name = "PPID", .title = "PPID", .description = "Parent process ID", .flags = 0, }, + [PGRP] = { .name = "PGRP", .title = "PGRP", .description = "Process group ID", .flags = 0, }, + [SESSION] = { .name = "SESSION", .title = "SID", .description = "Process's session ID", .flags = 0, }, + [TTY_NR] = { .name = "TTY_NR", .title = "TTY ", .description = "Controlling terminal", .flags = 0, }, + [TPGID] = { .name = "TPGID", .title = "TPGID", .description = "Process ID of the fg process group of the controlling terminal", .flags = 0, }, + [MINFLT] = { .name = "MINFLT", .title = " MINFLT ", .description = "Number of minor faults which have not required loading a memory page from disk", .flags = 0, .defaultSortDesc = true, }, + [CMINFLT] = { .name = "CMINFLT", .title = " CMINFLT ", .description = "Children processes' minor faults", .flags = 0, .defaultSortDesc = true, }, + [MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of major faults which have required loading a memory page from disk", .flags = 0, .defaultSortDesc = true, }, + [CMAJFLT] = { .name = "CMAJFLT", .title = " CMAJFLT ", .description = "Children processes' major faults", .flags = 0, .defaultSortDesc = true, }, + [UTIME] = { .name = "UTIME", .title = " UTIME+ ", .description = "User CPU time - time the process spent executing in user mode", .flags = 0, .defaultSortDesc = true, }, + [STIME] = { .name = "STIME", .title = " STIME+ ", .description = "System CPU time - time the kernel spent running system calls for this process", .flags = 0, .defaultSortDesc = true, }, + [CUTIME] = { .name = "CUTIME", .title = " CUTIME+ ", .description = "Children processes' user CPU time", .flags = 0, .defaultSortDesc = true, }, + [CSTIME] = { .name = "CSTIME", .title = " CSTIME+ ", .description = "Children processes' system CPU time", .flags = 0, .defaultSortDesc = true, }, + [PRIORITY] = { .name = "PRIORITY", .title = "PRI ", .description = "Kernel's internal priority for the process", .flags = 0, }, + [NICE] = { .name = "NICE", .title = " NI ", .description = "Nice value (the higher the value, the more it lets other processes take priority)", .flags = 0, }, + [STARTTIME] = { .name = "STARTTIME", .title = "START ", .description = "Time the process was started", .flags = 0, }, + [PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "If 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, }, + [M_SHARE] = { .name = "M_SHARE", .title = " SHR ", .description = "Size of the process's shared pages", .flags = 0, .defaultSortDesc = true, }, + [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 (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, }, + [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, }, + [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, }, + [RCHAR] = { .name = "RCHAR", .title = "RCHAR ", .description = "Number of bytes the process has read", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, + [WCHAR] = { .name = "WCHAR", .title = "WCHAR ", .description = "Number of bytes the process has written", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, + [SYSCR] = { .name = "SYSCR", .title = " READ_SYSC ", .description = "Number of read(2) syscalls for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, + [SYSCW] = { .name = "SYSCW", .title = " WRITE_SYSC ", .description = "Number of write(2) syscalls for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, + [RBYTES] = { .name = "RBYTES", .title = " IO_R ", .description = "Bytes of read(2) I/O for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, + [WBYTES] = { .name = "WBYTES", .title = " IO_W ", .description = "Bytes of write(2) I/O for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, + [CNCLWB] = { .name = "CNCLWB", .title = " IO_C ", .description = "Bytes of cancelled write(2) I/O", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, }, + [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, }, + [OOM] = { .name = "OOM", .title = " OOM ", .description = "OOM (Out-of-Memory) killer score", .flags = PROCESS_FLAG_LINUX_OOM, .defaultSortDesc = true, }, + [PERCENT_CPU_DELAY] = { .name = "PERCENT_CPU_DELAY", .title = "CPUD% ", .description = "CPU delay %", .flags = 0, .defaultSortDesc = true, }, + [PERCENT_IO_DELAY] = { .name = "PERCENT_IO_DELAY", .title = "IOD% ", .description = "Block I/O delay %", .flags = 0, .defaultSortDesc = true, }, + [PERCENT_SWAP_DELAY] = { .name = "PERCENT_SWAP_DELAY", .title = "SWAPD% ", .description = "Swapin delay %", .flags = 0, .defaultSortDesc = true, }, + [M_PSS] = { .name = "M_PSS", .title = " PSS ", .description = "proportional set size, same as M_RESIDENT but each page is divided by the number of processes sharing it.", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, }, + [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, }, + [PROC_COMM] = { .name = "COMM", .title = "COMM ", .description = "comm string of the process from /proc/[pid]/comm", .flags = 0, }, +}; + +/* This function returns the string displayed in Command column, so that sorting + * happens on what is displayed - whether comm, full path, basename, etc.. So + * this follows PCPProcess_writeField(COMM) and PCPProcess_writeCommand */ +static const char* PCPProcess_getCommandStr(const Process *this) { + const PCPProcess *pp = (const PCPProcess *)this; + if ((Process_isUserlandThread(this) && this->settings->showThreadNames) || !pp->mergedCommand.str) { + return this->comm; + } + return pp->mergedCommand.str; +} + +Process* PCPProcess_new(const Settings* settings) { + PCPProcess* this = xCalloc(1, sizeof(PCPProcess)); + Object_setClass(this, Class(PCPProcess)); + Process_init(&this->super, settings); + return &this->super; +} + +void Process_delete(Object* cast) { + PCPProcess* this = (PCPProcess*) cast; + Process_done((Process*)cast); + free(this->cgroup); + free(this->secattr); + free(this->ttyDevice); + free(this->procComm); + free(this->mergedCommand.str); + free(this); +} + +static void PCPProcess_printDelay(float delay_percent, char* buffer, int n) { + if (isnan(delay_percent)) { + xSnprintf(buffer, n, " N/A "); + } else { + xSnprintf(buffer, n, "%4.1f ", delay_percent); + } +} + +/* +TASK_COMM_LEN is defined to be 16 for /proc/[pid]/comm in man proc(5), but is +not available in an userspace header - so define it. Note: when colorizing a +basename with the comm prefix, the entire basename (not just the comm prefix) +is colorized for better readability, and it is implicit that only up to +(TASK_COMM_LEN - 1) could be comm. +*/ +#define TASK_COMM_LEN 16 + +/* +This function makes the merged Command string. It also stores the offsets of +the basename, comm w.r.t the merged Command string - these offsets will be used +by PCPProcess_writeCommand() for coloring. The merged Command string is also +returned by PCPProcess_getCommandStr() for searching, sorting and filtering. +*/ +void PCPProcess_makeCommandStr(Process* this) { + PCPProcess *pp = (PCPProcess *)this; + PCPProcessMergedCommand *mc = &pp->mergedCommand; + + bool showMergedCommand = this->settings->showMergedCommand; + bool showProgramPath = this->settings->showProgramPath; + bool searchCommInCmdline = this->settings->findCommInCmdline; + + /* pp->mergedCommand.str needs updating only if its state or contents + * changed. Its content is based on the fields cmdline and comm. */ + if ( + mc->prevMergeSet == showMergedCommand && + mc->prevPathSet == showProgramPath && + mc->prevCommSet == searchCommInCmdline && + !mc->cmdlineChanged && + !mc->commChanged + ) { + return; + } + + /* The field separtor "│" has been chosen such that it will not match any + * valid string used for searching or filtering */ + const char *SEPARATOR = CRT_treeStr[TREE_STR_VERT]; + const int SEPARATOR_LEN = strlen(SEPARATOR); + + /* Check for any changed fields since we last built this string */ + if (mc->cmdlineChanged || mc->commChanged) { + free(mc->str); + /* Accommodate the column text, two field separators and terminating NUL */ + mc->str = xCalloc(1, mc->maxLen + 2*SEPARATOR_LEN + 1); + } + + /* Preserve the settings used in this run */ + mc->prevMergeSet = showMergedCommand; + mc->prevPathSet = showProgramPath; + mc->prevCommSet = searchCommInCmdline; + + /* Mark everything as unchanged */ + mc->cmdlineChanged = false; + mc->commChanged = false; + + /* Clear any separators */ + mc->sep1 = 0; + mc->sep2 = 0; + /* Clear any highlighting locations */ + mc->baseStart = 0; + mc->baseEnd = 0; + mc->commStart = 0; + mc->commEnd = 0; + + const char *cmdline = this->comm; + const char *procComm = pp->procComm; + + char *strStart = mc->str; + char *str = strStart; + + int cmdlineBasenameOffset = pp->procCmdlineBasenameOffset; + int cmdlineBasenameEnd = pp->procCmdlineBasenameEnd; + + if (!cmdline) { + cmdlineBasenameOffset = 0; + cmdlineBasenameEnd = 0; + cmdline = "(zombie)"; + } + + assert(cmdlineBasenameOffset >= 0); + assert(cmdlineBasenameOffset <= (int)strlen(cmdline)); + + if (showMergedCommand && procComm && strlen(procComm)) { /* Prefix column with comm */ + if (strncmp(cmdline + cmdlineBasenameOffset, procComm, MINIMUM(TASK_COMM_LEN - 1, strlen(procComm))) != 0) { + mc->commStart = 0; + mc->commEnd = strlen(procComm); + + str = stpcpy(str, procComm); + + mc->sep1 = str - strStart; + str = stpcpy(str, SEPARATOR); + } + } + + if (showProgramPath) { + (void) stpcpy(str, cmdline); + mc->baseStart = cmdlineBasenameOffset; + mc->baseEnd = cmdlineBasenameEnd; + } else { + (void) stpcpy(str, cmdline + cmdlineBasenameOffset); + mc->baseStart = 0; + mc->baseEnd = cmdlineBasenameEnd - cmdlineBasenameOffset; + } + + if (mc->sep1) { + mc->baseStart += str - strStart - SEPARATOR_LEN + 1; + mc->baseEnd += str - strStart - SEPARATOR_LEN + 1; + } +} + +static void PCPProcess_writeCommand(const Process* this, int attr, int baseAttr, RichString* str) { + const PCPProcess *pp = (const PCPProcess *)this; + const PCPProcessMergedCommand *mc = &pp->mergedCommand; + + int strStart = RichString_size(str); + + int baseStart = strStart + pp->mergedCommand.baseStart; + int baseEnd = strStart + pp->mergedCommand.baseEnd; + int commStart = strStart + pp->mergedCommand.commStart; + int commEnd = strStart + pp->mergedCommand.commEnd; + + int commAttr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_COMM : PROCESS_COMM]; + + bool highlightBaseName = this->settings->highlightBaseName; + + RichString_appendWide(str, attr, pp->mergedCommand.str); + + if (pp->mergedCommand.commEnd) { + if (!pp->mergedCommand.separateComm && commStart == baseStart && highlightBaseName) { + /* If it was matched with binaries basename, make it bold if needed */ + if (commEnd > baseEnd) { + RichString_setAttrn(str, A_BOLD | baseAttr, baseStart, baseEnd - baseStart); + RichString_setAttrn(str, A_BOLD | commAttr, baseEnd, commEnd - baseEnd); + } else if (commEnd < baseEnd) { + RichString_setAttrn(str, A_BOLD | commAttr, commStart, commEnd - commStart); + RichString_setAttrn(str, A_BOLD | baseAttr, commEnd, baseEnd - commEnd); + } else { + // Actually should be highlighted commAttr, but marked baseAttr to reduce visual noise + RichString_setAttrn(str, A_BOLD | baseAttr, commStart, commEnd - commStart); + } + + baseStart = baseEnd; + } else { + RichString_setAttrn(str, commAttr, commStart, commEnd - commStart); + } + } + + if (baseStart < baseEnd && highlightBaseName) { + RichString_setAttrn(str, baseAttr, baseStart, baseEnd - baseStart); + } + + if (mc->sep1) + RichString_setAttrn(str, CRT_colors[FAILED_READ], strStart + mc->sep1, 1); + if (mc->sep2) + RichString_setAttrn(str, CRT_colors[FAILED_READ], strStart + mc->sep2, 1); +} + +static void PCPProcess_writeCommandField(const Process *this, RichString *str, char *buffer, int n, int attr) { + /* This code is from Process_writeField for COMM, but we invoke + * PCPProcess_writeCommand to display the full binary path + * (or its basename)│/proc/pid/comm│/proc/pid/cmdline */ + int baseattr = CRT_colors[PROCESS_BASENAME]; + if (this->settings->highlightThreads && Process_isThread(this)) { + attr = CRT_colors[PROCESS_THREAD]; + baseattr = CRT_colors[PROCESS_THREAD_BASENAME]; + } + if (!this->settings->treeView || this->indent == 0) { + PCPProcess_writeCommand(this, attr, baseattr, str); + } else { + char* buf = buffer; + int maxIndent = 0; + bool lastItem = (this->indent < 0); + int indent = (this->indent < 0 ? -this->indent : this->indent); + int vertLen = strlen(CRT_treeStr[TREE_STR_VERT]); + + for (int i = 0; i < 32; i++) { + if (indent & (1U << i)) { + maxIndent = i+1; + } + } + for (int i = 0; i < maxIndent - 1; i++) { + if (indent & (1 << i)) { + if (buf - buffer + (vertLen + 3) > n) { + break; + } + buf = stpcpy(buf, CRT_treeStr[TREE_STR_VERT]); + buf = stpcpy(buf, " "); + } else { + if (buf - buffer + 4 > n) { + break; + } + buf = stpcpy(buf, " "); + } + } + + n -= (buf - buffer); + const char* draw = CRT_treeStr[lastItem ? TREE_STR_BEND : TREE_STR_RTEE]; + xSnprintf(buf, n, "%s%s ", draw, this->showChildren ? CRT_treeStr[TREE_STR_SHUT] : CRT_treeStr[TREE_STR_OPEN] ); + RichString_appendWide(str, CRT_colors[PROCESS_TREE], buffer); + PCPProcess_writeCommand(this, attr, baseattr, str); + } +} + +static void PCPProcess_writeField(const Process* this, RichString* str, ProcessField field) { + const PCPProcess* pp = (const PCPProcess*) this; + bool coloring = this->settings->highlightMegabytes; + char buffer[256]; buffer[255] = '\0'; + int attr = CRT_colors[DEFAULT_COLOR]; + int n = sizeof(buffer) - 1; + switch ((int)field) { + case TTY_NR: + if (pp->ttyDevice) { + xSnprintf(buffer, n, "%-8s", pp->ttyDevice + 5 /* skip "/dev/" */); + break; + } + Process_writeField(this, str, field); + break; + case CMINFLT: Process_colorNumber(str, pp->cminflt, coloring); return; + case CMAJFLT: Process_colorNumber(str, pp->cmajflt, coloring); return; + case M_DRS: Process_humanNumber(str, pp->m_drs, coloring); return; + case M_DT: Process_humanNumber(str, pp->m_dt, coloring); return; + case M_LRS: Process_humanNumber(str, pp->m_lrs, coloring); return; + case M_TRS: Process_humanNumber(str, pp->m_trs, coloring); return; + case M_SHARE: Process_humanNumber(str, pp->m_share, coloring); return; + case M_PSS: Process_humanNumber(str, pp->m_pss, coloring); return; + case M_SWAP: Process_humanNumber(str, pp->m_swap, coloring); return; + case M_PSSWP: Process_humanNumber(str, pp->m_psswp, coloring); return; + case UTIME: Process_printTime(str, pp->utime); return; + case STIME: Process_printTime(str, pp->stime); return; + case CUTIME: Process_printTime(str, pp->cutime); return; + case CSTIME: Process_printTime(str, pp->cstime); return; + case RCHAR: Process_humanNumber(str, pp->io_rchar, coloring); return; + case WCHAR: Process_humanNumber(str, pp->io_wchar, coloring); return; + case SYSCR: Process_colorNumber(str, pp->io_syscr, coloring); return; + case SYSCW: Process_colorNumber(str, pp->io_syscw, coloring); return; + case RBYTES: Process_humanNumber(str, pp->io_read_bytes, coloring); return; + case WBYTES: Process_humanNumber(str, pp->io_write_bytes, coloring); return; + case CNCLWB: Process_humanNumber(str, pp->io_cancelled_write_bytes, coloring); return; + case IO_READ_RATE: Process_outputRate(str, buffer, n, pp->io_rate_read_bps, coloring); return; + case IO_WRITE_RATE: Process_outputRate(str, buffer, n, pp->io_rate_write_bps, coloring); return; + case IO_RATE: { + double totalRate = NAN; + if (!isnan(pp->io_rate_read_bps) && !isnan(pp->io_rate_write_bps)) + totalRate = pp->io_rate_read_bps + pp->io_rate_write_bps; + else if (!isnan(pp->io_rate_read_bps)) + totalRate = pp->io_rate_read_bps; + else if (!isnan(pp->io_rate_write_bps)) + totalRate = pp->io_rate_write_bps; + else + totalRate = NAN; + Process_outputRate(str, buffer, n, totalRate, coloring); return; + } + case CGROUP: xSnprintf(buffer, n, "%-10s ", pp->cgroup ? pp->cgroup : ""); break; + case OOM: xSnprintf(buffer, n, "%4u ", pp->oom); break; + case PERCENT_CPU_DELAY: + PCPProcess_printDelay(pp->cpu_delay_percent, buffer, n); + break; + case PERCENT_IO_DELAY: + PCPProcess_printDelay(pp->blkio_delay_percent, buffer, n); + break; + case PERCENT_SWAP_DELAY: + PCPProcess_printDelay(pp->swapin_delay_percent, buffer, n); + break; + case CTXT: + if (pp->ctxt_diff > 1000) { + attr |= A_BOLD; + } + xSnprintf(buffer, n, "%5lu ", pp->ctxt_diff); + break; + case SECATTR: snprintf(buffer, n, "%-30s ", pp->secattr ? pp->secattr : "?"); break; + case COMM: { + if ((Process_isUserlandThread(this) && this->settings->showThreadNames) || !pp->mergedCommand.str) { + Process_writeField(this, str, field); + } else { + PCPProcess_writeCommandField(this, str, buffer, n, attr); + } + return; + } + case PROC_COMM: { + const char* procComm; + if (pp->procComm) { + attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_COMM : PROCESS_COMM]; + procComm = pp->procComm; + } else { + attr = CRT_colors[PROCESS_SHADOW]; + procComm = Process_isKernelThread(this) ? kthreadID : "N/A"; + } + /* 15 being (TASK_COMM_LEN - 1) */ + Process_printLeftAlignedField(str, attr, procComm, 15); + return; + } + default: + Process_writeField(this, str, field); + return; + } + RichString_appendWide(str, attr, buffer); +} + +static double adjustNaN(double num) { + if (isnan(num)) + return -0.0005; + + return num; +} + +static int PCPProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) { + const PCPProcess* p1 = (const PCPProcess*)v1; + const PCPProcess* p2 = (const PCPProcess*)v2; + + switch (key) { + case M_DRS: + return SPACESHIP_NUMBER(p1->m_drs, p2->m_drs); + case M_DT: + return SPACESHIP_NUMBER(p1->m_dt, p2->m_dt); + case M_LRS: + return SPACESHIP_NUMBER(p1->m_lrs, p2->m_lrs); + case M_TRS: + return SPACESHIP_NUMBER(p1->m_trs, p2->m_trs); + case M_SHARE: + return SPACESHIP_NUMBER(p1->m_share, p2->m_share); + case M_PSS: + return SPACESHIP_NUMBER(p1->m_pss, p2->m_pss); + case M_SWAP: + return SPACESHIP_NUMBER(p1->m_swap, p2->m_swap); + case M_PSSWP: + return SPACESHIP_NUMBER(p1->m_psswp, p2->m_psswp); + case UTIME: + return SPACESHIP_NUMBER(p1->utime, p2->utime); + case CUTIME: + return SPACESHIP_NUMBER(p1->cutime, p2->cutime); + case STIME: + return SPACESHIP_NUMBER(p1->stime, p2->stime); + case CSTIME: + return SPACESHIP_NUMBER(p1->cstime, p2->cstime); + case RCHAR: + return SPACESHIP_NUMBER(p1->io_rchar, p2->io_rchar); + case WCHAR: + return SPACESHIP_NUMBER(p1->io_wchar, p2->io_wchar); + case SYSCR: + return SPACESHIP_NUMBER(p1->io_syscr, p2->io_syscr); + case SYSCW: + return SPACESHIP_NUMBER(p1->io_syscw, p2->io_syscw); + case RBYTES: + return SPACESHIP_NUMBER(p1->io_read_bytes, p2->io_read_bytes); + case WBYTES: + return SPACESHIP_NUMBER(p1->io_write_bytes, p2->io_write_bytes); + case CNCLWB: + return SPACESHIP_NUMBER(p1->io_cancelled_write_bytes, p2->io_cancelled_write_bytes); + case IO_READ_RATE: + return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_read_bps), adjustNaN(p2->io_rate_read_bps)); + case IO_WRITE_RATE: + return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_write_bps), adjustNaN(p2->io_rate_write_bps)); + case IO_RATE: + return SPACESHIP_NUMBER(adjustNaN(p1->io_rate_read_bps) + adjustNaN(p1->io_rate_write_bps), adjustNaN(p2->io_rate_read_bps) + adjustNaN(p2->io_rate_write_bps)); + case CGROUP: + return SPACESHIP_NULLSTR(p1->cgroup, p2->cgroup); + case OOM: + return SPACESHIP_NUMBER(p1->oom, p1->oom); + case PERCENT_CPU_DELAY: + return SPACESHIP_NUMBER(p1->cpu_delay_percent, p1->cpu_delay_percent); + case PERCENT_IO_DELAY: + return SPACESHIP_NUMBER(p1->blkio_delay_percent, p1->blkio_delay_percent); + case PERCENT_SWAP_DELAY: + return SPACESHIP_NUMBER(p1->swapin_delay_percent, p1->swapin_delay_percent); + case CTXT: + return SPACESHIP_NUMBER(p1->ctxt_diff, p1->ctxt_diff); + case SECATTR: + return SPACESHIP_NULLSTR(p1->secattr, p2->secattr); + case PROC_COMM: { + const char *comm1 = p1->procComm ? p1->procComm : (Process_isKernelThread(v1) ? kthreadID : ""); + const char *comm2 = p2->procComm ? p2->procComm : (Process_isKernelThread(v2) ? kthreadID : ""); + return strcmp(comm1, comm2); + } + default: + return Process_compareByKey_Base(v1, v2, key); + } +} + +bool Process_isThread(const Process* this) { + return (Process_isUserlandThread(this) || Process_isKernelThread(this)); +} + +const ProcessClass PCPProcess_class = { + .super = { + .extends = Class(Process), + .display = Process_display, + .delete = Process_delete, + .compare = Process_compare + }, + .writeField = PCPProcess_writeField, + .getCommandStr = PCPProcess_getCommandStr, + .compareByKey = PCPProcess_compareByKey +}; diff --git a/pcp/PCPProcess.h b/pcp/PCPProcess.h new file mode 100644 index 00000000..9819f5c9 --- /dev/null +++ b/pcp/PCPProcess.h @@ -0,0 +1,140 @@ +#ifndef HEADER_PCPProcess +#define HEADER_PCPProcess +/* +htop - PCPProcess.h +(C) 2014 Hisham H. Muhammad +(C) 2020 htop dev team +(C) 2020-2021 Red Hat, Inc. All Rights Reserved. +Released under the GNU GPLv2, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" + +#include +#include + +#include "Object.h" +#include "Process.h" +#include "RichString.h" +#include "Settings.h" + +#define PROCESS_FLAG_LINUX_CGROUP 0x0800 +#define PROCESS_FLAG_LINUX_OOM 0x1000 +#define PROCESS_FLAG_LINUX_SMAPS 0x2000 +#define PROCESS_FLAG_LINUX_CTXT 0x4000 +#define PROCESS_FLAG_LINUX_SECATTR 0x8000 + +/* PCPProcessMergedCommand is populated by PCPProcess_makeCommandStr: It + * contains the merged Command string, and the information needed by + * PCPProcess_writeCommand to color the string. str will be NULL for kernel + * threads and zombies */ +typedef struct PCPProcessMergedCommand_ { + char *str; /* merged Command string */ + int maxLen; /* maximum expected length of Command string */ + int baseStart; /* basename's start offset */ + int baseEnd; /* basename's end offset */ + int commStart; /* comm's start offset */ + int commEnd; /* comm's end offset */ + int sep1; /* first field separator, used if non-zero */ + int sep2; /* second field separator, used if non-zero */ + bool separateComm; /* whether comm is a separate field */ + bool cmdlineChanged; /* whether cmdline changed */ + bool commChanged; /* whether comm changed */ + bool prevMergeSet; /* whether showMergedCommand was set */ + bool prevPathSet; /* whether showProgramPath was set */ + bool prevCommSet; /* whether findCommInCmdline was set */ + bool prevCmdlineSet; /* whether findCommInCmdline was set */ +} PCPProcessMergedCommand; + +typedef struct PCPProcess_ { + Process super; + char *procComm; + int procCmdlineBasenameOffset; + int procCmdlineBasenameEnd; + PCPProcessMergedCommand mergedCommand; + bool isKernelThread; + unsigned long int cminflt; + unsigned long int cmajflt; + unsigned long long int utime; + unsigned long long int stime; + unsigned long long int cutime; + unsigned long long int cstime; + long m_share; + long m_pss; + long m_swap; + long m_psswp; + long m_trs; + long m_drs; + long m_lrs; + long m_dt; + + /* Data read (in kilobytes) */ + unsigned long long io_rchar; + + /* Data written (in kilobytes) */ + unsigned long long io_wchar; + + /* Number of read(2) syscalls */ + unsigned long long io_syscr; + + /* Number of write(2) syscalls */ + unsigned long long io_syscw; + + /* Storage data read (in kilobytes) */ + unsigned long long io_read_bytes; + + /* Storage data written (in kilobytes) */ + unsigned long long io_write_bytes; + + /* Storage data cancelled (in kilobytes) */ + unsigned long long io_cancelled_write_bytes; + + /* Point in time of last io scan (in seconds elapsed since the Epoch) */ + unsigned long long io_last_scan_time; + + double io_rate_read_bps; + double io_rate_write_bps; + char* cgroup; + unsigned int oom; + char* ttyDevice; + unsigned long long int delay_read_time; + unsigned long long cpu_delay_total; + unsigned long long blkio_delay_total; + unsigned long long swapin_delay_total; + float cpu_delay_percent; + float blkio_delay_percent; + float swapin_delay_percent; + unsigned long ctxt_total; + unsigned long ctxt_diff; + char* secattr; + unsigned long long int last_mlrs_calctime; +} PCPProcess; + +static inline void Process_setKernelThread(Process* this, bool truth) { + ((PCPProcess*)this)->isKernelThread = truth; +} + +static inline bool Process_isKernelThread(const Process* this) { + return ((const PCPProcess*)this)->isKernelThread; +} + +static inline bool Process_isUserlandThread(const Process* this) { + return this->pid != this->tgid; +} + +extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD]; + +extern const ProcessClass PCPProcess_class; + +Process* PCPProcess_new(const Settings* settings); + +void Process_delete(Object* cast); + +/* This function constructs the string that is displayed by + * PCPProcess_writeCommand and also returned by PCPProcess_getCommandStr */ +void PCPProcess_makeCommandStr(Process *this); + +bool Process_isThread(const Process* this); + +#endif diff --git a/pcp/PCPProcessList.c b/pcp/PCPProcessList.c new file mode 100644 index 00000000..0e608b1d --- /dev/null +++ b/pcp/PCPProcessList.c @@ -0,0 +1,779 @@ +/* +htop - PCPProcessList.c +(C) 2014 Hisham H. Muhammad +(C) 2020 htop dev team +(C) 2020-2021 Red Hat, Inc. All Rights Reserved. +Released under the GNU GPLv2, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "PCPProcessList.h" + +#include + +#include "CRT.h" +#include "Macros.h" +#include "Object.h" +#include "PCPProcess.h" +#include "Process.h" +#include "Settings.h" +#include "XUtils.h" + + +static int PCPProcessList_computeCPUcount(void) { + int cpus; + if ((cpus = Platform_getMaxCPU()) <= 0) + cpus = Metric_instanceCount(PCP_PERCPU_SYSTEM); + return cpus > 1 ? cpus : 1; +} + +static void PCPProcessList_updateCPUcount(PCPProcessList* this) { + ProcessList* pl = &(this->super); + int cpus = PCPProcessList_computeCPUcount(); + if (cpus == pl->cpuCount) + return; + + pl->cpuCount = cpus; + free(this->percpu); + free(this->values); + + this->percpu = xCalloc(cpus, sizeof(pmAtomValue *)); + for (int i = 0; i < cpus; i++) + this->percpu[i] = xCalloc(CPU_METRIC_COUNT, sizeof(pmAtomValue)); + this->values = xCalloc(cpus, sizeof(pmAtomValue)); +} + +static char* setUser(UsersTable* this, unsigned int uid, int pid, int offset) { + char* name = Hashtable_get(this->users, uid); + if (name) + return name; + + pmAtomValue value; + if (Metric_instance(PCP_PROC_ID_USER, pid, offset, &value, PM_TYPE_STRING)) { + Hashtable_put(this->users, uid, value.cp); + name = value.cp; + } + return name; +} + +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId) { + PCPProcessList* this = xCalloc(1, sizeof(PCPProcessList)); + ProcessList* super = &(this->super); + + ProcessList_init(super, Class(PCPProcess), usersTable, pidMatchList, userId); + + struct timeval timestamp; + gettimeofday(×tamp, NULL); + this->timestamp = pmtimevalToReal(×tamp); + + this->cpu = xCalloc(CPU_METRIC_COUNT, sizeof(pmAtomValue)); + PCPProcessList_updateCPUcount(this); + + return super; +} + +void ProcessList_delete(ProcessList* pl) { + PCPProcessList* this = (PCPProcessList*) pl; + ProcessList_done(pl); + free(this->values); + for (int i = 0; i < pl->cpuCount; i++) + free(this->percpu[i]); + free(this->percpu); + free(this->cpu); + free(this); +} + +static inline unsigned long long PCPProcessList_adjustTime(unsigned long long t) { + return t / 10; +} + +static void PCPProcessList_updateID(Process* process, int pid, int offset) { + pmAtomValue value; + + if (Metric_instance(PCP_PROC_TGID, pid, offset, &value, PM_TYPE_U32)) + process->tgid = value.ul; + else + process->tgid = 1; + + if (Metric_instance(PCP_PROC_PPID, pid, offset, &value, PM_TYPE_U32)) + process->ppid = value.ul; + else + process->ppid = 1; + + if (Metric_instance(PCP_PROC_STATE, pid, offset, &value, PM_TYPE_STRING)) { + process->state = value.cp[0]; + free(value.cp); + } else { + process->state = 'X'; + } +} + +static void PCPProcessList_updateInfo(Process* process, int pid, int offset, char* command, size_t commLen) { + PCPProcess* pp = (PCPProcess*) process; + pmAtomValue value; + + if (!Metric_instance(PCP_PROC_CMD, pid, offset, &value, PM_TYPE_STRING)) + value.cp = xStrdup(""); + String_safeStrncpy(command, value.cp, commLen); + free(value.cp); + + if (Metric_instance(PCP_PROC_PGRP, pid, offset, &value, PM_TYPE_U32)) + process->pgrp = value.ul; + else + process->pgrp = 0; + + if (Metric_instance(PCP_PROC_SESSION, pid, offset, &value, PM_TYPE_U32)) + process->session = value.ul; + else + process->session = 0; + + if (Metric_instance(PCP_PROC_TTY, pid, offset, &value, PM_TYPE_U32)) + process->tty_nr = value.ul; + else + process->tty_nr = 0; + + if (Metric_instance(PCP_PROC_TTYPGRP, pid, offset, &value, PM_TYPE_U32)) + process->tpgid = value.ul; + else + process->tpgid = 0; + + if (Metric_instance(PCP_PROC_MINFLT, pid, offset, &value, PM_TYPE_U32)) + process->minflt = value.ul; + else + process->minflt = 0; + + if (Metric_instance(PCP_PROC_CMINFLT, pid, offset, &value, PM_TYPE_U32)) + pp->cminflt = value.ul; + else + pp->cminflt = 0; + + if (Metric_instance(PCP_PROC_MAJFLT, pid, offset, &value, PM_TYPE_U32)) + process->majflt = value.ul; + else + process->majflt = 0; + + if (Metric_instance(PCP_PROC_CMAJFLT, pid, offset, &value, PM_TYPE_U32)) + pp->cmajflt = value.ul; + else + pp->cmajflt = 0; + + if (Metric_instance(PCP_PROC_UTIME, pid, offset, &value, PM_TYPE_U64)) + pp->utime = PCPProcessList_adjustTime(value.ull); + else + pp->utime = 0; + + if (Metric_instance(PCP_PROC_STIME, pid, offset, &value, PM_TYPE_U64)) + pp->stime = PCPProcessList_adjustTime(value.ull); + else + pp->stime = 0; + + if (Metric_instance(PCP_PROC_CUTIME, pid, offset, &value, PM_TYPE_U64)) + pp->cutime = PCPProcessList_adjustTime(value.ull); + else + pp->cutime = 0; + + if (Metric_instance(PCP_PROC_CSTIME, pid, offset, &value, PM_TYPE_U64)) + pp->cstime = PCPProcessList_adjustTime(value.ull); + else + pp->cstime = 0; + + if (Metric_instance(PCP_PROC_PRIORITY, pid, offset, &value, PM_TYPE_U32)) + process->priority = value.ul; + else + process->priority = 0; + + if (Metric_instance(PCP_PROC_NICE, pid, offset, &value, PM_TYPE_32)) + process->nice = value.l; + else + process->nice = 0; + + if (Metric_instance(PCP_PROC_THREADS, pid, offset, &value, PM_TYPE_U32)) + process->nlwp = value.ul; + else + process->nlwp = 0; + + if (Metric_instance(PCP_PROC_STARTTIME, pid, offset, &value, PM_TYPE_U64)) + process->starttime_ctime = PCPProcessList_adjustTime(value.ull); + else + process->starttime_ctime = 0; + + if (Metric_instance(PCP_PROC_PROCESSOR, pid, offset, &value, PM_TYPE_U32)) + process->processor = value.ul; + else + process->processor = 0; + + process->time = pp->utime + pp->stime; +} + +static void PCPProcessList_updateIO(PCPProcess* process, int pid, int offset, unsigned long long now) { + pmAtomValue value; + + if (Metric_instance(PCP_PROC_IO_RCHAR, pid, offset, &value, PM_TYPE_U64)) + process->io_rchar = value.ull / ONE_K; + else + process->io_rchar = ULLONG_MAX; + + if (Metric_instance(PCP_PROC_IO_WCHAR, pid, offset, &value, PM_TYPE_U64)) + process->io_wchar = value.ull / ONE_K; + else + process->io_wchar = ULLONG_MAX; + + if (Metric_instance(PCP_PROC_IO_SYSCR, pid, offset, &value, PM_TYPE_U64)) + process->io_syscr = value.ull; + else + process->io_syscr = ULLONG_MAX; + + if (Metric_instance(PCP_PROC_IO_SYSCW, pid, offset, &value, PM_TYPE_U64)) + process->io_syscw = value.ull; + else + process->io_syscw = ULLONG_MAX; + + if (Metric_instance(PCP_PROC_IO_CANCELLED, pid, offset, &value, PM_TYPE_U64)) + process->io_cancelled_write_bytes = value.ull / ONE_K; + else + process->io_cancelled_write_bytes = ULLONG_MAX; + + if (Metric_instance(PCP_PROC_IO_READB, pid, offset, &value, PM_TYPE_U64)) { + unsigned long long last_read = process->io_read_bytes; + process->io_read_bytes = value.ull / ONE_K; + process->io_rate_read_bps = + ONE_K * (process->io_read_bytes - last_read) / + (now - process->io_last_scan_time); + } else { + process->io_read_bytes = ULLONG_MAX; + process->io_rate_read_bps = NAN; + } + + if (Metric_instance(PCP_PROC_IO_WRITEB, pid, offset, &value, PM_TYPE_U64)) { + unsigned long long last_write = process->io_write_bytes; + process->io_write_bytes = value.ull; + process->io_rate_write_bps = + ONE_K * (process->io_write_bytes - last_write) / + (now - process->io_last_scan_time); + } else { + process->io_write_bytes = ULLONG_MAX; + process->io_rate_write_bps = NAN; + } + + process->io_last_scan_time = now; +} + +static void PCPProcessList_updateMemory(PCPProcess* process, int pid, int offset) { + pmAtomValue value; + + if (Metric_instance(PCP_PROC_MEM_SIZE, pid, offset, &value, PM_TYPE_U32)) + process->super.m_virt = value.ul; + else + process->super.m_virt = 0; + + if (Metric_instance(PCP_PROC_MEM_RSS, pid, offset, &value, PM_TYPE_U32)) + process->super.m_resident = value.ul; + else + process->super.m_resident = 0; + + if (Metric_instance(PCP_PROC_MEM_SHARE, pid, offset, &value, PM_TYPE_U32)) + process->m_share = value.ul; + else + process->m_share = 0; + + if (Metric_instance(PCP_PROC_MEM_TEXTRS, pid, offset, &value, PM_TYPE_U32)) + process->m_trs = value.ul; + else + process->m_trs = 0; + + if (Metric_instance(PCP_PROC_MEM_LIBRS, pid, offset, &value, PM_TYPE_U32)) + process->m_lrs = value.ul; + else + process->m_lrs = 0; + + if (Metric_instance(PCP_PROC_MEM_DATRS, pid, offset, &value, PM_TYPE_U32)) + process->m_drs = value.ul; + else + process->m_drs = 0; + + if (Metric_instance(PCP_PROC_MEM_DIRTY, pid, offset, &value, PM_TYPE_U32)) + process->m_dt = value.ul; + else + process->m_dt = 0; +} + +static void PCPProcessList_updateSmaps(PCPProcess* process, pid_t pid, int offset) { + pmAtomValue value; + + if (Metric_instance(PCP_PROC_SMAPS_PSS, pid, offset, &value, PM_TYPE_U64)) + process->m_pss = value.ull; + else + process->m_pss = 0LL; + + if (Metric_instance(PCP_PROC_SMAPS_SWAP, pid, offset, &value, PM_TYPE_U64)) + process->m_swap = value.ull; + else + process->m_swap = 0LL; + + if (Metric_instance(PCP_PROC_SMAPS_SWAPPSS, pid, offset, &value, PM_TYPE_U64)) + process->m_psswp = value.ull; + else + process->m_psswp = 0LL; +} + +static void PCPProcessList_readOomData(PCPProcess* process, int pid, int offset) { + pmAtomValue value; + if (Metric_instance(PCP_PROC_OOMSCORE, pid, offset, &value, PM_TYPE_U32)) + process->oom = value.ul; + else + process->oom = 0; +} + +static void PCPProcessList_readCtxtData(PCPProcess* process, int pid, int offset) { + pmAtomValue value; + unsigned long ctxt = 0; + + if (Metric_instance(PCP_PROC_VCTXSW, pid, offset, &value, PM_TYPE_U32)) + ctxt += value.ul; + if (Metric_instance(PCP_PROC_NVCTXSW, pid, offset, &value, PM_TYPE_U32)) + ctxt += value.ul; + if (ctxt > process->ctxt_total) + process->ctxt_diff = ctxt - process->ctxt_total; + else + process->ctxt_diff = 0; + process->ctxt_total = ctxt; +} + +static char* setString(Metric metric, int pid, int offset, char* string) { + if (string) + free(string); + pmAtomValue value; + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_STRING)) + string = value.cp; + else + string = NULL; + return string; +} + +static void PCPProcessList_updateTTY(PCPProcess* process, int pid, int offset) { + process->ttyDevice = setString(PCP_PROC_TTYNAME, pid, offset, process->ttyDevice); +} + +static void PCPProcessList_readCGroups(PCPProcess* process, int pid, int offset) { + process->cgroup = setString(PCP_PROC_CGROUPS, pid, offset, process->cgroup); +} + +static void PCPProcessList_readSecattrData(PCPProcess* process, int pid, int offset) { + process->secattr = setString(PCP_PROC_LABELS, pid, offset, process->secattr); +} + +static void PCPProcessList_updateUsername(Process* process, int pid, int offset, UsersTable* users) { + unsigned int uid = 0; + pmAtomValue value; + if (Metric_instance(PCP_PROC_ID_UID, pid, offset, &value, PM_TYPE_U32)) + uid = value.ul; + process->st_uid = uid; + process->user = setUser(users, uid, pid, offset); +} + +static void PCPProcessList_updateCmdline(Process* process, int pid, int offset, const char* comm) { + pmAtomValue value; + if (!Metric_instance(PCP_PROC_PSARGS, pid, offset, &value, PM_TYPE_STRING)) { + if (process->state != 'Z') + Process_setKernelThread(process, true); + else + process->basenameOffset = 0; + return; + } + + char *command = value.cp; + int length = strlen(command); + if (command[0] != '(') { + Process_setKernelThread(process, false); + } else { + ++command; + --length; + if (command[length-1] == ')') + command[length-1] = '\0'; + Process_setKernelThread(process, true); + } + + int tokenEnd = 0; + int tokenStart = 0; + int lastChar = 0; + for (int i = 0; i < length; i++) { + /* htop considers the next character after the last / that is before + * basenameOffset, as the start of the basename in cmdline - see + * Process_writeCommand */ + if (command[i] == '/') + tokenStart = i + 1; + lastChar = i; + } + tokenEnd = length; + + PCPProcess *pp = (PCPProcess *)process; + pp->mergedCommand.maxLen = lastChar + 1; /* accommodate cmdline */ + if (!process->comm || !String_eq(command, process->comm)) { + process->basenameOffset = tokenEnd; + free_and_xStrdup(&process->comm, command); + pp->procCmdlineBasenameOffset = tokenStart; + pp->procCmdlineBasenameEnd = tokenEnd; + pp->mergedCommand.cmdlineChanged = true; + } + + /* comm could change, so should be updated */ + if ((length = strlen(comm)) > 0) { + pp->mergedCommand.maxLen += length; + if (!pp->procComm || !String_eq(command, pp->procComm)) { + free_and_xStrdup(&pp->procComm, command); + pp->mergedCommand.commChanged = true; + } + } else if (pp->procComm) { + free(pp->procComm); + pp->procComm = NULL; + pp->mergedCommand.commChanged = true; + } + + free(value.cp); +} + +static bool PCPProcessList_updateProcesses(PCPProcessList* this, double period, struct timeval* tv) { + ProcessList* pl = (ProcessList*) this; + const Settings* settings = pl->settings; + + bool hideKernelThreads = settings->hideKernelThreads; + bool hideUserlandThreads = settings->hideUserlandThreads; + + unsigned long long now = tv->tv_sec * 1000LL + tv->tv_usec / 1000LL; + int pid = -1, offset = -1; + + /* for every process ... */ + while (Metric_iterate(PCP_PROC_PID, &pid, &offset)) { + + bool preExisting; + Process* proc = ProcessList_getProcess(pl, pid, &preExisting, PCPProcess_new); + PCPProcess* pp = (PCPProcess*) proc; + PCPProcessList_updateID(proc, pid, offset); + + /* + * These conditions will not trigger on first occurrence, cause we need to + * add the process to the ProcessList and do all one time scans + * (e.g. parsing the cmdline to detect a kernel thread) + * But it will short-circuit subsequent scans. + */ + if (preExisting && hideKernelThreads && Process_isKernelThread(proc)) { + proc->updated = true; + proc->show = false; + if (proc->state == 'R') + pl->runningTasks++; + pl->kernelThreads++; + pl->totalTasks++; + continue; + } + if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) { + proc->updated = true; + proc->show = false; + if (proc->state == 'R') + pl->runningTasks++; + pl->userlandThreads++; + pl->totalTasks++; + continue; + } + + if (settings->flags & PROCESS_FLAG_IO) + PCPProcessList_updateIO(pp, pid, offset, now); + + PCPProcessList_updateMemory(pp, pid, offset); + + if ((settings->flags & PROCESS_FLAG_LINUX_SMAPS) && + (Process_isKernelThread(proc) == false)) { + if (Metric_enabled(PCP_PROC_SMAPS_PSS)) + PCPProcessList_updateSmaps(pp, pid, offset); + } + + char command[MAX_NAME + 1]; + unsigned int tty_nr = proc->tty_nr; + unsigned long long int lasttimes = pp->utime + pp->stime; + + PCPProcessList_updateInfo(proc, pid, offset, command, sizeof(command)); + proc->starttime_ctime += Platform_getBootTime(); + if (tty_nr != proc->tty_nr) + PCPProcessList_updateTTY(pp, pid, offset); + + float percent_cpu = (pp->utime + pp->stime - lasttimes) / period * 100.0; + proc->percent_cpu = isnan(percent_cpu) ? + 0.0 : CLAMP(percent_cpu, 0.0, pl->cpuCount * 100.0); + proc->percent_mem = proc->m_resident / (double)pl->totalMem * 100.0; + + if (!preExisting) { + PCPProcessList_updateUsername(proc, pid, offset, pl->usersTable); + PCPProcessList_updateCmdline(proc, pid, offset, command); + Process_fillStarttimeBuffer(proc); + ProcessList_add(pl, proc); + } else if (settings->updateProcessNames && proc->state != 'Z') { + PCPProcessList_updateCmdline(proc, pid, offset, command); + } + + /* (Re)Generate the Command string, but only if the process is: + * - not a kernel thread, and + * - not a zombie or it became zombie under htop's watch, and + * - not a user thread or if showThreadNames is not set */ + if (!Process_isKernelThread(proc) && + (proc->state != 'Z' || pp->mergedCommand.str) && + (!Process_isUserlandThread(proc) || !settings->showThreadNames)) { + PCPProcess_makeCommandStr(proc); + } + + if (settings->flags & PROCESS_FLAG_LINUX_CGROUP) + PCPProcessList_readCGroups(pp, pid, offset); + + if (settings->flags & PROCESS_FLAG_LINUX_OOM) + PCPProcessList_readOomData(pp, pid, offset); + + if (settings->flags & PROCESS_FLAG_LINUX_CTXT) + PCPProcessList_readCtxtData(pp, pid, offset); + + if (settings->flags & PROCESS_FLAG_LINUX_SECATTR) + PCPProcessList_readSecattrData(pp, pid, offset); + + if (proc->state == 'Z' && proc->basenameOffset == 0) { + proc->basenameOffset = -1; + free_and_xStrdup(&proc->comm, command); + pp->procCmdlineBasenameOffset = 0; + pp->procCmdlineBasenameEnd = 0; + pp->mergedCommand.commChanged = true; + } else if (Process_isThread(proc)) { + if (settings->showThreadNames || Process_isKernelThread(proc)) { + proc->basenameOffset = -1; + free_and_xStrdup(&proc->comm, command); + pp->procCmdlineBasenameOffset = 0; + pp->procCmdlineBasenameEnd = 0; + pp->mergedCommand.commChanged = true; + } + if (Process_isKernelThread(proc)) { + pl->kernelThreads++; + } else { + pl->userlandThreads++; + } + } + + /* Set at the end when we know if a new entry is a thread */ + proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || + (hideUserlandThreads && Process_isUserlandThread(proc))); +//fprintf(stderr, "Updated PID %d [%s] show=%d user=%d[%d] kern=%d[%d]\n", pid, command, proc->show, Process_isUserlandThread(proc), hideUserlandThreads, Process_isKernelThread(proc), hideKernelThreads); + + pl->totalTasks++; + if (proc->state == 'R') + pl->runningTasks++; + proc->updated = true; + } +//fprintf(stderr, "Total tasks %d, running=%d\n", pl->totalTasks, pl->runningTasks); + return true; +} + +static void PCPProcessList_updateMemoryInfo(ProcessList* super) { + unsigned long long int freeMem = 0; + unsigned long long int swapFreeMem = 0; + unsigned long long int sreclaimableMem = 0; + super->totalMem = super->usedMem = super->cachedMem = 0; + super->usedSwap = super->totalSwap = 0; + + pmAtomValue value; + if (Metric_values(PCP_MEM_TOTAL, &value, 1, PM_TYPE_U64) != NULL) + super->totalMem = value.ull; + if (Metric_values(PCP_MEM_FREE, &value, 1, PM_TYPE_U64) != NULL) + freeMem = value.ull; + if (Metric_values(PCP_MEM_BUFFERS, &value, 1, PM_TYPE_U64) != NULL) + super->buffersMem = value.ull; + if (Metric_values(PCP_MEM_SRECLAIM, &value, 1, PM_TYPE_U64) != NULL) + sreclaimableMem = value.ull; + if (Metric_values(PCP_MEM_CACHED, &value, 1, PM_TYPE_U64) != NULL) { + super->cachedMem = value.ull; + super->cachedMem += sreclaimableMem; + } + unsigned long long int usedDiff; + usedDiff = freeMem + super->cachedMem + super->buffersMem; + super->usedMem = (super->totalMem >= usedDiff) ? + super->totalMem - usedDiff : super->totalMem - freeMem; + if (Metric_values(PCP_MEM_AVAILABLE, &value, 1, PM_TYPE_U64) != NULL) + super->availableMem = MINIMUM(value.ull, super->totalMem); + else + super->availableMem = freeMem; + if (Metric_values(PCP_MEM_SWAPFREE, &value, 1, PM_TYPE_U64) != NULL) + swapFreeMem = value.ull; + if (Metric_values(PCP_MEM_SWAPTOTAL, &value, 1, PM_TYPE_U64) != NULL) + super->totalSwap = value.ull; + if (Metric_values(PCP_MEM_SWAPCACHED, &value, 1, PM_TYPE_U64) != NULL) + super->cachedSwap = value.ull; + super->usedSwap = super->totalSwap - swapFreeMem - super->cachedSwap; +} + +/* make copies of previously sampled values to avoid overwrite */ +static inline void PCPProcessList_backupCPUTime(pmAtomValue* values) { + /* the PERIOD fields (must) mirror the TIME fields */ + for (int metric = CPU_TOTAL_TIME; metric < CPU_TOTAL_PERIOD; metric++) { + values[metric + CPU_TOTAL_PERIOD] = values[metric]; + } +} + +static inline void PCPProcessList_saveCPUTimePeriod(pmAtomValue* values, CPUMetric previous, pmAtomValue* latest) { + pmAtomValue *value; + + /* new value for period */ + value = &values[previous]; + if (latest->ull > value->ull) + value->ull = latest->ull - value->ull; + else + value->ull = 0; + + /* new value for time */ + value = &values[previous - CPU_TOTAL_PERIOD]; + value->ull = latest->ull; +} + +/* using copied sampled values and new values, calculate derivations */ +static void PCPProcessList_deriveCPUTime(pmAtomValue* values) { + + pmAtomValue* usertime = &values[CPU_USER_TIME]; + pmAtomValue* guesttime = &values[CPU_GUEST_TIME]; + usertime->ull -= guesttime->ull; + + pmAtomValue* nicetime = &values[CPU_NICE_TIME]; + pmAtomValue* guestnicetime = &values[CPU_GUESTNICE_TIME]; + nicetime->ull -= guestnicetime->ull; + + pmAtomValue* idletime = &values[CPU_IDLE_TIME]; + pmAtomValue* iowaittime = &values[CPU_IOWAIT_TIME]; + pmAtomValue* idlealltime = &values[CPU_IDLE_ALL_TIME]; + idlealltime->ull = idletime->ull + iowaittime->ull; + + pmAtomValue* systemtime = &values[CPU_SYSTEM_TIME]; + pmAtomValue* irqtime = &values[CPU_IRQ_TIME]; + pmAtomValue* softirqtime = &values[CPU_SOFTIRQ_TIME]; + pmAtomValue* systalltime = &values[CPU_SYSTEM_ALL_TIME]; + systalltime->ull = systemtime->ull + irqtime->ull + softirqtime->ull; + + pmAtomValue* virtalltime = &values[CPU_GUEST_TIME]; + virtalltime->ull = guesttime->ull + guestnicetime->ull; + + pmAtomValue* stealtime = &values[CPU_STEAL_TIME]; + pmAtomValue* totaltime = &values[CPU_TOTAL_TIME]; + totaltime->ull = usertime->ull + nicetime->ull + systalltime->ull + + idlealltime->ull + stealtime->ull + virtalltime->ull; + + PCPProcessList_saveCPUTimePeriod(values, CPU_USER_PERIOD, usertime); + PCPProcessList_saveCPUTimePeriod(values, CPU_NICE_PERIOD, nicetime); + PCPProcessList_saveCPUTimePeriod(values, CPU_SYSTEM_PERIOD, systemtime); + PCPProcessList_saveCPUTimePeriod(values, CPU_SYSTEM_ALL_PERIOD, systalltime); + PCPProcessList_saveCPUTimePeriod(values, CPU_IDLE_ALL_PERIOD, idlealltime); + PCPProcessList_saveCPUTimePeriod(values, CPU_IDLE_PERIOD, idletime); + PCPProcessList_saveCPUTimePeriod(values, CPU_IOWAIT_PERIOD, iowaittime); + PCPProcessList_saveCPUTimePeriod(values, CPU_IRQ_PERIOD, irqtime); + PCPProcessList_saveCPUTimePeriod(values, CPU_SOFTIRQ_PERIOD, softirqtime); + PCPProcessList_saveCPUTimePeriod(values, CPU_STEAL_PERIOD, stealtime); + PCPProcessList_saveCPUTimePeriod(values, CPU_GUEST_PERIOD, virtalltime); + PCPProcessList_saveCPUTimePeriod(values, CPU_TOTAL_PERIOD, totaltime); +} + +static void PCPProcessList_updateAllCPUTime(PCPProcessList* this, Metric metric, CPUMetric cpumetric) +{ + pmAtomValue* value = &this->cpu[cpumetric]; + if (Metric_values(metric, value, 1, PM_TYPE_U64) == NULL) + memset(&value, 0, sizeof(pmAtomValue)); +} + +static void PCPProcessList_updatePerCPUTime(PCPProcessList* this, Metric metric, CPUMetric cpumetric) +{ + int cpus = this->super.cpuCount; + if (Metric_values(metric, this->values, cpus, PM_TYPE_U64) == NULL) + memset(this->values, 0, cpus * sizeof(pmAtomValue)); + for (int i = 0; i < cpus; i++) + this->percpu[i][cpumetric].ull = this->values[i].ull; +} + +static void PCPProcessList_updatePerCPUReal(PCPProcessList* this, Metric metric, CPUMetric cpumetric) +{ + int cpus = this->super.cpuCount; + if (Metric_values(metric, this->values, cpus, PM_TYPE_DOUBLE) == NULL) + memset(this->values, 0, cpus * sizeof(pmAtomValue)); + for (int i = 0; i < cpus; i++) + this->percpu[i][cpumetric].d = this->values[i].d; +} + +static void PCPProcessList_updateHeader(ProcessList* super, const Settings* settings) { + PCPProcessList_updateMemoryInfo(super); + + PCPProcessList* this = (PCPProcessList*) super; + PCPProcessList_updateCPUcount(this); + + PCPProcessList_backupCPUTime(this->cpu); + PCPProcessList_updateAllCPUTime(this, PCP_CPU_USER, CPU_USER_TIME); + PCPProcessList_updateAllCPUTime(this, PCP_CPU_NICE, CPU_NICE_TIME); + PCPProcessList_updateAllCPUTime(this, PCP_CPU_SYSTEM, CPU_SYSTEM_TIME); + PCPProcessList_updateAllCPUTime(this, PCP_CPU_IDLE, CPU_IDLE_TIME); + PCPProcessList_updateAllCPUTime(this, PCP_CPU_IOWAIT, CPU_IOWAIT_TIME); + PCPProcessList_updateAllCPUTime(this, PCP_CPU_IRQ, CPU_IRQ_TIME); + PCPProcessList_updateAllCPUTime(this, PCP_CPU_SOFTIRQ, CPU_SOFTIRQ_TIME); + PCPProcessList_updateAllCPUTime(this, PCP_CPU_STEAL, CPU_STEAL_TIME); + PCPProcessList_updateAllCPUTime(this, PCP_CPU_GUEST, CPU_GUEST_TIME); + PCPProcessList_deriveCPUTime(this->cpu); + + for (int i = 0; i < super->cpuCount; i++) + PCPProcessList_backupCPUTime(this->percpu[i]); + PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_USER, CPU_USER_TIME); + PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_NICE, CPU_NICE_TIME); + PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_SYSTEM, CPU_SYSTEM_TIME); + PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_IDLE, CPU_IDLE_TIME); + PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_IOWAIT, CPU_IOWAIT_TIME); + PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_IRQ, CPU_IRQ_TIME); + PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_SOFTIRQ, CPU_SOFTIRQ_TIME); + PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_STEAL, CPU_STEAL_TIME); + PCPProcessList_updatePerCPUTime(this, PCP_PERCPU_GUEST, CPU_GUEST_TIME); + for (int i = 0; i < super->cpuCount; i++) + PCPProcessList_deriveCPUTime(this->percpu[i]); + + if (settings->showCPUFrequency) + PCPProcessList_updatePerCPUReal(this, PCP_HINV_CPUCLOCK, CPU_FREQUENCY); +} + +void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { + PCPProcessList* this = (PCPProcessList*) super; + const Settings* settings = super->settings; + bool enabled = !pauseProcessUpdate; + + bool flagged = settings->showCPUFrequency; + Metric_enable(PCP_HINV_CPUCLOCK, flagged); + + /* In pause mode do not sample per-process metric values at all */ + for (int metric = PCP_PROC_PID; metric < PCP_METRIC_COUNT; metric++) + Metric_enable(metric, enabled); + + flagged = settings->flags & PROCESS_FLAG_LINUX_CGROUP; + Metric_enable(PCP_PROC_CGROUPS, flagged && enabled); + flagged = settings->flags & PROCESS_FLAG_LINUX_OOM; + Metric_enable(PCP_PROC_OOMSCORE, flagged && enabled); + flagged = settings->flags & PROCESS_FLAG_LINUX_CTXT; + Metric_enable(PCP_PROC_VCTXSW, flagged && enabled); + Metric_enable(PCP_PROC_NVCTXSW, flagged && enabled); + flagged = settings->flags & PROCESS_FLAG_LINUX_SECATTR; + Metric_enable(PCP_PROC_LABELS, flagged && enabled); + + /* Sample smaps metrics on every second pass to improve performance */ + static int smaps_flag; + smaps_flag = !!smaps_flag; + Metric_enable(PCP_PROC_SMAPS_PSS, smaps_flag && enabled); + Metric_enable(PCP_PROC_SMAPS_SWAP, smaps_flag && enabled); + Metric_enable(PCP_PROC_SMAPS_SWAPPSS, smaps_flag && enabled); + + struct timeval timestamp; + Metric_fetch(×tamp); + + double sample = this->timestamp; + this->timestamp = pmtimevalToReal(×tamp); + + PCPProcessList_updateHeader(super, settings); + + /* In pause mode only update global data for meters (CPU, memory, etc) */ + if (pauseProcessUpdate) + return; + + double period = (this->timestamp - sample) * 100; + PCPProcessList_updateProcesses(this, period, ×tamp); +} diff --git a/pcp/PCPProcessList.h b/pcp/PCPProcessList.h new file mode 100644 index 00000000..28f1d9ce --- /dev/null +++ b/pcp/PCPProcessList.h @@ -0,0 +1,68 @@ +#ifndef HEADER_PCPProcessList +#define HEADER_PCPProcessList +/* +htop - PCPProcessList.h +(C) 2014 Hisham H. Muhammad +Released under the GNU GPLv2, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" + +#include +#include + +#include "Hashtable.h" +#include "ProcessList.h" +#include "pcp/Platform.h" +#include "UsersTable.h" + +typedef enum CPUMetric_ { + CPU_TOTAL_TIME, + CPU_USER_TIME, + CPU_SYSTEM_TIME, + CPU_SYSTEM_ALL_TIME, + CPU_IDLE_ALL_TIME, + CPU_IDLE_TIME, + CPU_NICE_TIME, + CPU_IOWAIT_TIME, + CPU_IRQ_TIME, + CPU_SOFTIRQ_TIME, + CPU_STEAL_TIME, + CPU_GUEST_TIME, + CPU_GUESTNICE_TIME, + + CPU_TOTAL_PERIOD, + CPU_USER_PERIOD, + CPU_SYSTEM_PERIOD, + CPU_SYSTEM_ALL_PERIOD, + CPU_IDLE_ALL_PERIOD, + CPU_IDLE_PERIOD, + CPU_NICE_PERIOD, + CPU_IOWAIT_PERIOD, + CPU_IRQ_PERIOD, + CPU_SOFTIRQ_PERIOD, + CPU_STEAL_PERIOD, + CPU_GUEST_PERIOD, + CPU_GUESTNICE_PERIOD, + + CPU_FREQUENCY, + + CPU_METRIC_COUNT +} CPUMetric; + +typedef struct PCPProcessList_ { + ProcessList super; + double timestamp; /* previous sample timestamp */ + pmAtomValue* cpu; /* aggregate values for each metric */ + pmAtomValue** percpu; /* per-processor values for each metric */ + pmAtomValue* values; /* per-processor buffer for just one metric */ +} PCPProcessList; + +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, uid_t userId); + +void ProcessList_delete(ProcessList* pl); + +void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate); + +#endif diff --git a/pcp/Platform.c b/pcp/Platform.c new file mode 100644 index 00000000..4ac7c701 --- /dev/null +++ b/pcp/Platform.c @@ -0,0 +1,687 @@ +/* +htop - linux/Platform.c +(C) 2014 Hisham H. Muhammad +(C) 2020 htop dev team +(C) 2020-2021 Red Hat, Inc. All Rights Reserved. +Released under the GNU GPLv2, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" + +#include "Platform.h" + +#include + +#include "BatteryMeter.h" +#include "ClockMeter.h" +#include "Compat.h" +#include "CPUMeter.h" +#include "DateMeter.h" +#include "DateTimeMeter.h" +#include "DiskIOMeter.h" +#include "HostnameMeter.h" +#include "LoadAverageMeter.h" +#include "Macros.h" +#include "MainPanel.h" +#include "MemoryMeter.h" +#include "Meter.h" +#include "NetworkIOMeter.h" +#include "Object.h" +#include "Panel.h" +#include "PCPProcess.h" +#include "PCPProcessList.h" +#include "ProcessList.h" +#include "ProvideCurses.h" +#include "Settings.h" +#include "SwapMeter.h" +#include "TasksMeter.h" +#include "UptimeMeter.h" +#include "XUtils.h" + +#include "linux/PressureStallMeter.h" +#include "linux/ZramMeter.h" +#include "linux/ZramStats.h" + +typedef struct Platform_ { + int context; /* PMAPI(3) context identifier */ + unsigned int total; /* total number of all metrics */ + const char** names; /* name array indexed by Metric */ + pmID* pmids; /* all known metric identifiers */ + pmID* fetch; /* enabled identifiers for sampling */ + pmDesc* descs; /* metric desc array indexed by Metric */ + pmResult* result; /* sample values result indexed by Metric */ + + long long btime; /* boottime in seconds since the epoch */ + int pidmax; /* maximum platform process identifier */ + int ncpu; /* maximum processor count configured */ +} Platform; + +Platform* pcp; + +ProcessField Platform_defaultFields[] = { PID, USER, PRIORITY, NICE, M_VIRT, M_RESIDENT, (int)M_SHARE, STATE, PERCENT_CPU, PERCENT_MEM, TIME, COMM, 0 }; + +int Platform_numberOfFields = LAST_PROCESSFIELD; + +const SignalItem Platform_signals[] = { + { .name = " 0 Cancel", .number = 0 }, +}; + +const unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals); + +const MeterClass* const Platform_meterTypes[] = { + &CPUMeter_class, + &ClockMeter_class, + &DateMeter_class, + &DateTimeMeter_class, + &LoadAverageMeter_class, + &LoadMeter_class, + &MemoryMeter_class, + &SwapMeter_class, + &TasksMeter_class, + &UptimeMeter_class, + &BatteryMeter_class, + &HostnameMeter_class, + &AllCPUsMeter_class, + &AllCPUs2Meter_class, + &AllCPUs4Meter_class, + &AllCPUs8Meter_class, + &LeftCPUsMeter_class, + &RightCPUsMeter_class, + &LeftCPUs2Meter_class, + &RightCPUs2Meter_class, + &LeftCPUs4Meter_class, + &RightCPUs4Meter_class, + &LeftCPUs8Meter_class, + &RightCPUs8Meter_class, + &BlankMeter_class, + &PressureStallCPUSomeMeter_class, + &PressureStallIOSomeMeter_class, + &PressureStallIOFullMeter_class, + &PressureStallMemorySomeMeter_class, + &PressureStallMemoryFullMeter_class, + &ZramMeter_class, + &DiskIOMeter_class, + &NetworkIOMeter_class, + NULL +}; + +static const char *Platform_metricNames[] = { + [PCP_CONTROL_THREADS] = "proc.control.perclient.threads", + + [PCP_HINV_NCPU] = "hinv.ncpu", + [PCP_HINV_CPUCLOCK] = "hinv.cpu.clock", + [PCP_UNAME_SYSNAME] = "kernel.uname.sysname", + [PCP_UNAME_RELEASE] = "kernel.uname.release", + [PCP_UNAME_MACHINE] = "kernel.uname.machine", + [PCP_LOAD_AVERAGE] = "kernel.all.load", + [PCP_PID_MAX] = "kernel.all.pid_max", + [PCP_UPTIME] = "kernel.all.uptime", + [PCP_BOOTTIME] = "kernel.all.boottime", + [PCP_CPU_USER] = "kernel.all.cpu.user", + [PCP_CPU_NICE] = "kernel.all.cpu.nice", + [PCP_CPU_SYSTEM] = "kernel.all.cpu.sys", + [PCP_CPU_IDLE] = "kernel.all.cpu.idle", + [PCP_CPU_IOWAIT] = "kernel.all.cpu.wait.total", + [PCP_CPU_IRQ] = "kernel.all.cpu.intr", + [PCP_CPU_SOFTIRQ] = "kernel.all.cpu.irq.soft", + [PCP_CPU_STEAL] = "kernel.all.cpu.steal", + [PCP_CPU_GUEST] = "kernel.all.cpu.guest", + [PCP_CPU_GUESTNICE] = "kernel.all.cpu.guest_nice", + [PCP_PERCPU_USER] = "kernel.percpu.cpu.user", + [PCP_PERCPU_NICE] = "kernel.percpu.cpu.nice", + [PCP_PERCPU_SYSTEM] = "kernel.percpu.cpu.sys", + [PCP_PERCPU_IDLE] = "kernel.percpu.cpu.idle", + [PCP_PERCPU_IOWAIT] = "kernel.percpu.cpu.wait.total", + [PCP_PERCPU_IRQ] = "kernel.percpu.cpu.intr", + [PCP_PERCPU_SOFTIRQ] = "kernel.percpu.cpu.irq.soft", + [PCP_PERCPU_STEAL] = "kernel.percpu.cpu.steal", + [PCP_PERCPU_GUEST] = "kernel.percpu.cpu.guest", + [PCP_PERCPU_GUESTNICE] = "kernel.percpu.cpu.guest_nice", + [PCP_MEM_TOTAL] = "mem.physmem", + [PCP_MEM_FREE] = "mem.util.free", + [PCP_MEM_AVAILABLE] = "mem.util.available", + [PCP_MEM_BUFFERS] = "mem.util.bufmem", + [PCP_MEM_CACHED] = "mem.util.cached", + [PCP_MEM_SRECLAIM] = "mem.util.slabReclaimable", + [PCP_MEM_SWAPCACHED] = "mem.util.swapCached", + [PCP_MEM_SWAPTOTAL] = "mem.util.swapTotal", + [PCP_MEM_SWAPFREE] = "mem.util.swapFree", + [PCP_DISK_READB] = "disk.all.read_bytes", + [PCP_DISK_WRITEB] = "disk.all.write_bytes", + [PCP_DISK_ACTIVE] = "disk.all.avactive", + [PCP_NET_RECVB] = "network.all.in.bytes", + [PCP_NET_SENDB] = "network.all.out.bytes", + [PCP_NET_RECVP] = "network.all.in.packets", + [PCP_NET_SENDP] = "network.all.out.packets", + + [PCP_PSI_CPUSOME] = "kernel.all.pressure.cpu.some.avg", + [PCP_PSI_IOSOME] = "kernel.all.pressure.io.some.avg", + [PCP_PSI_IOFULL] = "kernel.all.pressure.io.full.avg", + [PCP_PSI_MEMSOME] = "kernel.all.pressure.memory.some.avg", + [PCP_PSI_MEMFULL] = "kernel.all.pressure.memory.full.avg", + + [PCP_ZRAM_CAPACITY] = "zram.capacity", + [PCP_ZRAM_ORIGINAL] = "zram.mm_stat.data_size.original", + [PCP_ZRAM_COMPRESSED] = "zram.mm_stat.data_size.compressed", + + [PCP_PROC_PID] = "proc.psinfo.pid", + [PCP_PROC_PPID] = "proc.psinfo.ppid", + [PCP_PROC_TGID] = "proc.psinfo.tgid", + [PCP_PROC_PGRP] = "proc.psinfo.pgrp", + [PCP_PROC_SESSION] = "proc.psinfo.session", + [PCP_PROC_STATE] = "proc.psinfo.sname", + [PCP_PROC_TTY] = "proc.psinfo.tty", + [PCP_PROC_TTYPGRP] = "proc.psinfo.tty_pgrp", + [PCP_PROC_MINFLT] = "proc.psinfo.minflt", + [PCP_PROC_MAJFLT] = "proc.psinfo.maj_flt", + [PCP_PROC_CMINFLT] = "proc.psinfo.cmin_flt", + [PCP_PROC_CMAJFLT] = "proc.psinfo.cmaj_flt", + [PCP_PROC_UTIME] = "proc.psinfo.utime", + [PCP_PROC_STIME] = "proc.psinfo.stime", + [PCP_PROC_CUTIME] = "proc.psinfo.cutime", + [PCP_PROC_CSTIME] = "proc.psinfo.cstime", + [PCP_PROC_PRIORITY] = "proc.psinfo.priority", + [PCP_PROC_NICE] = "proc.psinfo.nice", + [PCP_PROC_THREADS] = "proc.psinfo.threads", + [PCP_PROC_STARTTIME] = "proc.psinfo.start_time", + [PCP_PROC_PROCESSOR] = "proc.psinfo.processor", + [PCP_PROC_CMD] = "proc.psinfo.cmd", + [PCP_PROC_PSARGS] = "proc.psinfo.psargs", + [PCP_PROC_CGROUPS] = "proc.psinfo.cgroups", + [PCP_PROC_OOMSCORE] = "proc.psinfo.oom_score", + [PCP_PROC_VCTXSW] = "proc.psinfo.vctxsw", + [PCP_PROC_NVCTXSW] = "proc.psinfo.nvctxsw", + [PCP_PROC_LABELS] = "proc.psinfo.labels", + [PCP_PROC_ENVIRON] = "proc.psinfo.environ", + [PCP_PROC_TTYNAME] = "proc.psinfo.ttyname", + [PCP_PROC_ID_UID] = "proc.id.uid", + [PCP_PROC_ID_USER] = "proc.id.uid_nm", + [PCP_PROC_IO_RCHAR] = "proc.io.rchar", + [PCP_PROC_IO_WCHAR] = "proc.io.wchar", + [PCP_PROC_IO_SYSCR] = "proc.io.syscr", + [PCP_PROC_IO_SYSCW] = "proc.io.syscw", + [PCP_PROC_IO_READB] = "proc.io.read_bytes", + [PCP_PROC_IO_WRITEB] = "proc.io.write_bytes", + [PCP_PROC_IO_CANCELLED] = "proc.io.cancelled_write_bytes", + [PCP_PROC_MEM_SIZE] = "proc.memory.size", + [PCP_PROC_MEM_RSS] = "proc.memory.rss", + [PCP_PROC_MEM_SHARE] = "proc.memory.share", + [PCP_PROC_MEM_TEXTRS] = "proc.memory.textrss", + [PCP_PROC_MEM_LIBRS] = "proc.memory.librss", + [PCP_PROC_MEM_DATRS] = "proc.memory.datrss", + [PCP_PROC_MEM_DIRTY] = "proc.memory.dirty", + [PCP_PROC_SMAPS_PSS] = "proc.smaps.pss", + [PCP_PROC_SMAPS_SWAP] = "proc.smaps.swap", + [PCP_PROC_SMAPS_SWAPPSS] = "proc.smaps.swappss", + + [PCP_METRIC_COUNT] = NULL +}; + +pmAtomValue* Metric_values(Metric metric, pmAtomValue *atom, int count, int type) { + + pmValueSet* vset = pcp->result->vset[metric]; + if (!vset || vset->numval <= 0) + return NULL; + + /* allocate space for atom if needed */ + if (!atom || !count) { + if (!count) + count = vset->numval; + atom = xCalloc(count, sizeof(pmAtomValue)); + } + + /* extract requested number of values as requested type */ + const pmDesc* desc = &pcp->descs[metric]; + for (int i = 0; i < vset->numval; i++) { + if (i == count) + break; + const pmValue *value = &vset->vlist[i]; + int sts = pmExtractValue(vset->valfmt, value, desc->type, &atom[i], type); + if (sts < 0) { + if (pmDebugOptions.appl0) + fprintf(stderr, "Error: cannot extract metric value: %s\n", + pmErrStr(sts)); + memset(&atom[i], 0, sizeof(pmAtomValue)); + } + } + return atom; +} + +int Metric_instanceCount(Metric metric) { + pmValueSet* vset = pcp->result->vset[metric]; + if (vset) + return vset->numval; + return 0; +} + +int Metric_instanceOffset(Metric metric, int inst) { + pmValueSet* vset = pcp->result->vset[metric]; + if (!vset || vset->numval <= 0) + return 0; + + /* search for optimal offset for subsequent inst lookups to begin */ + for (int i = 0; i < vset->numval; i++) { + if (inst == vset->vlist[i].inst) + return i; + } + return 0; +} + +static pmAtomValue *Metric_extract(Metric metric, int inst, int offset, pmValueSet *vset, pmAtomValue *atom, int type) { + + /* extract value (using requested type) of given metric instance */ + const pmDesc* desc = &pcp->descs[metric]; + const pmValue *value = &vset->vlist[offset]; + int sts = pmExtractValue(vset->valfmt, value, desc->type, atom, type); + if (sts < 0) { + if (pmDebugOptions.appl0) + fprintf(stderr, "Error: cannot extract %s instance %d value: %s\n", + pcp->names[metric], inst, pmErrStr(sts)); + memset(atom, 0, sizeof(pmAtomValue)); + } + return atom; +} + +pmAtomValue *Metric_instance(Metric metric, int inst, int offset, pmAtomValue *atom, int type) { + + pmValueSet* vset = pcp->result->vset[metric]; + if (!vset || vset->numval <= 0) + return NULL; + + /* fast-path using heuristic offset based on expected location */ + if (offset >= 0 && offset < vset->numval && inst == vset->vlist[offset].inst) + return Metric_extract(metric, inst, offset, vset, atom, type); + + /* slow-path using a linear search for the requested instance */ + for (int i = 0; i < vset->numval; i++) { + if (inst == vset->vlist[i].inst) + return Metric_extract(metric, inst, i, vset, atom, type); + } + return NULL; +} + +/* + * Iterate over a set of instances (incl PM_IN_NULL) + * returning the next instance identifier and offset. + * + * Start it off by passing offset -1 into the routine. + */ +bool Metric_iterate(Metric metric, int* instp, int* offsetp) { + pmValueSet* vset = pcp->result->vset[metric]; + if (!vset || vset->numval <= 0) + return false; + + int offset = *offsetp; + offset = (offset < 0) ? 0 : offset + 1; + if (offset > vset->numval - 1) + return false; + + *offsetp = offset; + *instp = vset->vlist[offset].inst; + return true; +} + +/* Switch on/off a metric for value fetching (sampling) */ +void Metric_enable(Metric metric, bool enable) { + pcp->fetch[metric] = enable ? pcp->pmids[metric] : PM_ID_NULL; +} + +bool Metric_enabled(Metric metric) { + return pcp->fetch[metric] != PM_ID_NULL; +} + +void Metric_enableThreads(void) { + pmValueSet* vset = xCalloc(1, sizeof(pmValueSet)); + vset->vlist[0].inst = PM_IN_NULL; + vset->vlist[0].value.lval = 1; + vset->valfmt = PM_VAL_INSITU; + vset->numval = 1; + vset->pmid = pcp->pmids[PCP_CONTROL_THREADS]; + + pmResult* result = xCalloc(1, sizeof(pmResult)); + result->vset[0] = vset; + result->numpmid = 1; + + int sts = pmStore(result); + if (sts < 0 && pmDebugOptions.appl0) + fprintf(stderr, "Error: cannot enable threads: %s\n", pmErrStr(sts)); + + pmFreeResult(result); +} + +bool Metric_fetch(struct timeval *timestamp) { + int sts = pmFetch(pcp->total, pcp->fetch, &pcp->result); + if (sts < 0) { + if (pmDebugOptions.appl0) + fprintf(stderr, "Error: cannot fetch metric values): %s\n", + pmErrStr(sts)); + return false; + } + if (timestamp) + *timestamp = pcp->result->timestamp; + return true; +} + +static int Platform_addMetric(Metric id, const char *name) { + unsigned int i = (unsigned int)id; + + if (i >= PCP_METRIC_COUNT && i >= pcp->total) { + /* added via configuration files */ + unsigned int j = pcp->total + 1; + pcp->fetch = xRealloc(pcp->fetch, j * sizeof(pmID)); + pcp->pmids = xRealloc(pcp->pmids, j * sizeof(pmID)); + pcp->names = xRealloc(pcp->names, j * sizeof(char*)); + pcp->descs = xRealloc(pcp->descs, j * sizeof(pmDesc)); + memset(&pcp->descs[i], 0, sizeof(pmDesc)); + } + + pcp->pmids[i] = pcp->fetch[i] = PM_ID_NULL; + pcp->names[i] = name; + return ++pcp->total; +} + +void Platform_init(void) { + int sts = pmNewContext(PM_CONTEXT_HOST, "local:"); + if (sts < 0) + sts = pmNewContext(PM_CONTEXT_LOCAL, NULL); + if (sts < 0) { + fprintf(stderr, "Cannot setup PCP metric source: %s\n", pmErrStr(sts)); + exit(1); + } + pcp = xCalloc(1, sizeof(Platform)); + pcp->context = sts; + pcp->fetch = xCalloc(PCP_METRIC_COUNT, sizeof(pmID)); + pcp->pmids = xCalloc(PCP_METRIC_COUNT, sizeof(pmID)); + pcp->names = xCalloc(PCP_METRIC_COUNT, sizeof(char*)); + pcp->descs = xCalloc(PCP_METRIC_COUNT, sizeof(pmDesc)); + + for (unsigned int i = 0; i < PCP_METRIC_COUNT; i++) + Platform_addMetric(i, Platform_metricNames[i]); + + sts = pmLookupName(pcp->total, pcp->names, pcp->pmids); + if (sts < 0) { + fprintf(stderr, "Error: cannot lookup metric names: %s\n", pmErrStr(sts)); + exit(1); + } + + for (unsigned int i = 0; i < pcp->total; i++) { + pcp->fetch[i] = PM_ID_NULL; /* default is to not sample */ + + /* expect some metrics to be missing - e.g. PMDA not available */ + if (pcp->pmids[i] == PM_ID_NULL) + continue; + + sts = pmLookupDesc(pcp->pmids[i], &pcp->descs[i]); + if (sts < 0) { + if (pmDebugOptions.appl0) + fprintf(stderr, "Error: cannot lookup metric %s(%s): %s\n", + pcp->names[i], pmIDStr(pcp->pmids[i]), pmErrStr(sts)); + pcp->pmids[i] = PM_ID_NULL; + continue; + } + } + + /* set proc.control.perclient.threads to 1 for live contexts */ + Metric_enableThreads(); + + /* extract values needed for setup - e.g. cpu count, pid_max */ + Metric_enable(PCP_PID_MAX, true); + Metric_enable(PCP_BOOTTIME, true); + Metric_enable(PCP_HINV_NCPU, true); + Metric_enable(PCP_PERCPU_SYSTEM, true); + Metric_enable(PCP_UNAME_SYSNAME, true); + Metric_enable(PCP_UNAME_RELEASE, true); + Metric_enable(PCP_UNAME_MACHINE, true); + + Metric_fetch(NULL); + + for (Metric metric = 0; metric < PCP_PROC_PID; metric++) + Metric_enable(metric, true); + Metric_enable(PCP_PID_MAX, false); /* needed one time only */ + Metric_enable(PCP_BOOTTIME, false); + Metric_enable(PCP_UNAME_SYSNAME, false); + Metric_enable(PCP_UNAME_RELEASE, false); + Metric_enable(PCP_UNAME_MACHINE, false); + + /* first sample (fetch) performed above, save constants */ + Platform_getBootTime(); + Platform_getMaxCPU(); + Platform_getMaxPid(); +} + +void Platform_done(void) { + pmDestroyContext(pcp->context); + free(pcp->fetch); + free(pcp->pmids); + free(pcp->names); + free(pcp->descs); + free(pcp); +} + +void Platform_setBindings(Htop_Action* keys) { + /* no platform-specific key bindings */ + (void)keys; +} + +int Platform_getUptime(void) { + pmAtomValue value; + if (Metric_values(PCP_UPTIME, &value, 1, PM_TYPE_32) == NULL) + return 0; + return value.l; +} + +void Platform_getLoadAverage(double* one, double* five, double* fifteen) { + *one = *five = *fifteen = 0.0; + + pmAtomValue values[3] = {0}; + if (Metric_values(PCP_LOAD_AVERAGE, values, 3, PM_TYPE_DOUBLE) != NULL) { + *one = values[0].d; + *five = values[1].d; + *fifteen = values[2].d; + } +} + +int Platform_getMaxCPU(void) { + if (pcp->ncpu) + return pcp->ncpu; + + pmAtomValue value; + if (Metric_values(PCP_HINV_NCPU, &value, 1, PM_TYPE_32) != NULL) + pcp->ncpu = value.l; + else + pcp->ncpu = -1; + return pcp->ncpu; +} + +int Platform_getMaxPid(void) { + if (pcp->pidmax) + return pcp->pidmax; + + pmAtomValue value; + if (Metric_values(PCP_PID_MAX, &value, 1, PM_TYPE_32) == NULL) + return -1; + pcp->pidmax = value.l; + return pcp->pidmax; +} + +long long Platform_getBootTime(void) { + if (pcp->btime) + return pcp->btime; + + pmAtomValue value; + if (Metric_values(PCP_BOOTTIME, &value, 1, PM_TYPE_64) != NULL) + pcp->btime = value.ll; + return pcp->btime; +} + +static double Platform_setOneCPUValues(Meter* this, pmAtomValue* values) { + + unsigned long long value = values[CPU_TOTAL_PERIOD].ull; + double total = (double) (value == 0 ? 1 : value); + double percent; + + double* v = this->values; + v[CPU_METER_NICE] = values[CPU_NICE_PERIOD].ull / total * 100.0; + v[CPU_METER_NORMAL] = values[CPU_USER_PERIOD].ull / total * 100.0; + if (this->pl->settings->detailedCPUTime) { + v[CPU_METER_KERNEL] = values[CPU_SYSTEM_PERIOD].ull / total * 100.0; + v[CPU_METER_IRQ] = values[CPU_IRQ_PERIOD].ull / total * 100.0; + v[CPU_METER_SOFTIRQ] = values[CPU_SOFTIRQ_PERIOD].ull / total * 100.0; + v[CPU_METER_STEAL] = values[CPU_STEAL_PERIOD].ull / total * 100.0; + v[CPU_METER_GUEST] = values[CPU_GUEST_PERIOD].ull / total * 100.0; + v[CPU_METER_IOWAIT] = values[CPU_IOWAIT_PERIOD].ull / total * 100.0; + this->curItems = 8; + if (this->pl->settings->accountGuestInCPUMeter) + percent = v[0] + v[1] + v[2] + v[3] + v[4] + v[5] + v[6]; + else + percent = v[0] + v[1] + v[2] + v[3] + v[4]; + } else { + v[2] = values[CPU_SYSTEM_ALL_PERIOD].ull / total * 100.0; + value = values[CPU_STEAL_PERIOD].ull + values[CPU_GUEST_PERIOD].ull; + v[3] = value / total * 100.0; + this->curItems = 4; + percent = v[0] + v[1] + v[2] + v[3]; + } + percent = CLAMP(percent, 0.0, 100.0); + if (isnan(percent)) + percent = 0.0; + + v[CPU_METER_FREQUENCY] = values[CPU_FREQUENCY].d; + v[CPU_METER_TEMPERATURE] = NAN; + + return percent; +} + +double Platform_setCPUValues(Meter* this, int cpu) { + const PCPProcessList* pl = (const PCPProcessList*) this->pl; + if (cpu <= 0) /* use aggregate values */ + return Platform_setOneCPUValues(this, pl->cpu); + return Platform_setOneCPUValues(this, pl->percpu[cpu - 1]); +} + +void Platform_setMemoryValues(Meter* this) { + const ProcessList* pl = this->pl; + long int usedMem = pl->usedMem; + long int buffersMem = pl->buffersMem; + long int cachedMem = pl->cachedMem; + usedMem -= buffersMem + cachedMem; + this->total = pl->totalMem; + this->values[0] = usedMem; + this->values[1] = buffersMem; + this->values[2] = cachedMem; +} + +void Platform_setSwapValues(Meter* this) { + const ProcessList* pl = this->pl; + this->total = pl->totalSwap; + this->values[0] = pl->usedSwap; + this->values[1] = pl->cachedSwap; +} + +void Platform_setZramValues(Meter* this) { + (void)this; + + int i, count = Metric_instanceCount(PCP_ZRAM_CAPACITY); + pmAtomValue *values = xCalloc(count, sizeof(pmAtomValue)); + ZramStats stats = {0}; + + if (Metric_values(PCP_ZRAM_CAPACITY, values, count, PM_TYPE_U64)) { + for (i = 0; i < count; i++) + stats.totalZram += values[i].ull; + } + if (Metric_values(PCP_ZRAM_ORIGINAL, values, count, PM_TYPE_U64)) { + for (i = 0; i < count; i++) + stats.usedZramOrig += values[i].ull; + } + if (Metric_values(PCP_ZRAM_COMPRESSED, values, count, PM_TYPE_U64)) { + for (i = 0; i < count; i++) + stats.usedZramComp += values[i].ull; + } + + free(values); + + this->total = stats.totalZram; + this->values[0] = stats.usedZramComp; + this->values[1] = stats.usedZramOrig; +} + +char* Platform_getProcessEnv(pid_t pid) { + pmAtomValue value; + if (!Metric_instance(PCP_PROC_ENVIRON, pid, 0, &value, PM_TYPE_STRING)) + return NULL; + return value.cp; +} + +char* Platform_getInodeFilename(pid_t pid, ino_t inode) { + (void)pid; + (void)inode; + return NULL; +} + +FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) { + (void)pid; + return NULL; +} + +void Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred) { + *ten = *sixty = *threehundred = 0; + + Metric metric; + if (String_eq(file, "cpu")) + metric = PCP_PSI_CPUSOME; + else if (String_eq(file, "io")) + metric = some ? PCP_PSI_IOSOME : PCP_PSI_IOFULL; + else if (String_eq(file, "mem")) + metric = some ? PCP_PSI_MEMSOME : PCP_PSI_MEMFULL; + else + return; + + pmAtomValue values[3] = {0}; + if (Metric_values(metric, values, 3, PM_TYPE_DOUBLE) != NULL) { + *ten = values[0].d; + *sixty = values[1].d; + *threehundred = values[2].d; + } +} + +bool Platform_getDiskIO(DiskIOData* data) { + data->totalBytesRead = 0; + data->totalBytesWritten = 0; + data->totalMsTimeSpend = 0; + + pmAtomValue value; + if (Metric_values(PCP_DISK_READB, &value, 1, PM_TYPE_U64) != NULL) + data->totalBytesRead = value.ull; + if (Metric_values(PCP_DISK_WRITEB, &value, 1, PM_TYPE_U64) != NULL) + data->totalBytesWritten = value.ull; + if (Metric_values(PCP_DISK_ACTIVE, &value, 1, PM_TYPE_U64) != NULL) + data->totalMsTimeSpend = value.ull; + return true; +} + +bool Platform_getNetworkIO(unsigned long int* bytesReceived, + unsigned long int* packetsReceived, + unsigned long int* bytesTransmitted, + unsigned long int* packetsTransmitted) { + *bytesReceived = 0; + *packetsReceived = 0; + *bytesTransmitted = 0; + *packetsTransmitted = 0; + + pmAtomValue value; + if (Metric_values(PCP_NET_RECVB, &value, 1, PM_TYPE_U64) != NULL) + *bytesReceived = value.ull; + if (Metric_values(PCP_NET_SENDB, &value, 1, PM_TYPE_U64) != NULL) + *bytesTransmitted = value.ull; + if (Metric_values(PCP_NET_RECVP, &value, 1, PM_TYPE_U64) != NULL) + *packetsReceived = value.ull; + if (Metric_values(PCP_NET_SENDP, &value, 1, PM_TYPE_U64) != NULL) + *packetsTransmitted = value.ull; + return true; +} + +void Platform_getBattery(double* level, ACPresence* isOnAC) { + *level = NAN; + *isOnAC = AC_ERROR; +} diff --git a/pcp/Platform.h b/pcp/Platform.h new file mode 100644 index 00000000..c9e46b80 --- /dev/null +++ b/pcp/Platform.h @@ -0,0 +1,214 @@ +#ifndef HEADER_Platform +#define HEADER_Platform +/* +htop - pcp/Platform.h +(C) 2014 Hisham H. Muhammad +(C) 2020 htop dev team +(C) 2020-2021 Red Hat, Inc. All Rights Reserved. +Released under the GNU GPLv2, see the COPYING file +in the source distribution for its full text. +*/ + +#include +#include +#include + +#undef PACKAGE_URL +#undef PACKAGE_NAME +#undef PACKAGE_STRING +#undef PACKAGE_TARNAME +#undef PACKAGE_VERSION +#undef PACKAGE_BUGREPORT + +#include "Action.h" +#include "BatteryMeter.h" +#include "DiskIOMeter.h" +#include "Meter.h" +#include "Process.h" +#include "ProcessLocksScreen.h" +#include "SignalsPanel.h" + +extern ProcessField Platform_defaultFields[]; + +extern int Platform_numberOfFields; + +extern const SignalItem Platform_signals[]; + +extern const unsigned int Platform_numberOfSignals; + +extern const MeterClass* const Platform_meterTypes[]; + +void Platform_init(void); + +void Platform_done(void); + +void Platform_setBindings(Htop_Action* keys); + +int Platform_getUptime(void); + +void Platform_getLoadAverage(double* one, double* five, double* fifteen); + +long long Platform_getBootTime(void); + +int Platform_getMaxCPU(void); + +int Platform_getMaxPid(void); + +double Platform_setCPUValues(Meter* this, int cpu); + +void Platform_setMemoryValues(Meter* this); + +void Platform_setSwapValues(Meter* this); + +void Platform_setZramValues(Meter* this); + +char* Platform_getProcessEnv(pid_t pid); + +char* Platform_getInodeFilename(pid_t pid, ino_t inode); + +FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid); + +void Platform_getPressureStall(const char *file, bool some, double* ten, double* sixty, double* threehundred); + +bool Platform_getDiskIO(DiskIOData* data); + +bool Platform_getNetworkIO(unsigned long int* bytesReceived, + unsigned long int* packetsReceived, + unsigned long int* bytesTransmitted, + unsigned long int* packetsTransmitted); + +void Platform_getBattery(double *percent, ACPresence *isOnAC); + +typedef enum Metric_ { + PCP_CONTROL_THREADS, /* proc.control.perclient.threads */ + + PCP_HINV_NCPU, /* hinv.ncpu */ + PCP_HINV_CPUCLOCK, /* hinv.cpu.clock */ + PCP_UNAME_SYSNAME, /* kernel.uname.sysname */ + PCP_UNAME_RELEASE, /* kernel.uname.release */ + PCP_UNAME_MACHINE, /* kernel.uname.machine */ + PCP_LOAD_AVERAGE, /* kernel.all.load */ + PCP_PID_MAX, /* kernel.all.pid_max */ + PCP_UPTIME, /* kernel.all.uptime */ + PCP_BOOTTIME, /* kernel.all.boottime */ + PCP_CPU_USER, /* kernel.all.cpu.user */ + PCP_CPU_NICE, /* kernel.all.cpu.nice */ + PCP_CPU_SYSTEM, /* kernel.all.cpu.sys */ + PCP_CPU_IDLE, /* kernel.all.cpu.idle */ + PCP_CPU_IOWAIT, /* kernel.all.cpu.wait.total */ + PCP_CPU_IRQ, /* kernel.all.cpu.intr */ + PCP_CPU_SOFTIRQ, /* kernel.all.cpu.irq.soft */ + PCP_CPU_STEAL, /* kernel.all.cpu.steal */ + PCP_CPU_GUEST, /* kernel.all.cpu.guest */ + PCP_CPU_GUESTNICE, /* kernel.all.cpu.guest_nice */ + PCP_PERCPU_USER, /* kernel.percpu.cpu.user */ + PCP_PERCPU_NICE, /* kernel.percpu.cpu.nice */ + PCP_PERCPU_SYSTEM, /* kernel.percpu.cpu.sys */ + PCP_PERCPU_IDLE, /* kernel.percpu.cpu.idle */ + PCP_PERCPU_IOWAIT, /* kernel.percpu.cpu.wait.total */ + PCP_PERCPU_IRQ, /* kernel.percpu.cpu.intr */ + PCP_PERCPU_SOFTIRQ, /* kernel.percpu.cpu.irq.soft */ + PCP_PERCPU_STEAL, /* kernel.percpu.cpu.steal */ + PCP_PERCPU_GUEST, /* kernel.percpu.cpu.guest */ + PCP_PERCPU_GUESTNICE, /* kernel.percpu.cpu.guest_nice */ + PCP_MEM_TOTAL, /* mem.physmem */ + PCP_MEM_FREE, /* mem.util.free */ + PCP_MEM_BUFFERS, /* mem.util.bufmem */ + PCP_MEM_CACHED, /* mem.util.cached */ + PCP_MEM_AVAILABLE, /* mem.util.available */ + PCP_MEM_SRECLAIM, /* mem.util.slabReclaimable */ + PCP_MEM_SWAPCACHED, /* mem.util.swapCached */ + PCP_MEM_SWAPTOTAL, /* mem.util.swapTotal */ + PCP_MEM_SWAPFREE, /* mem.util.swapFree */ + PCP_DISK_READB, /* disk.all.read_bytes */ + PCP_DISK_WRITEB, /* disk.all.write_bytes */ + PCP_DISK_ACTIVE, /* disk.all.avactive */ + PCP_NET_RECVB, /* network.all.in.bytes */ + PCP_NET_SENDB, /* network.all.out.bytes */ + PCP_NET_RECVP, /* network.all.in.packets */ + PCP_NET_SENDP, /* network.all.out.packets */ + PCP_PSI_CPUSOME, /* kernel.all.pressure.cpu.some.avg */ + PCP_PSI_IOSOME, /* kernel.all.pressure.io.some.avg */ + PCP_PSI_IOFULL, /* kernel.all.pressure.io.full.avg */ + PCP_PSI_MEMSOME, /* kernel.all.pressure.memory.some.avg */ + PCP_PSI_MEMFULL, /* kernel.all.pressure.memory.full.avg */ + PCP_ZRAM_CAPACITY, /* zram.capacity */ + PCP_ZRAM_ORIGINAL, /* zram.mm_stat.data_size.original */ + PCP_ZRAM_COMPRESSED, /* zram.mm_stat.data_size.compressed */ + + PCP_PROC_PID, /* proc.psinfo.pid */ + PCP_PROC_PPID, /* proc.psinfo.ppid */ + PCP_PROC_TGID, /* proc.psinfo.tgid */ + PCP_PROC_PGRP, /* proc.psinfo.pgrp */ + PCP_PROC_SESSION, /* proc.psinfo.session */ + PCP_PROC_STATE, /* proc.psinfo.sname */ + PCP_PROC_TTY, /* proc.psinfo.tty */ + PCP_PROC_TTYPGRP, /* proc.psinfo.tty_pgrp */ + PCP_PROC_MINFLT, /* proc.psinfo.minflt */ + PCP_PROC_MAJFLT, /* proc.psinfo.maj_flt */ + PCP_PROC_CMINFLT, /* proc.psinfo.cmin_flt */ + PCP_PROC_CMAJFLT, /* proc.psinfo.cmaj_flt */ + PCP_PROC_UTIME, /* proc.psinfo.utime */ + PCP_PROC_STIME, /* proc.psinfo.stime */ + PCP_PROC_CUTIME, /* proc.psinfo.cutime */ + PCP_PROC_CSTIME, /* proc.psinfo.cstime */ + PCP_PROC_PRIORITY, /* proc.psinfo.priority */ + PCP_PROC_NICE, /* proc.psinfo.nice */ + PCP_PROC_THREADS, /* proc.psinfo.threads */ + PCP_PROC_STARTTIME, /* proc.psinfo.start_time */ + PCP_PROC_PROCESSOR, /* proc.psinfo.processor */ + PCP_PROC_CMD, /* proc.psinfo.cmd */ + PCP_PROC_PSARGS, /* proc.psinfo.psargs */ + PCP_PROC_CGROUPS, /* proc.psinfo.cgroups */ + PCP_PROC_OOMSCORE, /* proc.psinfo.oom_score */ + PCP_PROC_VCTXSW, /* proc.psinfo.vctxsw */ + PCP_PROC_NVCTXSW, /* proc.psinfo.nvctxsw */ + PCP_PROC_LABELS, /* proc.psinfo.labels */ + PCP_PROC_ENVIRON, /* proc.psinfo.environ */ + PCP_PROC_TTYNAME, /* proc.psinfo.ttyname */ + + PCP_PROC_ID_UID, /* proc.id.uid */ + PCP_PROC_ID_USER, /* proc.id.uid_nm */ + + PCP_PROC_IO_RCHAR, /* proc.io.rchar */ + PCP_PROC_IO_WCHAR, /* proc.io.wchar */ + PCP_PROC_IO_SYSCR, /* proc.io.syscr */ + PCP_PROC_IO_SYSCW, /* proc.io.syscw */ + PCP_PROC_IO_READB, /* proc.io.read_bytes */ + PCP_PROC_IO_WRITEB, /* proc.io.write_bytes */ + PCP_PROC_IO_CANCELLED, /* proc.io.cancelled_write_bytes */ + + PCP_PROC_MEM_SIZE, /* proc.memory.size */ + PCP_PROC_MEM_RSS, /* proc.memory.rss */ + PCP_PROC_MEM_SHARE, /* proc.memory.share */ + PCP_PROC_MEM_TEXTRS, /* proc.memory.textrss */ + PCP_PROC_MEM_LIBRS, /* proc.memory.librss */ + PCP_PROC_MEM_DATRS, /* proc.memory.datrss */ + PCP_PROC_MEM_DIRTY, /* proc.memory.dirty */ + + PCP_PROC_SMAPS_PSS, /* proc.smaps.pss */ + PCP_PROC_SMAPS_SWAP, /* proc.smaps.swap */ + PCP_PROC_SMAPS_SWAPPSS, /* proc.smaps.swappss */ + + PCP_METRIC_COUNT /* total metric count */ +} Metric; + +void Metric_enable(Metric metric, bool enable); + +bool Metric_enabled(Metric metric); + +void Metric_enableThreads(void); + +bool Metric_fetch(struct timeval *timestamp); + +bool Metric_iterate(Metric metric, int* instp, int* offsetp); + +pmAtomValue* Metric_values(Metric metric, pmAtomValue *atom, int count, int type); + +int Metric_instanceCount(Metric metric); + +int Metric_instanceOffset(Metric metric, int inst); + +pmAtomValue *Metric_instance(Metric metric, int inst, int offset, pmAtomValue *atom, int type); + +#endif diff --git a/pcp/ProcessField.h b/pcp/ProcessField.h new file mode 100644 index 00000000..ee03cbaf --- /dev/null +++ b/pcp/ProcessField.h @@ -0,0 +1,50 @@ +#ifndef HEADER_PCPProcessField +#define HEADER_PCPProcessField +/* +htop - pcp/ProcessField.h +(C) 2014 Hisham H. Muhammad +(C) 2021 htop dev team +(C) 2020-2021 Red Hat, Inc. All Rights Reserved. +Released under the GNU GPLv2, see the COPYING file +in the source distribution for its full text. +*/ + + +#define PLATFORM_PROCESS_FIELDS \ + CMINFLT = 11, \ + CMAJFLT = 13, \ + UTIME = 14, \ + STIME = 15, \ + CUTIME = 16, \ + CSTIME = 17, \ + M_SHARE = 41, \ + M_TRS = 42, \ + M_DRS = 43, \ + M_LRS = 44, \ + M_DT = 45, \ + CTID = 100, \ + RCHAR = 103, \ + WCHAR = 104, \ + SYSCR = 105, \ + SYSCW = 106, \ + RBYTES = 107, \ + WBYTES = 108, \ + CNCLWB = 109, \ + IO_READ_RATE = 110, \ + IO_WRITE_RATE = 111, \ + IO_RATE = 112, \ + CGROUP = 113, \ + OOM = 114, \ + PERCENT_CPU_DELAY = 116, \ + PERCENT_IO_DELAY = 117, \ + PERCENT_SWAP_DELAY = 118, \ + M_PSS = 119, \ + M_SWAP = 120, \ + M_PSSWP = 121, \ + CTXT = 122, \ + SECATTR = 123, \ + PROC_COMM = 124, \ + // End of list + + +#endif /* HEADER_PCPProcessField */