diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 19:58:07 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 19:58:07 +0000 |
commit | 10eea2ab1bae2a8ec159d81c0446fd8061b33e2b (patch) | |
tree | e8270dd60ec096bee8157dbadf029e15ed584592 /pcp | |
parent | Initial commit. (diff) | |
download | htop-upstream.tar.xz htop-upstream.zip |
Adding upstream version 3.3.0.upstream/3.3.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'pcp')
49 files changed, 5626 insertions, 0 deletions
diff --git a/pcp/InDomTable.c b/pcp/InDomTable.c new file mode 100644 index 0000000..2f9a500 --- /dev/null +++ b/pcp/InDomTable.c @@ -0,0 +1,99 @@ +/* +htop - InDomTable.c +(C) 2023 htop dev team +(C) 2022-2023 Sohaib Mohammed +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "pcp/InDomTable.h" + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "CRT.h" +#include "DynamicColumn.h" +#include "Hashtable.h" +#include "Macros.h" +#include "Platform.h" +#include "Table.h" +#include "Vector.h" +#include "XUtils.h" + +#include "pcp/Instance.h" +#include "pcp/Metric.h" +#include "pcp/PCPDynamicColumn.h" + + +InDomTable* InDomTable_new(Machine* host, pmInDom indom, int metricKey) { + InDomTable* this = xCalloc(1, sizeof(InDomTable)); + Object_setClass(this, Class(InDomTable)); + this->metricKey = metricKey; + this->id = indom; + + Table* super = &this->super; + Table_init(super, Class(Row), host); + + return this; +} + +void InDomTable_done(InDomTable* this) { + Table_done(&this->super); +} + +static void InDomTable_delete(Object* cast) { + InDomTable* this = (InDomTable*) cast; + InDomTable_done(this); + free(this); +} + +static Instance* InDomTable_getInstance(InDomTable* this, int id, bool* preExisting) { + const Table* super = &this->super; + Instance* inst = (Instance*) Hashtable_get(super->table, id); + *preExisting = inst != NULL; + if (inst) { + assert(Vector_indexOf(super->rows, inst, Row_idEqualCompare) != -1); + assert(Instance_getId(inst) == id); + } else { + inst = Instance_new(super->host, this); + assert(inst->name == NULL); + Instance_setId(inst, id); + } + return inst; +} + +static void InDomTable_goThroughEntries(InDomTable* this) { + Table* super = &this->super; + + /* for every instance ... */ + int instid = -1, offset = -1; + while (Metric_iterate(this->metricKey, &instid, &offset)) { + bool preExisting; + Instance* inst = InDomTable_getInstance(this, instid, &preExisting); + inst->offset = offset >= 0 ? offset : 0; + + Row* row = (Row*) inst; + if (!preExisting) + Table_add(super, row); + row->updated = true; + row->show = true; + } +} + +static void InDomTable_iterateEntries(Table* super) { + InDomTable* this = (InDomTable*) super; + InDomTable_goThroughEntries(this); +} + +const TableClass InDomTable_class = { + .super = { + .extends = Class(Table), + .delete = InDomTable_delete, + }, + .prepare = Table_prepareEntries, + .iterate = InDomTable_iterateEntries, + .cleanup = Table_cleanupEntries, +}; diff --git a/pcp/InDomTable.h b/pcp/InDomTable.h new file mode 100644 index 0000000..f44a39f --- /dev/null +++ b/pcp/InDomTable.h @@ -0,0 +1,34 @@ +#ifndef HEADER_InDomTable +#define HEADER_InDomTable +/* +htop - InDomTable.h +(C) 2023 htop dev team +(C) 2022-2023 Sohaib Mohammed +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include <stdbool.h> +#include <sys/types.h> + +#include "Platform.h" +#include "Table.h" + + +typedef struct InDomTable_ { + Table super; + pmInDom id; /* shared by metrics in the table */ + unsigned int metricKey; /* representative metric using this indom */ +} InDomTable; + +extern const TableClass InDomTable_class; + +InDomTable* InDomTable_new(Machine* host, pmInDom indom, int metricKey); + +void InDomTable_done(InDomTable* this); + +RowField RowField_keyAt(const Settings* settings, int at); + +void InDomTable_scan(Table* super); + +#endif diff --git a/pcp/Instance.c b/pcp/Instance.c new file mode 100644 index 0000000..8ae9051 --- /dev/null +++ b/pcp/Instance.c @@ -0,0 +1,163 @@ +/* +htop - Instance.c +(C) 2022-2023 Sohaib Mohammed +(C) 2022-2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "pcp/Instance.h" + +#include <stdbool.h> +#include <stdlib.h> + +#include "CRT.h" +#include "DynamicColumn.h" +#include "DynamicScreen.h" +#include "Hashtable.h" +#include "Machine.h" +#include "Macros.h" +#include "Metric.h" +#include "Platform.h" +#include "PCPDynamicColumn.h" +#include "PCPDynamicScreen.h" +#include "Row.h" +#include "RichString.h" +#include "XUtils.h" + +#include "pcp/InDomTable.h" +#include "pcp/Metric.h" + + +Instance* Instance_new(const Machine* host, const InDomTable* indom) { + Instance* this = xCalloc(1, sizeof(Instance)); + Object_setClass(this, Class(Instance)); + + Row* super = &this->super; + Row_init(super, host); + + this->indom = indom; + + return this; +} + +void Instance_done(Instance* this) { + if (this->name) + free(this->name); + Row_done(&this->super); +} + +static void Instance_delete(Object* cast) { + Instance* this = (Instance*) cast; + Instance_done(this); + free(this); +} + +static void Instance_writeField(const Row* super, RichString* str, RowField field) { + const Instance* this = (const Instance*) super; + int instid = Instance_getId(this); + + const Settings* settings = super->host->settings; + DynamicColumn* column = Hashtable_get(settings->dynamicColumns, field); + PCPDynamicColumn* cp = (PCPDynamicColumn*) column; + if (!cp) + return; + + pmAtomValue atom; + pmAtomValue* ap = &atom; + const pmDesc* descp = Metric_desc(cp->id); + if (!Metric_instance(cp->id, instid, this->offset, ap, descp->type)) + ap = NULL; + + PCPDynamicColumn_writeAtomValue(cp, str, settings, cp->id, instid, descp, ap); + + if (ap && descp->type == PM_TYPE_STRING) + free(ap->cp); +} + +static const char* Instance_externalName(Row* super) { + Instance* this = (Instance*) super; + + if (!this->name) + /* ignore any failure here - its safe and we try again next time */ + (void)pmNameInDom(InDom_getId(this), Instance_getId(this), &this->name); + return this->name; +} + +static int Instance_compareByKey(const Row* v1, const Row* v2, int key) { + const Instance* i1 = (const Instance*)v1; + const Instance* i2 = (const Instance*)v2; + + if (key < 0) + return 0; + + Hashtable* dc = Platform_dynamicColumns(); + const PCPDynamicColumn* column = Hashtable_get(dc, key); + if (!column) + return -1; + + size_t metric = column->id; + unsigned int type = Metric_type(metric); + + pmAtomValue atom1 = {0}, atom2 = {0}; + if (!Metric_instance(metric, i1->offset, i1->offset, &atom1, type) || + !Metric_instance(metric, i2->offset, i2->offset, &atom2, type)) { + if (type == PM_TYPE_STRING) { + free(atom1.cp); + free(atom2.cp); + } + return -1; + } + + switch (type) { + case PM_TYPE_STRING: { + int cmp = SPACESHIP_NULLSTR(atom2.cp, atom1.cp); + free(atom2.cp); + free(atom1.cp); + return cmp; + } + case PM_TYPE_32: + return SPACESHIP_NUMBER(atom2.l, atom1.l); + case PM_TYPE_U32: + return SPACESHIP_NUMBER(atom2.ul, atom1.ul); + case PM_TYPE_64: + return SPACESHIP_NUMBER(atom2.ll, atom1.ll); + case PM_TYPE_U64: + return SPACESHIP_NUMBER(atom2.ull, atom1.ull); + case PM_TYPE_FLOAT: + return SPACESHIP_NUMBER(atom2.f, atom1.f); + case PM_TYPE_DOUBLE: + return SPACESHIP_NUMBER(atom2.d, atom1.d); + default: + break; + } + + return 0; +} + +static int Instance_compare(const void* v1, const void* v2) { + const Instance* i1 = (const Instance*)v1; + const Instance* i2 = (const Instance*)v2; + const ScreenSettings* ss = i1->super.host->settings->ss; + RowField key = ScreenSettings_getActiveSortKey(ss); + int result = Instance_compareByKey(v1, v2, key); + + // Implement tie-breaker (needed to make tree mode more stable) + if (!result) + return SPACESHIP_NUMBER(Instance_getId(i1), Instance_getId(i2)); + + return (ScreenSettings_getActiveDirection(ss) == 1) ? result : -result; +} + +const RowClass Instance_class = { + .super = { + .extends = Class(Row), + .display = Row_display, + .delete = Instance_delete, + .compare = Instance_compare, + }, + .sortKeyString = Instance_externalName, + .writeField = Instance_writeField, +}; diff --git a/pcp/Instance.h b/pcp/Instance.h new file mode 100644 index 0000000..aefd642 --- /dev/null +++ b/pcp/Instance.h @@ -0,0 +1,37 @@ +#ifndef HEADER_Instance +#define HEADER_Instance +/* +htop - Instance.h +(C) 2022-2023 htop dev team +(C) 2022-2023 Sohaib Mohammed +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "Hashtable.h" +#include "Object.h" +#include "Platform.h" +#include "Row.h" + + +typedef struct Instance_ { + Row super; + + char* name; /* external instance name */ + const struct InDomTable_* indom; /* instance domain */ + + /* default result offset to use for searching metrics with instances */ + unsigned int offset; +} Instance; + +#define InDom_getId(i_) ((i_)->indom->id) +#define Instance_getId(i_) ((i_)->super.id) +#define Instance_setId(i_, id_) ((i_)->super.id = (id_)) + +extern const RowClass Instance_class; + +Instance* Instance_new(const Machine* host, const struct InDomTable_* indom); + +void Instance_done(Instance* this); + +#endif diff --git a/pcp/Metric.c b/pcp/Metric.c new file mode 100644 index 0000000..4a3c858 --- /dev/null +++ b/pcp/Metric.c @@ -0,0 +1,200 @@ +/* +htop - Metric.c +(C) 2020-2021 htop dev team +(C) 2020-2021 Red Hat, Inc. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "pcp/Metric.h" + +#include <ctype.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> + +#include "XUtils.h" + +#include "pcp/Platform.h" + + +extern Platform* pcp; + +const pmDesc* Metric_desc(Metric metric) { + return &pcp->descs[metric]; +} + +int Metric_type(Metric metric) { + return pcp->descs[metric].type; +} + +pmAtomValue* Metric_values(Metric metric, pmAtomValue* atom, int count, int type) { + if (pcp->result == NULL) + return NULL; + + pmValueSet* vset = pcp->result->vset[metric]; + if (!vset || vset->numval <= 0) + return NULL; + + /* 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) { + if (!pcp->result) + return false; + + 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) { + if (pcp->result) { + pmFreeResult(pcp->result); + pcp->result = NULL; + } + int sts, count = 0; + do { + sts = pmFetch(pcp->totalMetrics, pcp->fetch, &pcp->result); + } while (sts == PM_ERR_IPC && ++count < 3); + 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; +} + +void Metric_externalName(Metric metric, int inst, char** externalName) { + const pmDesc* desc = &pcp->descs[metric]; + /* ignore a failure here - its safe to do so */ + (void)pmNameInDom(desc->indom, inst, externalName); +} + +int Metric_lookupText(const char* metric, char** desc) { + pmID pmid; + int sts; + + sts = pmLookupName(1, &metric, &pmid); + if (sts < 0) + return sts; + + if (pmLookupText(pmid, PM_TEXT_ONELINE, desc) >= 0) + (*desc)[0] = toupper((*desc)[0]); /* UI consistency */ + return 0; +} diff --git a/pcp/Metric.h b/pcp/Metric.h new file mode 100644 index 0000000..e72f6e1 --- /dev/null +++ b/pcp/Metric.h @@ -0,0 +1,189 @@ +#ifndef HEADER_Metric +#define HEADER_Metric +/* +htop - Metric.h +(C) 2020-2021 htop dev team +(C) 2020-2021 Red Hat, Inc. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include <ctype.h> +#include <stdbool.h> +#include <pcp/pmapi.h> +#include <sys/time.h> + +/* use htop config.h values for these macros, not pcp values */ +#undef PACKAGE_URL +#undef PACKAGE_NAME +#undef PACKAGE_STRING +#undef PACKAGE_TARNAME +#undef PACKAGE_VERSION +#undef PACKAGE_BUGREPORT + + +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_UNAME_DISTRO, /* kernel.uname.distro */ + 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_SHARED, /* mem.util.shared */ + 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_IRQFULL, /* kernel.all.pressure.irq.full.avg */ + PCP_PSI_MEMSOME, /* kernel.all.pressure.memory.some.avg */ + PCP_PSI_MEMFULL, /* kernel.all.pressure.memory.full.avg */ + PCP_ZFS_ARC_ANON_SIZE, /* zfs.arc.anon_size */ + PCP_ZFS_ARC_BONUS_SIZE, /* zfs.arc.bonus_size */ + PCP_ZFS_ARC_COMPRESSED_SIZE, /* zfs.arc.compressed_size */ + PCP_ZFS_ARC_UNCOMPRESSED_SIZE, /* zfs.arc.uncompressed_size */ + PCP_ZFS_ARC_C_MIN, /* zfs.arc.c_min */ + PCP_ZFS_ARC_C_MAX, /* zfs.arc.c_max */ + PCP_ZFS_ARC_DBUF_SIZE, /* zfs.arc.dbuf_size */ + PCP_ZFS_ARC_DNODE_SIZE, /* zfs.arc.dnode_size */ + PCP_ZFS_ARC_HDR_SIZE, /* zfs.arc.hdr_size */ + PCP_ZFS_ARC_MFU_SIZE, /* zfs.arc.mfu_size */ + PCP_ZFS_ARC_MRU_SIZE, /* zfs.arc.mru_size */ + PCP_ZFS_ARC_SIZE, /* zfs.arc.size */ + PCP_ZRAM_CAPACITY, /* zram.capacity */ + PCP_ZRAM_ORIGINAL, /* zram.mm_stat.data_size.original */ + PCP_ZRAM_COMPRESSED, /* zram.mm_stat.data_size.compressed */ + PCP_MEM_ZSWAP, /* mem.util.zswap */ + PCP_MEM_ZSWAPPED, /* mem.util.zswapped */ + PCP_VFS_FILES_COUNT, /* vfs.files.count */ + PCP_VFS_FILES_MAX, /* vfs.files.max */ + + 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_EXE, /* proc.psinfo.exe */ + PCP_PROC_CWD, /* proc.psinfo.cwd */ + + PCP_PROC_AUTOGROUP_ID, /* proc.autogroup.id */ + PCP_PROC_AUTOGROUP_NICE, /* proc.autogroup.nice */ + + 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); + +const pmDesc* Metric_desc(Metric metric); + +int Metric_type(Metric metric); + +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); + +void Metric_externalName(Metric metric, int inst, char** externalName); + +int Metric_lookupText(const char* metric, char** desc); + +#endif diff --git a/pcp/PCPDynamicColumn.c b/pcp/PCPDynamicColumn.c new file mode 100644 index 0000000..b0bd03e --- /dev/null +++ b/pcp/PCPDynamicColumn.c @@ -0,0 +1,516 @@ +/* +htop - PCPDynamicColumn.c +(C) 2021-2023 Sohaib Mohammed +(C) 2021-2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "pcp/PCPDynamicColumn.h" + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <pwd.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "CRT.h" +#include "Macros.h" +#include "Platform.h" +#include "Process.h" +#include "ProcessTable.h" +#include "RichString.h" +#include "XUtils.h" + +#include "linux/CGroupUtils.h" +#include "pcp/Metric.h" +#include "pcp/PCPProcess.h" + + +static bool PCPDynamicColumn_addMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column) { + if (!column->super.name[0]) + return false; + + size_t bytes = 16 + strlen(column->super.name); + char* metricName = xMalloc(bytes); + xSnprintf(metricName, bytes, "htop.column.%s", column->super.name); + + column->metricName = metricName; + column->id = columns->offset + columns->cursor; + columns->cursor++; + + Platform_addMetric(column->id, metricName); + return true; +} + +static void PCPDynamicColumn_parseMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column, const char* path, unsigned int line, char* value) { + /* pmLookupText */ + if (!column->super.description) + Metric_lookupText(value, &column->super.description); + + /* lookup a dynamic metric with this name, else create */ + if (PCPDynamicColumn_addMetric(columns, column) == false) + return; + + /* derived metrics in all dynamic columns for simplicity */ + char* error; + if (pmRegisterDerivedMetric(column->metricName, value, &error) < 0) { + char* note; + xAsprintf(¬e, + "%s: failed to parse expression in %s at line %u\n%s\n", + pmGetProgname(), path, line, error); + free(error); + errno = EINVAL; + CRT_fatalError(note); + free(note); + } +} + +// Ensure a valid name for use in a PCP metric name and in htoprc +static bool PCPDynamicColumn_validateColumnName(char* key, const char* path, unsigned int line) { + char* p = key; + char* end = strrchr(key, ']'); + + if (end) { + *end = '\0'; + } else { + fprintf(stderr, + "%s: no closing brace on column name at %s line %u\n\"%s\"", + pmGetProgname(), path, line, key); + return false; + } + + while (*p) { + if (p == key) { + if (!isalpha(*p) && *p != '_') + break; + } else { + if (!isalnum(*p) && *p != '_') + break; + } + p++; + } + if (*p != '\0') { /* badness */ + fprintf(stderr, + "%s: invalid column name at %s line %u\n\"%s\"", + pmGetProgname(), path, line, key); + return false; + } + return true; +} + +// Ensure a column name has not been defined previously +static bool PCPDynamicColumn_uniqueName(char* key, PCPDynamicColumns* columns) { + return DynamicColumn_search(columns->table, key, NULL) == NULL; +} + +static PCPDynamicColumn* PCPDynamicColumn_new(PCPDynamicColumns* columns, const char* name) { + PCPDynamicColumn* column = xCalloc(1, sizeof(*column)); + String_safeStrncpy(column->super.name, name, sizeof(column->super.name)); + column->super.enabled = false; + column->percent = false; + column->instances = false; + column->defaultEnabled = true; + + size_t id = columns->count + LAST_PROCESSFIELD; + Hashtable_put(columns->table, id, column); + columns->count++; + + return column; +} + +static void PCPDynamicColumn_parseFile(PCPDynamicColumns* columns, const char* path) { + FILE* file = fopen(path, "r"); + if (!file) + return; + + PCPDynamicColumn* column = NULL; + unsigned int lineno = 0; + bool ok = true; + for (;;) { + char* line = String_readLine(file); + if (!line) + break; + lineno++; + + /* cleanup whitespace, skip comment lines */ + char* trimmed = String_trim(line); + free(line); + if (!trimmed || !trimmed[0] || trimmed[0] == '#') { + free(trimmed); + continue; + } + + size_t n; + char** config = String_split(trimmed, '=', &n); + free(trimmed); + if (config == NULL) + continue; + + char* key = String_trim(config[0]); + char* value = n > 1 ? String_trim(config[1]) : NULL; + if (key[0] == '[') { /* new section heading - i.e. new column */ + ok = PCPDynamicColumn_validateColumnName(key + 1, path, lineno); + if (ok) + ok = PCPDynamicColumn_uniqueName(key + 1, columns); + if (ok) + column = PCPDynamicColumn_new(columns, key + 1); + } else if (value && column && String_eq(key, "caption")) { + free_and_xStrdup(&column->super.caption, value); + } else if (value && column && String_eq(key, "heading")) { + free_and_xStrdup(&column->super.heading, value); + } else if (value && column && String_eq(key, "description")) { + free_and_xStrdup(&column->super.description, value); + } else if (value && column && String_eq(key, "width")) { + column->super.width = strtoul(value, NULL, 10); + } else if (value && column && String_eq(key, "format")) { + free_and_xStrdup(&column->format, value); + } else if (value && column && String_eq(key, "instances")) { + if (String_eq(value, "True") || String_eq(value, "true")) + column->instances = true; + } else if (value && column && (String_eq(key, "default") || String_eq(key, "enabled"))) { + if (String_eq(value, "False") || String_eq(value, "false")) + column->defaultEnabled = false; + } else if (value && column && String_eq(key, "metric")) { + PCPDynamicColumn_parseMetric(columns, column, path, lineno, value); + } + String_freeArray(config); + free(value); + free(key); + } + fclose(file); +} + +static void PCPDynamicColumn_scanDir(PCPDynamicColumns* columns, char* path) { + DIR* dir = opendir(path); + if (!dir) + return; + + struct dirent* dirent; + while ((dirent = readdir(dir)) != NULL) { + if (dirent->d_name[0] == '.') + continue; + + char* file = String_cat(path, dirent->d_name); + PCPDynamicColumn_parseFile(columns, file); + free(file); + } + closedir(dir); +} + +void PCPDynamicColumns_init(PCPDynamicColumns* columns) { + const char* share = pmGetConfig("PCP_SHARE_DIR"); + const char* sysconf = pmGetConfig("PCP_SYSCONF_DIR"); + const char* xdgConfigHome = getenv("XDG_CONFIG_HOME"); + const char* override = getenv("PCP_HTOP_DIR"); + const char* home = getenv("HOME"); + char* path; + + if (!xdgConfigHome && !home) { + const struct passwd* pw = getpwuid(getuid()); + if (pw) + home = pw->pw_dir; + } + + columns->table = Hashtable_new(0, true); + + /* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */ + if (override) { + path = String_cat(override, "/columns/"); + PCPDynamicColumn_scanDir(columns, path); + free(path); + } + + /* next, search in home directory alongside htoprc */ + if (xdgConfigHome) + path = String_cat(xdgConfigHome, "/htop/columns/"); + else if (home) + path = String_cat(home, "/.config/htop/columns/"); + else + path = NULL; + if (path) { + PCPDynamicColumn_scanDir(columns, path); + free(path); + } + + /* next, search in the system columns directory */ + path = String_cat(sysconf, "/htop/columns/"); + PCPDynamicColumn_scanDir(columns, path); + free(path); + + /* next, try the readonly system columns directory */ + path = String_cat(share, "/htop/columns/"); + PCPDynamicColumn_scanDir(columns, path); + free(path); +} + +void PCPDynamicColumn_done(PCPDynamicColumn* this) { + DynamicColumn_done(&this->super); + free(this->metricName); + free(this->format); +} + +static void PCPDynamicColumns_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) { + PCPDynamicColumn* column = (PCPDynamicColumn*) value; + PCPDynamicColumn_done(column); +} + +void PCPDynamicColumns_done(Hashtable* table) { + Hashtable_foreach(table, PCPDynamicColumns_free, NULL); +} + +static void PCPDynamicColumn_setupWidth(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) { + PCPDynamicColumn* column = (PCPDynamicColumn*) value; + + /* calculate column size based on config file and metric units */ + const pmDesc* desc = Metric_desc(column->id); + + if (column->instances || desc->type == PM_TYPE_STRING) { + column->super.width = column->width; + if (column->super.width == 0) + column->super.width = -16; + return; + } + + if (column->format) { + if (strcmp(column->format, "percent") == 0) { + column->super.width = 5; + return; + } + if (strcmp(column->format, "process") == 0) { + column->super.width = Process_pidDigits; + return; + } + } + + if (column->width) { + column->super.width = column->width; + return; + } + + pmUnits units = desc->units; + if (units.dimSpace && units.dimTime) + column->super.width = 11; // Row_printRate + else if (units.dimSpace) + column->super.width = 5; // Row_printBytes + else if (units.dimCount && units.dimTime) + column->super.width = 11; // Row_printCount + else if (units.dimTime) + column->super.width = 8; // Row_printTime + else + column->super.width = 11; // Row_printCount +} + +void PCPDynamicColumns_setupWidths(PCPDynamicColumns* columns) { + Hashtable_foreach(columns->table, PCPDynamicColumn_setupWidth, NULL); +} + +/* normalize output units to bytes and seconds */ +static int PCPDynamicColumn_normalize(const pmDesc* desc, const pmAtomValue* ap, double* value) { + /* form normalized units based on the original metric units */ + pmUnits units = desc->units; + if (units.dimTime) + units.scaleTime = PM_TIME_SEC; + if (units.dimSpace) + units.scaleSpace = PM_SPACE_BYTE; + if (units.dimCount) + units.scaleCount = PM_COUNT_ONE; + + pmAtomValue atom; + int sts, type = desc->type; + if ((sts = pmConvScale(type, ap, &desc->units, &atom, &units)) < 0) + return sts; + + switch (type) { + case PM_TYPE_32: + *value = (double) atom.l; + break; + case PM_TYPE_U32: + *value = (double) atom.ul; + break; + case PM_TYPE_64: + *value = (double) atom.ll; + break; + case PM_TYPE_U64: + *value = (double) atom.ull; + break; + case PM_TYPE_FLOAT: + *value = (double) atom.f; + break; + case PM_TYPE_DOUBLE: + *value = atom.d; + break; + default: + return PM_ERR_CONV; + } + + return 0; +} + +void PCPDynamicColumn_writeAtomValue(PCPDynamicColumn* column, RichString* str, const struct Settings_* settings, int metric, int instance, const pmDesc* desc, const void* atom) { + const pmAtomValue* atomvalue = (const pmAtomValue*) atom; + char buffer[DYNAMIC_MAX_COLUMN_WIDTH + /*space*/ 1 + /*null*/ 1]; + int attr = CRT_colors[DEFAULT_COLOR]; + int width = column->super.width; + int n; + + if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH) + width = DYNAMIC_DEFAULT_COLUMN_WIDTH; + int abswidth = abs(width); + if (abswidth > DYNAMIC_MAX_COLUMN_WIDTH) { + abswidth = DYNAMIC_MAX_COLUMN_WIDTH; + width = -abswidth; + } + + if (atomvalue == NULL) { + n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, "N/A"); + RichString_appendnAscii(str, CRT_colors[PROCESS_SHADOW], buffer, n); + return; + } + + /* deal with instance names and metrics with string values first */ + if (column->instances || desc->type == PM_TYPE_STRING) { + char* value = NULL; + char* dupd1 = NULL; + if (column->instances) { + attr = CRT_colors[DYNAMIC_GRAY]; + Metric_externalName(metric, instance, &dupd1); + value = dupd1; + } else { + attr = CRT_colors[DYNAMIC_GREEN]; + value = atomvalue->cp; + } + if (column->format && value) { + char* dupd2 = NULL; + if (strcmp(column->format, "command") == 0) + attr = CRT_colors[PROCESS_COMM]; + else if (strcmp(column->format, "process") == 0) + attr = CRT_colors[PROCESS_SHADOW]; + else if (strcmp(column->format, "device") == 0 && strncmp(value, "/dev/", 5) == 0) + value += 5; + else if (strcmp(column->format, "cgroup") == 0 && (dupd2 = CGroup_filterName(value))) + value = dupd2; + n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, value); + if (dupd2) + free(dupd2); + } else if (value) { + n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, value); + } else { + n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, "N/A"); + } + if (dupd1) + free(dupd1); + RichString_appendnAscii(str, attr, buffer, n); + return; + } + + /* deal with any numeric value - first, normalize units to bytes/seconds */ + double value; + if (PCPDynamicColumn_normalize(desc, atomvalue, &value) < 0) { + n = xSnprintf(buffer, sizeof(buffer), "%*.*s ", width, abswidth, "no conv"); + RichString_appendnAscii(str, CRT_colors[METER_VALUE_ERROR], buffer, n); + return; + } + + if (column->format) { + if (strcmp(column->format, "percent") == 0) { + n = Row_printPercentage(value, buffer, sizeof(buffer), width, &attr); + RichString_appendnAscii(str, attr, buffer, n); + return; + } + if (strcmp(column->format, "process") == 0) { + n = xSnprintf(buffer, sizeof(buffer), "%*d ", Row_pidDigits, (int)value); + RichString_appendnAscii(str, attr, buffer, n); + return; + } + } + + /* width overrides unit suffix and coloring; too complex for a corner case */ + if (column->width) { + if (value - (unsigned long long)value > 0) /* display floating point */ + n = xSnprintf(buffer, sizeof(buffer), "%*.2f ", width, value); + else /* display as integer */ + n = xSnprintf(buffer, sizeof(buffer), "%*llu ", width, (unsigned long long)value); + RichString_appendnAscii(str, CRT_colors[PROCESS], buffer, n); + return; + } + + bool coloring = settings->highlightMegabytes; + pmUnits units = desc->units; + if (units.dimSpace && units.dimTime) + Row_printRate(str, value, coloring); + else if (units.dimSpace) + Row_printBytes(str, value, coloring); + else if (units.dimCount) + Row_printCount(str, value, coloring); + else if (units.dimTime) + Row_printTime(str, value / 10 /* hundreds of a second */, coloring); + else + Row_printCount(str, value, 0); /* e.g. PID */ +} + +void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str) { + const Settings* settings = proc->super.host->settings; + const PCPProcess* pp = (const PCPProcess*) proc; + const pmDesc* desc = Metric_desc(this->id); + pid_t pid = Process_getPid(proc); + + pmAtomValue atom; + pmAtomValue* ap = &atom; + if (!Metric_instance(this->id, pid, pp->offset, ap, desc->type)) + ap = NULL; + + PCPDynamicColumn_writeAtomValue(this, str, settings, this->id, pid, desc, ap); +} + +int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, ProcessField key) { + const Process* proc = &p1->super; + const Settings* settings = proc->super.host->settings; + const PCPDynamicColumn* column = Hashtable_get(settings->dynamicColumns, key); + + if (!column) + return -1; + + size_t metric = column->id; + unsigned int type = Metric_type(metric); + + pmAtomValue atom1 = {0}, atom2 = {0}; + if (!Metric_instance(metric, Process_getPid(&p1->super), p1->offset, &atom1, type) || + !Metric_instance(metric, Process_getPid(&p2->super), p2->offset, &atom2, type)) { + if (type == PM_TYPE_STRING) { + free(atom1.cp); + free(atom2.cp); + } + return -1; + } + + switch (type) { + case PM_TYPE_STRING: { + int cmp = SPACESHIP_NULLSTR(atom2.cp, atom1.cp); + free(atom2.cp); + free(atom1.cp); + return cmp; + } + case PM_TYPE_32: + return SPACESHIP_NUMBER(atom2.l, atom1.l); + case PM_TYPE_U32: + return SPACESHIP_NUMBER(atom2.ul, atom1.ul); + case PM_TYPE_64: + return SPACESHIP_NUMBER(atom2.ll, atom1.ll); + case PM_TYPE_U64: + return SPACESHIP_NUMBER(atom2.ull, atom1.ull); + case PM_TYPE_FLOAT: + return compareRealNumbers(atom2.f, atom1.f); + case PM_TYPE_DOUBLE: + return compareRealNumbers(atom2.d, atom1.d); + default: + break; + } + + return -1; +} diff --git a/pcp/PCPDynamicColumn.h b/pcp/PCPDynamicColumn.h new file mode 100644 index 0000000..ade782b --- /dev/null +++ b/pcp/PCPDynamicColumn.h @@ -0,0 +1,54 @@ +#ifndef HEADER_PCPDynamicColumn +#define HEADER_PCPDynamicColumn +/* +htop - PCPDynamicColumn.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include <stddef.h> + +#include "DynamicColumn.h" +#include "Hashtable.h" +#include "Process.h" +#include "RichString.h" + +#include "pcp/PCPProcess.h" + + +struct pmDesc; + +typedef struct PCPDynamicColumn_ { + DynamicColumn super; + char* metricName; + char* format; + size_t id; /* identifier for metric array lookups */ + int width; /* optional width from configuration file */ + bool defaultEnabled; /* default enabled in dynamic screen */ + bool percent; + bool instances; /* an instance *names* column, not values */ +} PCPDynamicColumn; + +typedef struct PCPDynamicColumns_ { + Hashtable* table; + size_t count; /* count of dynamic columns discovered by scan */ + size_t offset; /* start offset into the Platform metric array */ + size_t cursor; /* identifier allocator for each new metric used */ +} PCPDynamicColumns; + +void PCPDynamicColumns_init(PCPDynamicColumns* columns); + +void PCPDynamicColumns_done(Hashtable* table); + +void PCPDynamicColumns_setupWidths(PCPDynamicColumns* columns); + +void PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str); + +void PCPDynamicColumn_writeAtomValue(PCPDynamicColumn* column, RichString* str, const struct Settings_* settings, int metric, int instance, const struct pmDesc* desc, const void* atomvalue); + +int PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, ProcessField key); + +void PCPDynamicColumn_done(PCPDynamicColumn* this); + +#endif diff --git a/pcp/PCPDynamicMeter.c b/pcp/PCPDynamicMeter.c new file mode 100644 index 0000000..11df5f0 --- /dev/null +++ b/pcp/PCPDynamicMeter.c @@ -0,0 +1,473 @@ +/* +htop - PCPDynamicMeter.c +(C) 2021 htop dev team +(C) 2021 Red Hat, Inc. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "pcp/PCPDynamicMeter.h" + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <pwd.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <pcp/pmapi.h> + +#include "Macros.h" +#include "Platform.h" +#include "RichString.h" +#include "XUtils.h" + +#include "pcp/Metric.h" + + +static PCPDynamicMetric* PCPDynamicMeter_lookupMetric(PCPDynamicMeters* meters, PCPDynamicMeter* meter, const char* name) { + size_t bytes = 16 + strlen(meter->super.name) + strlen(name); + char* metricName = xMalloc(bytes); + xSnprintf(metricName, bytes, "htop.meter.%s.%s", meter->super.name, name); + + PCPDynamicMetric* metric; + for (size_t i = 0; i < meter->totalMetrics; i++) { + metric = &meter->metrics[i]; + if (String_eq(metric->name, metricName)) { + free(metricName); + return metric; + } + } + + /* not an existing metric in this meter - add it */ + size_t n = meter->totalMetrics + 1; + meter->metrics = xReallocArray(meter->metrics, n, sizeof(PCPDynamicMetric)); + meter->totalMetrics = n; + metric = &meter->metrics[n - 1]; + memset(metric, 0, sizeof(PCPDynamicMetric)); + metric->name = metricName; + metric->label = String_cat(name, ": "); + metric->id = meters->offset + meters->cursor; + meters->cursor++; + + Platform_addMetric(metric->id, metricName); + + return metric; +} + +static void PCPDynamicMeter_parseMetric(PCPDynamicMeters* meters, PCPDynamicMeter* meter, const char* path, unsigned int line, char* key, char* value) { + PCPDynamicMetric* metric; + char* p; + + if ((p = strchr(key, '.')) == NULL) + return; + *p++ = '\0'; /* end the name, p is now the attribute, e.g. 'label' */ + + if (String_eq(p, "metric")) { + /* lookup a dynamic metric with this name, else create */ + metric = PCPDynamicMeter_lookupMetric(meters, meter, key); + + /* use derived metrics in dynamic meters for simplicity */ + char* error; + if (pmRegisterDerivedMetric(metric->name, value, &error) < 0) { + char* note; + xAsprintf(¬e, + "%s: failed to parse expression in %s at line %u\n%s\n%s", + pmGetProgname(), path, line, error, pmGetProgname()); + free(error); + errno = EINVAL; + CRT_fatalError(note); + free(note); + } + } else { + /* this is a property of a dynamic metric - the metric expression */ + /* may not have been observed yet - i.e. we allow for any ordering */ + metric = PCPDynamicMeter_lookupMetric(meters, meter, key); + if (String_eq(p, "color")) { + if (String_eq(value, "gray")) + metric->color = DYNAMIC_GRAY; + else if (String_eq(value, "darkgray")) + metric->color = DYNAMIC_DARKGRAY; + else if (String_eq(value, "red")) + metric->color = DYNAMIC_RED; + else if (String_eq(value, "green")) + metric->color = DYNAMIC_GREEN; + else if (String_eq(value, "blue")) + metric->color = DYNAMIC_BLUE; + else if (String_eq(value, "cyan")) + metric->color = DYNAMIC_CYAN; + else if (String_eq(value, "magenta")) + metric->color = DYNAMIC_MAGENTA; + else if (String_eq(value, "yellow")) + metric->color = DYNAMIC_YELLOW; + else if (String_eq(value, "white")) + metric->color = DYNAMIC_WHITE; + } else if (String_eq(p, "label")) { + char* label = String_cat(value, ": "); + free_and_xStrdup(&metric->label, label); + free(label); + } else if (String_eq(p, "suffix")) { + free_and_xStrdup(&metric->suffix, value); + } + } +} + +// Ensure a valid name for use in a PCP metric name and in htoprc +static bool PCPDynamicMeter_validateMeterName(char* key, const char* path, unsigned int line) { + char* p = key; + char* end = strrchr(key, ']'); + + if (end) { + *end = '\0'; + } else { + fprintf(stderr, + "%s: no closing brace on meter name at %s line %u\n\"%s\"\n", + pmGetProgname(), path, line, key); + return false; + } + + while (*p) { + if (p == key) { + if (!isalpha(*p) && *p != '_') + break; + } else { + if (!isalnum(*p) && *p != '_') + break; + } + p++; + } + if (*p != '\0') { /* badness */ + fprintf(stderr, + "%s: invalid meter name at %s line %u\n\"%s\"\n", + pmGetProgname(), path, line, key); + return false; + } + return true; +} + +// Ensure a meter name has not been defined previously +static bool PCPDynamicMeter_uniqueName(char* key, PCPDynamicMeters* meters) { + return !DynamicMeter_search(meters->table, key, NULL); +} + +static PCPDynamicMeter* PCPDynamicMeter_new(PCPDynamicMeters* meters, const char* name) { + PCPDynamicMeter* meter = xCalloc(1, sizeof(*meter)); + String_safeStrncpy(meter->super.name, name, sizeof(meter->super.name)); + Hashtable_put(meters->table, ++meters->count, meter); + return meter; +} + +static void PCPDynamicMeter_parseFile(PCPDynamicMeters* meters, const char* path) { + FILE* file = fopen(path, "r"); + if (!file) + return; + + PCPDynamicMeter* meter = NULL; + unsigned int lineno = 0; + bool ok = true; + for (;;) { + char* line = String_readLine(file); + if (!line) + break; + lineno++; + + /* cleanup whitespace, skip comment lines */ + char* trimmed = String_trim(line); + free(line); + if (trimmed[0] == '#' || trimmed[0] == '\0') { + free(trimmed); + continue; + } + + size_t n; + char** config = String_split(trimmed, '=', &n); + free(trimmed); + if (config == NULL) + continue; + + char* key = String_trim(config[0]); + char* value = n > 1 ? String_trim(config[1]) : NULL; + if (key[0] == '[') { /* new section heading - i.e. new meter */ + ok = PCPDynamicMeter_validateMeterName(key + 1, path, lineno); + if (ok) + ok = PCPDynamicMeter_uniqueName(key + 1, meters); + if (ok) + meter = PCPDynamicMeter_new(meters, key + 1); + } else if (!ok) { + ; /* skip this one, we're looking for a new header */ + } else if (value && meter && String_eq(key, "caption")) { + char* caption = String_cat(value, ": "); + if (caption) { + free_and_xStrdup(&meter->super.caption, caption); + free(caption); + caption = NULL; + } + } else if (value && meter && String_eq(key, "description")) { + free_and_xStrdup(&meter->super.description, value); + } else if (value && meter && String_eq(key, "type")) { + if (String_eq(config[1], "bar")) + meter->super.type = BAR_METERMODE; + else if (String_eq(config[1], "text")) + meter->super.type = TEXT_METERMODE; + else if (String_eq(config[1], "graph")) + meter->super.type = GRAPH_METERMODE; + else if (String_eq(config[1], "led")) + meter->super.type = LED_METERMODE; + } else if (value && meter && String_eq(key, "maximum")) { + meter->super.maximum = strtod(value, NULL); + } else if (value && meter) { + PCPDynamicMeter_parseMetric(meters, meter, path, lineno, key, value); + } + String_freeArray(config); + free(value); + free(key); + } + fclose(file); +} + +static void PCPDynamicMeter_scanDir(PCPDynamicMeters* meters, char* path) { + DIR* dir = opendir(path); + if (!dir) + return; + + struct dirent* dirent; + while ((dirent = readdir(dir)) != NULL) { + if (dirent->d_name[0] == '.') + continue; + + char* file = String_cat(path, dirent->d_name); + PCPDynamicMeter_parseFile(meters, file); + free(file); + } + closedir(dir); +} + +void PCPDynamicMeters_init(PCPDynamicMeters* meters) { + const char* share = pmGetConfig("PCP_SHARE_DIR"); + const char* sysconf = pmGetConfig("PCP_SYSCONF_DIR"); + const char* xdgConfigHome = getenv("XDG_CONFIG_HOME"); + const char* override = getenv("PCP_HTOP_DIR"); + const char* home = getenv("HOME"); + char* path; + + if (!xdgConfigHome && !home) { + const struct passwd* pw = getpwuid(getuid()); + if (pw) + home = pw->pw_dir; + } + + meters->table = Hashtable_new(0, true); + + /* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */ + if (override) { + path = String_cat(override, "/meters/"); + PCPDynamicMeter_scanDir(meters, path); + free(path); + } + + /* next, search in home directory alongside htoprc */ + if (xdgConfigHome) + path = String_cat(xdgConfigHome, "/htop/meters/"); + else if (home) + path = String_cat(home, "/.config/htop/meters/"); + else + path = NULL; + if (path) { + PCPDynamicMeter_scanDir(meters, path); + free(path); + } + + /* next, search in the system meters directory */ + path = String_cat(sysconf, "/htop/meters/"); + PCPDynamicMeter_scanDir(meters, path); + free(path); + + /* next, try the readonly system meters directory */ + path = String_cat(share, "/htop/meters/"); + PCPDynamicMeter_scanDir(meters, path); + free(path); +} + +static void PCPDynamicMeter_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) { + PCPDynamicMeter* meter = (PCPDynamicMeter*) value; + for (size_t i = 0; i < meter->totalMetrics; i++) { + free(meter->metrics[i].name); + free(meter->metrics[i].label); + free(meter->metrics[i].suffix); + } + free(meter->metrics); + free(meter->super.caption); + free(meter->super.description); +} + +void PCPDynamicMeters_done(Hashtable* table) { + Hashtable_foreach(table, PCPDynamicMeter_free, NULL); +} + +void PCPDynamicMeter_enable(PCPDynamicMeter* this) { + for (size_t i = 0; i < this->totalMetrics; i++) + Metric_enable(this->metrics[i].id, true); +} + +void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter) { + char* buffer = meter->txtBuffer; + size_t size = sizeof(meter->txtBuffer); + size_t bytes = 0; + + for (size_t i = 0; i < this->totalMetrics; i++) { + if (i > 0 && bytes < size - 1) + buffer[bytes++] = '/'; /* separator */ + + PCPDynamicMetric* metric = &this->metrics[i]; + const pmDesc* desc = Metric_desc(metric->id); + pmAtomValue atom, raw; + + if (!Metric_values(metric->id, &raw, 1, desc->type)) { + bytes--; /* clear the separator */ + continue; + } + + pmUnits conv = desc->units; /* convert to canonical units */ + if (conv.dimSpace) + conv.scaleSpace = PM_SPACE_KBYTE; + if (conv.dimTime) + conv.scaleTime = PM_TIME_SEC; + if (desc->type == PM_TYPE_STRING) + atom = raw; + else if (pmConvScale(desc->type, &raw, &desc->units, &atom, &conv) < 0) { + bytes--; /* clear the separator */ + continue; + } + + size_t saved = bytes; + switch (desc->type) { + case PM_TYPE_STRING: + bytes += xSnprintf(buffer + bytes, size - bytes, "%s", atom.cp); + free(atom.cp); + break; + case PM_TYPE_32: + bytes += conv.dimSpace ? + Meter_humanUnit(buffer + bytes, (double) atom.l, size - bytes) : + xSnprintf(buffer + bytes, size - bytes, "%d", atom.l); + break; + case PM_TYPE_U32: + bytes += conv.dimSpace ? + Meter_humanUnit(buffer + bytes, (double) atom.ul, size - bytes) : + xSnprintf(buffer + bytes, size - bytes, "%u", atom.ul); + break; + case PM_TYPE_64: + bytes += conv.dimSpace ? + Meter_humanUnit(buffer + bytes, (double) atom.ll, size - bytes) : + xSnprintf(buffer + bytes, size - bytes, "%lld", (long long) atom.ll); + break; + case PM_TYPE_U64: + bytes += conv.dimSpace ? + Meter_humanUnit(buffer + bytes, (double) atom.ull, size - bytes) : + xSnprintf(buffer + bytes, size - bytes, "%llu", (unsigned long long) atom.ull); + break; + case PM_TYPE_FLOAT: + bytes += conv.dimSpace ? + Meter_humanUnit(buffer + bytes, (double) atom.f, size - bytes) : + xSnprintf(buffer + bytes, size - bytes, "%.2f", (double) atom.f); + break; + case PM_TYPE_DOUBLE: + bytes += conv.dimSpace ? + Meter_humanUnit(buffer + bytes, atom.d, size - bytes) : + xSnprintf(buffer + bytes, size - bytes, "%.2f", atom.d); + break; + default: + break; + } + + if (saved != bytes && metric->suffix) + bytes += xSnprintf(buffer + bytes, size - bytes, "%s", metric->suffix); + } + + if (!bytes) + xSnprintf(buffer, size, "no data"); +} + +void PCPDynamicMeter_display(PCPDynamicMeter* this, ATTR_UNUSED const Meter* meter, RichString* out) { + int nodata = 1; + + for (size_t i = 0; i < this->totalMetrics; i++) { + PCPDynamicMetric* metric = &this->metrics[i]; + const pmDesc* desc = Metric_desc(metric->id); + pmAtomValue atom, raw; + char buffer[64]; + + if (!Metric_values(metric->id, &raw, 1, desc->type)) + continue; + + pmUnits conv = desc->units; /* convert to canonical units */ + if (conv.dimSpace) + conv.scaleSpace = PM_SPACE_KBYTE; + if (conv.dimTime) + conv.scaleTime = PM_TIME_SEC; + if (desc->type == PM_TYPE_STRING) + atom = raw; + else if (pmConvScale(desc->type, &raw, &desc->units, &atom, &conv) < 0) + continue; + + nodata = 0; /* we will use this metric so *some* data will be added */ + + if (i > 0) + RichString_appendnAscii(out, CRT_colors[metric->color], " ", 1); + + if (metric->label) + RichString_appendAscii(out, CRT_colors[METER_TEXT], metric->label); + + int len = 0; + switch (desc->type) { + case PM_TYPE_STRING: + len = xSnprintf(buffer, sizeof(buffer), "%s", atom.cp); + free(atom.cp); + break; + case PM_TYPE_32: + len = conv.dimSpace ? + Meter_humanUnit(buffer, (double) atom.l, sizeof(buffer)) : + xSnprintf(buffer, sizeof(buffer), "%d", atom.l); + break; + case PM_TYPE_U32: + len = conv.dimSpace ? + Meter_humanUnit(buffer, (double) atom.ul, sizeof(buffer)) : + xSnprintf(buffer, sizeof(buffer), "%u", atom.ul); + break; + case PM_TYPE_64: + len = conv.dimSpace ? + Meter_humanUnit(buffer, (double) atom.ll, sizeof(buffer)) : + xSnprintf(buffer, sizeof(buffer), "%lld", (long long) atom.ll); + break; + case PM_TYPE_U64: + len = conv.dimSpace ? + Meter_humanUnit(buffer, (double) atom.ull, sizeof(buffer)) : + xSnprintf(buffer, sizeof(buffer), "%llu", (unsigned long long) atom.ull); + break; + case PM_TYPE_FLOAT: + len = conv.dimSpace ? + Meter_humanUnit(buffer, (double) atom.f, sizeof(buffer)) : + xSnprintf(buffer, sizeof(buffer), "%.2f", (double) atom.f); + break; + case PM_TYPE_DOUBLE: + len = conv.dimSpace ? + Meter_humanUnit(buffer, atom.d, sizeof(buffer)) : + xSnprintf(buffer, sizeof(buffer), "%.2f", atom.d); + break; + default: + break; + } + + if (len) { + RichString_appendnAscii(out, CRT_colors[metric->color], buffer, len); + if (metric->suffix) + RichString_appendAscii(out, CRT_colors[METER_TEXT], metric->suffix); + } + } + + if (nodata) + RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data"); +} diff --git a/pcp/PCPDynamicMeter.h b/pcp/PCPDynamicMeter.h new file mode 100644 index 0000000..3a72d13 --- /dev/null +++ b/pcp/PCPDynamicMeter.h @@ -0,0 +1,50 @@ +#ifndef HEADER_PCPDynamicMeter +#define HEADER_PCPDynamicMeter +/* +htop - PCPDynamicMeter.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include <stddef.h> + +#include "CRT.h" +#include "DynamicMeter.h" +#include "Hashtable.h" +#include "Meter.h" +#include "RichString.h" + + +typedef struct PCPDynamicMetric_ { + size_t id; /* index into metric array */ + ColorElements color; + char* name; /* derived metric name */ + char* label; + char* suffix; +} PCPDynamicMetric; + +typedef struct PCPDynamicMeter_ { + DynamicMeter super; + PCPDynamicMetric* metrics; + size_t totalMetrics; +} PCPDynamicMeter; + +typedef struct PCPDynamicMeters_ { + Hashtable* table; + size_t count; /* count of dynamic meters discovered by scan */ + size_t offset; /* start offset into the Platform metric array */ + size_t cursor; /* identifier allocator for each new metric used */ +} PCPDynamicMeters; + +void PCPDynamicMeters_init(PCPDynamicMeters* meters); + +void PCPDynamicMeters_done(Hashtable* table); + +void PCPDynamicMeter_enable(PCPDynamicMeter* this); + +void PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter); + +void PCPDynamicMeter_display(PCPDynamicMeter* this, const Meter* meter, RichString* out); + +#endif diff --git a/pcp/PCPDynamicScreen.c b/pcp/PCPDynamicScreen.c new file mode 100644 index 0000000..2222822 --- /dev/null +++ b/pcp/PCPDynamicScreen.c @@ -0,0 +1,407 @@ +/* +htop - PCPDynamicScreen.c +(C) 2022 Sohaib Mohammed +(C) 2022-2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "pcp/PCPDynamicScreen.h" + +#include <ctype.h> +#include <dirent.h> +#include <stdbool.h> +#include <pcp/pmapi.h> + +#include "AvailableColumnsPanel.h" +#include "Macros.h" +#include "Platform.h" +#include "Settings.h" +#include "XUtils.h" + +#include "pcp/InDomTable.h" +#include "pcp/PCPDynamicColumn.h" + + +static char* formatFields(PCPDynamicScreen* screen) { + char* columns = strdup(""); + + for (size_t j = 0; j < screen->totalColumns; j++) { + const PCPDynamicColumn* column = screen->columns[j]; + if (column->super.enabled == false) + continue; + char* prefix = columns; + xAsprintf(&columns, "%s Dynamic(%s)", prefix, column->super.name); + free(prefix); + } + + return columns; +} + +static void PCPDynamicScreens_appendDynamicColumns(PCPDynamicScreens* screens, PCPDynamicColumns* columns) { + for (size_t i = 0; i < screens->count; i++) { + PCPDynamicScreen* screen = Hashtable_get(screens->table, i); + if (!screen) + return; + + /* setup default fields (columns) based on configuration */ + for (size_t j = 0; j < screen->totalColumns; j++) { + PCPDynamicColumn* column = screen->columns[j]; + + column->id = columns->offset + columns->cursor; + columns->cursor++; + Platform_addMetric(column->id, column->metricName); + + size_t id = columns->count + LAST_PROCESSFIELD; + Hashtable_put(columns->table, id, column); + columns->count++; + + if (j == 0) { + const pmDesc* desc = Metric_desc(column->id); + assert(desc->indom != PM_INDOM_NULL); + screen->indom = desc->indom; + screen->key = column->id; + } + } + screen->super.columnKeys = formatFields(screen); + } +} + +static PCPDynamicColumn* PCPDynamicScreen_lookupMetric(PCPDynamicScreen* screen, const char* name) { + PCPDynamicColumn* column = NULL; + size_t bytes = strlen(name) + strlen(screen->super.name) + 1; /* colon */ + if (bytes >= sizeof(column->super.name)) + return NULL; + + bytes += 16; /* prefix, dots and terminator */ + char* metricName = xMalloc(bytes); + xSnprintf(metricName, bytes, "htop.screen.%s.%s", screen->super.name, name); + + for (size_t i = 0; i < screen->totalColumns; i++) { + column = screen->columns[i]; + if (String_eq(column->metricName, metricName)) { + free(metricName); + return column; + } + } + + /* not an existing column in this screen - create it and add to the list */ + column = xCalloc(1, sizeof(PCPDynamicColumn)); + xSnprintf(column->super.name, sizeof(column->super.name), "%s:%s", screen->super.name, name); + column->super.table = &screen->table->super; + column->metricName = metricName; + column->super.enabled = true; + + size_t n = screen->totalColumns + 1; + screen->columns = xReallocArray(screen->columns, n, sizeof(PCPDynamicColumn*)); + screen->columns[n - 1] = column; + screen->totalColumns = n; + + return column; +} + +static void PCPDynamicScreen_parseColumn(PCPDynamicScreen* screen, const char* path, unsigned int line, char* key, char* value) { + PCPDynamicColumn* column; + char* p; + + if ((p = strchr(key, '.')) == NULL) + return; + *p++ = '\0'; /* end the name, p is now the attribute, e.g. 'label' */ + + /* lookup a dynamic column with this name, else create */ + column = PCPDynamicScreen_lookupMetric(screen, key); + + if (String_eq(p, "metric")) { + char* error; + if (pmRegisterDerivedMetric(column->metricName, value, &error) < 0) { + char* note; + xAsprintf(¬e, + "%s: failed to parse expression in %s at line %u\n%s\n", + pmGetProgname(), path, line, error); + free(error); + errno = EINVAL; + CRT_fatalError(note); + free(note); + } + + /* pmLookupText - add optional metric help text */ + if (!column->super.description && !column->instances) + Metric_lookupText(value, &column->super.description); + + } else { + /* this is a property of a dynamic column - the column expression */ + /* may not have been observed yet; i.e. we allow for any ordering */ + + if (String_eq(p, "caption")) { + free_and_xStrdup(&column->super.caption, value); + } else if (String_eq(p, "heading")) { + free_and_xStrdup(&column->super.heading, value); + } else if (String_eq(p, "description")) { + free_and_xStrdup(&column->super.description, value); + } else if (String_eq(p, "width")) { + column->width = strtoul(value, NULL, 10); + } else if (String_eq(p, "format")) { + free_and_xStrdup(&column->format, value); + } else if (String_eq(p, "instances")) { + if (String_eq(value, "True") || String_eq(value, "true")) + column->instances = true; + free_and_xStrdup(&column->super.description, screen->super.caption); + } else if (String_eq(p, "default")) { /* displayed by default */ + if (String_eq(value, "False") || String_eq(value, "false")) + column->defaultEnabled = column->super.enabled = false; + } + } +} + +static bool PCPDynamicScreen_validateScreenName(char* key, const char* path, unsigned int line) { + char* p = key; + char* end = strrchr(key, ']'); + + if (end) { + *end = '\0'; + } else { + fprintf(stderr, + "%s: no closing brace on screen name at %s line %u\n\"%s\"", + pmGetProgname(), path, line, key); + return false; + } + + while (*p) { + if (p == key) { + if (!isalpha(*p) && *p != '_') + break; + } else { + if (!isalnum(*p) && *p != '_') + break; + } + p++; + } + if (*p != '\0') { /* badness */ + fprintf(stderr, + "%s: invalid screen name at %s line %u\n\"%s\"", + pmGetProgname(), path, line, key); + return false; + } + return true; +} + +/* Ensure a screen name has not been defined previously */ +static bool PCPDynamicScreen_uniqueName(char* key, PCPDynamicScreens* screens) { + return !DynamicScreen_search(screens->table, key, NULL); +} + +static PCPDynamicScreen* PCPDynamicScreen_new(PCPDynamicScreens* screens, const char* name) { + PCPDynamicScreen* screen = xCalloc(1, sizeof(*screen)); + String_safeStrncpy(screen->super.name, name, sizeof(screen->super.name)); + screen->defaultEnabled = true; + + size_t id = screens->count; + Hashtable_put(screens->table, id, screen); + screens->count++; + + return screen; +} + +static void PCPDynamicScreen_parseFile(PCPDynamicScreens* screens, const char* path) { + FILE* file = fopen(path, "r"); + if (!file) + return; + + PCPDynamicScreen* screen = NULL; + unsigned int lineno = 0; + bool ok = true; + for (;;) { + char* line = String_readLine(file); + if (!line) + break; + lineno++; + + /* cleanup whitespace, skip comment lines */ + char* trimmed = String_trim(line); + free(line); + if (!trimmed || !trimmed[0] || trimmed[0] == '#') { + free(trimmed); + continue; + } + + size_t n; + char** config = String_split(trimmed, '=', &n); + free(trimmed); + if (config == NULL) + continue; + + char* key = String_trim(config[0]); + char* value = n > 1 ? String_trim(config[1]) : NULL; + if (key[0] == '[') { /* new section name - i.e. new screen */ + ok = PCPDynamicScreen_validateScreenName(key + 1, path, lineno); + if (ok) + ok = PCPDynamicScreen_uniqueName(key + 1, screens); + if (ok) + screen = PCPDynamicScreen_new(screens, key + 1); + if (pmDebugOptions.appl0) + fprintf(stderr, "[%s] screen: %s\n", path, key + 1); + } else if (!ok) { + ; /* skip this one, we're looking for a new header */ + } else if (!value || !screen) { + ; /* skip this one as we always need value strings */ + } else if (String_eq(key, "heading")) { + free_and_xStrdup(&screen->super.heading, value); + } else if (String_eq(key, "caption")) { + free_and_xStrdup(&screen->super.caption, value); + } else if (String_eq(key, "sortKey")) { + free_and_xStrdup(&screen->super.sortKey, value); + } else if (String_eq(key, "sortDirection")) { + screen->super.direction = strtoul(value, NULL, 10); + } else if (String_eq(key, "default") || String_eq(key, "enabled")) { + if (String_eq(value, "False") || String_eq(value, "false")) + screen->defaultEnabled = false; + else if (String_eq(value, "True") || String_eq(value, "true")) + screen->defaultEnabled = true; /* also default */ + } else { + PCPDynamicScreen_parseColumn(screen, path, lineno, key, value); + } + String_freeArray(config); + free(value); + free(key); + } + fclose(file); +} + +static void PCPDynamicScreen_scanDir(PCPDynamicScreens* screens, char* path) { + DIR* dir = opendir(path); + if (!dir) + return; + + struct dirent* dirent; + while ((dirent = readdir(dir)) != NULL) { + if (dirent->d_name[0] == '.') + continue; + + char* file = String_cat(path, dirent->d_name); + PCPDynamicScreen_parseFile(screens, file); + free(file); + } + closedir(dir); +} + +void PCPDynamicScreens_init(PCPDynamicScreens* screens, PCPDynamicColumns* columns) { + const char* share = pmGetConfig("PCP_SHARE_DIR"); + const char* sysconf = pmGetConfig("PCP_SYSCONF_DIR"); + const char* xdgConfigHome = getenv("XDG_CONFIG_HOME"); + const char* override = getenv("PCP_HTOP_DIR"); + const char* home = getenv("HOME"); + char* path; + + screens->table = Hashtable_new(0, true); + + /* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */ + if (override) { + path = String_cat(override, "/screens/"); + PCPDynamicScreen_scanDir(screens, path); + free(path); + } + + /* next, search in home directory alongside htoprc */ + if (xdgConfigHome) + path = String_cat(xdgConfigHome, "/htop/screens/"); + else if (home) + path = String_cat(home, "/.config/htop/screens/"); + else + path = NULL; + if (path) { + PCPDynamicScreen_scanDir(screens, path); + free(path); + } + + /* next, search in the system screens directory */ + path = String_cat(sysconf, "/htop/screens/"); + PCPDynamicScreen_scanDir(screens, path); + free(path); + + /* next, try the readonly system screens directory */ + path = String_cat(share, "/htop/screens/"); + PCPDynamicScreen_scanDir(screens, path); + free(path); + + /* establish internal metric identifier mappings */ + PCPDynamicScreens_appendDynamicColumns(screens, columns); +} + +static void PCPDynamicScreen_done(PCPDynamicScreen* ds) { + DynamicScreen_done(&ds->super); + Object_delete(ds->table); + free(ds->columns); +} + +static void PCPDynamicScreens_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) { + PCPDynamicScreen* ds = (PCPDynamicScreen*) value; + PCPDynamicScreen_done(ds); +} + +void PCPDynamicScreens_done(Hashtable* table) { + Hashtable_foreach(table, PCPDynamicScreens_free, NULL); +} + +void PCPDynamicScreen_appendTables(PCPDynamicScreens* screens, Machine* host) { + PCPDynamicScreen* ds; + + for (size_t i = 0; i < screens->count; i++) { + if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL) + continue; + ds->table = InDomTable_new(host, ds->indom, ds->key); + } +} + +void PCPDynamicScreen_appendScreens(PCPDynamicScreens* screens, Settings* settings) { + PCPDynamicScreen* ds; + + for (size_t i = 0; i < screens->count; i++) { + if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL) + continue; + if (ds->defaultEnabled == false) + continue; + const char* tab = ds->super.heading; + Settings_newDynamicScreen(settings, tab, &ds->super, &ds->table->super); + } +} + +/* called when htoprc .dynamic line is parsed for a dynamic screen */ +void PCPDynamicScreen_addDynamicScreen(PCPDynamicScreens* screens, ScreenSettings* ss) { + PCPDynamicScreen* ds; + + for (size_t i = 0; i < screens->count; i++) { + if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL) + continue; + if (String_eq(ss->dynamic, ds->super.name) == false) + continue; + ss->table = &ds->table->super; + } +} + +void PCPDynamicScreens_addAvailableColumns(Panel* availableColumns, Hashtable* screens, const char* screen) { + Vector_prune(availableColumns->items); + + bool success; + unsigned int key; + success = DynamicScreen_search(screens, screen, &key); + if (!success) + return; + + PCPDynamicScreen* dynamicScreen = Hashtable_get(screens, key); + if (!screen) + return; + + for (unsigned int j = 0; j < dynamicScreen->totalColumns; j++) { + PCPDynamicColumn* column = dynamicScreen->columns[j]; + const char* title = column->super.heading ? column->super.heading : column->super.name; + const char* text = column->super.description ? column->super.description : column->super.caption; + char description[256]; + if (text) + xSnprintf(description, sizeof(description), "%s - %s", title, text); + else + xSnprintf(description, sizeof(description), "%s", title); + Panel_add(availableColumns, (Object*) ListItem_new(description, j)); + } +} diff --git a/pcp/PCPDynamicScreen.h b/pcp/PCPDynamicScreen.h new file mode 100644 index 0000000..6248394 --- /dev/null +++ b/pcp/PCPDynamicScreen.h @@ -0,0 +1,56 @@ +#ifndef HEADER_PCPDynamicScreen +#define HEADER_PCPDynamicScreen +/* +htop - PCPDynamicScreen.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include <stddef.h> +#include <stdbool.h> + +#include "CRT.h" +#include "DynamicScreen.h" +#include "Hashtable.h" +#include "Machine.h" +#include "Panel.h" +#include "Settings.h" + + +struct InDomTable_; +struct PCPDynamicColumn_; +struct PCPDynamicColumns_; + +typedef struct PCPDynamicScreen_ { + DynamicScreen super; + + struct InDomTable_* table; + struct PCPDynamicColumn_** columns; + size_t totalColumns; + + unsigned int indom; /* instance domain number */ + unsigned int key; /* PCPMetric identifier */ + + bool defaultEnabled; /* enabled setting from configuration file */ + /* at runtime enabled screens have entries in settings->screens */ +} PCPDynamicScreen; + +typedef struct PCPDynamicScreens_ { + Hashtable* table; + size_t count; /* count of dynamic screens discovered from scan */ +} PCPDynamicScreens; + +void PCPDynamicScreens_init(PCPDynamicScreens* screens, struct PCPDynamicColumns_* columns); + +void PCPDynamicScreens_done(Hashtable* table); + +void PCPDynamicScreen_appendTables(PCPDynamicScreens* screens, Machine* host); + +void PCPDynamicScreen_appendScreens(PCPDynamicScreens* screens, Settings* settings); + +void PCPDynamicScreen_addDynamicScreen(PCPDynamicScreens* screens, ScreenSettings* ss); + +void PCPDynamicScreens_addAvailableColumns(Panel* availableColumns, Hashtable* screens, const char* screen); + +#endif diff --git a/pcp/PCPMachine.c b/pcp/PCPMachine.c new file mode 100644 index 0000000..2e87253 --- /dev/null +++ b/pcp/PCPMachine.c @@ -0,0 +1,345 @@ +/* +htop - PCPProcessTable.c +(C) 2014 Hisham H. Muhammad +(C) 2020-2023 htop dev team +(C) 2020-2023 Red Hat, Inc. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "pcp/PCPMachine.h" + +#include <assert.h> +#include <limits.h> +#include <math.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> + +#include "Machine.h" +#include "Macros.h" +#include "Object.h" +#include "Platform.h" +#include "Settings.h" +#include "XUtils.h" + +#include "pcp/Metric.h" +#include "pcp/PCPProcess.h" + + +static void PCPMachine_updateCPUcount(PCPMachine* this) { + Machine* super = &this->super; + super->activeCPUs = Metric_instanceCount(PCP_PERCPU_SYSTEM); + unsigned int cpus = Platform_getMaxCPU(); + if (cpus == super->existingCPUs) + return; + if (cpus == 0) + cpus = super->activeCPUs; + if (cpus <= 1) + cpus = super->activeCPUs = 1; + super->existingCPUs = cpus; + + free(this->percpu); + free(this->values); + + this->percpu = xCalloc(cpus, sizeof(pmAtomValue*)); + for (unsigned int i = 0; i < cpus; i++) + this->percpu[i] = xCalloc(CPU_METRIC_COUNT, sizeof(pmAtomValue)); + this->values = xCalloc(cpus, sizeof(pmAtomValue)); +} + +static void PCPMachine_updateMemoryInfo(Machine* host) { + unsigned long long int freeMem = 0; + unsigned long long int swapFreeMem = 0; + unsigned long long int sreclaimableMem = 0; + host->totalMem = host->usedMem = host->cachedMem = 0; + host->usedSwap = host->totalSwap = host->sharedMem = 0; + + pmAtomValue value; + if (Metric_values(PCP_MEM_TOTAL, &value, 1, PM_TYPE_U64) != NULL) + host->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) + host->buffersMem = value.ull; + if (Metric_values(PCP_MEM_SRECLAIM, &value, 1, PM_TYPE_U64) != NULL) + sreclaimableMem = value.ull; + if (Metric_values(PCP_MEM_SHARED, &value, 1, PM_TYPE_U64) != NULL) + host->sharedMem = value.ull; + if (Metric_values(PCP_MEM_CACHED, &value, 1, PM_TYPE_U64) != NULL) + host->cachedMem = value.ull + sreclaimableMem - host->sharedMem; + const memory_t usedDiff = freeMem + host->cachedMem + sreclaimableMem + host->buffersMem; + host->usedMem = (host->totalMem >= usedDiff) ? + host->totalMem - usedDiff : host->totalMem - freeMem; + if (Metric_values(PCP_MEM_AVAILABLE, &value, 1, PM_TYPE_U64) != NULL) + host->availableMem = MINIMUM(value.ull, host->totalMem); + else + host->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) + host->totalSwap = value.ull; + if (Metric_values(PCP_MEM_SWAPCACHED, &value, 1, PM_TYPE_U64) != NULL) + host->cachedSwap = value.ull; + host->usedSwap = host->totalSwap - swapFreeMem - host->cachedSwap; +} + +/* make copies of previously sampled values to avoid overwrite */ +static inline void PCPMachine_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 PCPMachine_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 PCPMachine_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; + + PCPMachine_saveCPUTimePeriod(values, CPU_USER_PERIOD, usertime); + PCPMachine_saveCPUTimePeriod(values, CPU_NICE_PERIOD, nicetime); + PCPMachine_saveCPUTimePeriod(values, CPU_SYSTEM_PERIOD, systemtime); + PCPMachine_saveCPUTimePeriod(values, CPU_SYSTEM_ALL_PERIOD, systalltime); + PCPMachine_saveCPUTimePeriod(values, CPU_IDLE_ALL_PERIOD, idlealltime); + PCPMachine_saveCPUTimePeriod(values, CPU_IDLE_PERIOD, idletime); + PCPMachine_saveCPUTimePeriod(values, CPU_IOWAIT_PERIOD, iowaittime); + PCPMachine_saveCPUTimePeriod(values, CPU_IRQ_PERIOD, irqtime); + PCPMachine_saveCPUTimePeriod(values, CPU_SOFTIRQ_PERIOD, softirqtime); + PCPMachine_saveCPUTimePeriod(values, CPU_STEAL_PERIOD, stealtime); + PCPMachine_saveCPUTimePeriod(values, CPU_GUEST_PERIOD, virtalltime); + PCPMachine_saveCPUTimePeriod(values, CPU_TOTAL_PERIOD, totaltime); +} + +static void PCPMachine_updateAllCPUTime(PCPMachine* 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 PCPMachine_updatePerCPUTime(PCPMachine* this, Metric metric, CPUMetric cpumetric) +{ + int cpus = this->super.existingCPUs; + 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 PCPMachine_updatePerCPUReal(PCPMachine* this, Metric metric, CPUMetric cpumetric) +{ + int cpus = this->super.existingCPUs; + 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 inline void PCPMachine_scanZswapInfo(PCPMachine* this) { + pmAtomValue value; + + memset(&this->zswap, 0, sizeof(ZswapStats)); + if (Metric_values(PCP_MEM_ZSWAP, &value, 1, PM_TYPE_U64)) + this->zswap.usedZswapComp = value.ull; + if (Metric_values(PCP_MEM_ZSWAPPED, &value, 1, PM_TYPE_U64)) + this->zswap.usedZswapOrig = value.ull; +} + +static inline void PCPMachine_scanZfsArcstats(PCPMachine* this) { + unsigned long long int dbufSize = 0; + unsigned long long int dnodeSize = 0; + unsigned long long int bonusSize = 0; + pmAtomValue value; + + memset(&this->zfs, 0, sizeof(ZfsArcStats)); + if (Metric_values(PCP_ZFS_ARC_ANON_SIZE, &value, 1, PM_TYPE_U64)) + this->zfs.anon = value.ull / ONE_K; + if (Metric_values(PCP_ZFS_ARC_C_MIN, &value, 1, PM_TYPE_U64)) + this->zfs.min = value.ull / ONE_K; + if (Metric_values(PCP_ZFS_ARC_C_MAX, &value, 1, PM_TYPE_U64)) + this->zfs.max = value.ull / ONE_K; + if (Metric_values(PCP_ZFS_ARC_BONUS_SIZE, &value, 1, PM_TYPE_U64)) + bonusSize = value.ull / ONE_K; + if (Metric_values(PCP_ZFS_ARC_DBUF_SIZE, &value, 1, PM_TYPE_U64)) + dbufSize = value.ull / ONE_K; + if (Metric_values(PCP_ZFS_ARC_DNODE_SIZE, &value, 1, PM_TYPE_U64)) + dnodeSize = value.ull / ONE_K; + if (Metric_values(PCP_ZFS_ARC_COMPRESSED_SIZE, &value, 1, PM_TYPE_U64)) + this->zfs.compressed = value.ull / ONE_K; + if (Metric_values(PCP_ZFS_ARC_UNCOMPRESSED_SIZE, &value, 1, PM_TYPE_U64)) + this->zfs.uncompressed = value.ull / ONE_K; + if (Metric_values(PCP_ZFS_ARC_HDR_SIZE, &value, 1, PM_TYPE_U64)) + this->zfs.header = value.ull / ONE_K; + if (Metric_values(PCP_ZFS_ARC_MFU_SIZE, &value, 1, PM_TYPE_U64)) + this->zfs.MFU = value.ull / ONE_K; + if (Metric_values(PCP_ZFS_ARC_MRU_SIZE, &value, 1, PM_TYPE_U64)) + this->zfs.MRU = value.ull / ONE_K; + if (Metric_values(PCP_ZFS_ARC_SIZE, &value, 1, PM_TYPE_U64)) + this->zfs.size = value.ull / ONE_K; + + this->zfs.other = (dbufSize + dnodeSize + bonusSize) / ONE_K; + this->zfs.enabled = (this->zfs.size > 0); + this->zfs.isCompressed = (this->zfs.compressed > 0); +} + +static void PCPMachine_scan(PCPMachine* this) { + Machine* super = &this->super; + + PCPMachine_updateMemoryInfo(super); + PCPMachine_updateCPUcount(this); + + PCPMachine_backupCPUTime(this->cpu); + PCPMachine_updateAllCPUTime(this, PCP_CPU_USER, CPU_USER_TIME); + PCPMachine_updateAllCPUTime(this, PCP_CPU_NICE, CPU_NICE_TIME); + PCPMachine_updateAllCPUTime(this, PCP_CPU_SYSTEM, CPU_SYSTEM_TIME); + PCPMachine_updateAllCPUTime(this, PCP_CPU_IDLE, CPU_IDLE_TIME); + PCPMachine_updateAllCPUTime(this, PCP_CPU_IOWAIT, CPU_IOWAIT_TIME); + PCPMachine_updateAllCPUTime(this, PCP_CPU_IRQ, CPU_IRQ_TIME); + PCPMachine_updateAllCPUTime(this, PCP_CPU_SOFTIRQ, CPU_SOFTIRQ_TIME); + PCPMachine_updateAllCPUTime(this, PCP_CPU_STEAL, CPU_STEAL_TIME); + PCPMachine_updateAllCPUTime(this, PCP_CPU_GUEST, CPU_GUEST_TIME); + PCPMachine_deriveCPUTime(this->cpu); + + for (unsigned int i = 0; i < super->existingCPUs; i++) + PCPMachine_backupCPUTime(this->percpu[i]); + PCPMachine_updatePerCPUTime(this, PCP_PERCPU_USER, CPU_USER_TIME); + PCPMachine_updatePerCPUTime(this, PCP_PERCPU_NICE, CPU_NICE_TIME); + PCPMachine_updatePerCPUTime(this, PCP_PERCPU_SYSTEM, CPU_SYSTEM_TIME); + PCPMachine_updatePerCPUTime(this, PCP_PERCPU_IDLE, CPU_IDLE_TIME); + PCPMachine_updatePerCPUTime(this, PCP_PERCPU_IOWAIT, CPU_IOWAIT_TIME); + PCPMachine_updatePerCPUTime(this, PCP_PERCPU_IRQ, CPU_IRQ_TIME); + PCPMachine_updatePerCPUTime(this, PCP_PERCPU_SOFTIRQ, CPU_SOFTIRQ_TIME); + PCPMachine_updatePerCPUTime(this, PCP_PERCPU_STEAL, CPU_STEAL_TIME); + PCPMachine_updatePerCPUTime(this, PCP_PERCPU_GUEST, CPU_GUEST_TIME); + for (unsigned int i = 0; i < super->existingCPUs; i++) + PCPMachine_deriveCPUTime(this->percpu[i]); + + if (super->settings->showCPUFrequency) + PCPMachine_updatePerCPUReal(this, PCP_HINV_CPUCLOCK, CPU_FREQUENCY); + + PCPMachine_scanZfsArcstats(this); + PCPMachine_scanZswapInfo(this); +} + +void Machine_scan(Machine* super) { + PCPMachine* host = (PCPMachine*) super; + const Settings* settings = super->settings; + uint32_t flags = settings->ss->flags; + bool flagged; + + for (int metric = PCP_PROC_PID; metric < PCP_METRIC_COUNT; metric++) + Metric_enable(metric, true); + + flagged = settings->showCPUFrequency; + Metric_enable(PCP_HINV_CPUCLOCK, flagged); + flagged = flags & PROCESS_FLAG_LINUX_CGROUP; + Metric_enable(PCP_PROC_CGROUPS, flagged); + flagged = flags & PROCESS_FLAG_LINUX_OOM; + Metric_enable(PCP_PROC_OOMSCORE, flagged); + flagged = flags & PROCESS_FLAG_LINUX_CTXT; + Metric_enable(PCP_PROC_VCTXSW, flagged); + Metric_enable(PCP_PROC_NVCTXSW, flagged); + flagged = flags & PROCESS_FLAG_LINUX_SECATTR; + Metric_enable(PCP_PROC_LABELS, flagged); + flagged = flags & PROCESS_FLAG_LINUX_AUTOGROUP; + Metric_enable(PCP_PROC_AUTOGROUP_ID, flagged); + Metric_enable(PCP_PROC_AUTOGROUP_NICE, flagged); + + /* Sample smaps metrics on every second pass to improve performance */ + host->smaps_flag = !!host->smaps_flag; + Metric_enable(PCP_PROC_SMAPS_PSS, host->smaps_flag); + Metric_enable(PCP_PROC_SMAPS_SWAP, host->smaps_flag); + Metric_enable(PCP_PROC_SMAPS_SWAPPSS, host->smaps_flag); + + struct timeval timestamp; + if (Metric_fetch(×tamp) != true) + return; + + double sample = host->timestamp; + host->timestamp = pmtimevalToReal(×tamp); + host->period = (host->timestamp - sample) * 100; + + PCPMachine_scan(host); +} + +Machine* Machine_new(UsersTable* usersTable, uid_t userId) { + PCPMachine* this = xCalloc(1, sizeof(PCPMachine)); + Machine* super = &this->super; + + Machine_init(super, usersTable, userId); + + struct timeval timestamp; + gettimeofday(×tamp, NULL); + this->timestamp = pmtimevalToReal(×tamp); + + this->cpu = xCalloc(CPU_METRIC_COUNT, sizeof(pmAtomValue)); + PCPMachine_updateCPUcount(this); + + Platform_updateTables(super); + + return super; +} + +void Machine_delete(Machine* super) { + PCPMachine* this = (PCPMachine*) super; + Machine_done(super); + free(this->values); + for (unsigned int i = 0; i < super->existingCPUs; i++) + free(this->percpu[i]); + free(this->percpu); + free(this->cpu); + free(this); +} + +bool Machine_isCPUonline(const Machine* host, unsigned int id) { + assert(id < host->existingCPUs); + (void) host; + + pmAtomValue value; + if (Metric_instance(PCP_PERCPU_SYSTEM, id, id, &value, PM_TYPE_U32)) + return true; + return false; +} diff --git a/pcp/PCPMachine.h b/pcp/PCPMachine.h new file mode 100644 index 0000000..6518bd4 --- /dev/null +++ b/pcp/PCPMachine.h @@ -0,0 +1,71 @@ +#ifndef HEADER_PCPMachine +#define HEADER_PCPMachine +/* +htop - PCPMachine.h +(C) 2014 Hisham H. Muhammad +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include <stdbool.h> +#include <sys/types.h> + +#include "Hashtable.h" +#include "Machine.h" +#include "UsersTable.h" + +#include "pcp/Platform.h" +#include "linux/ZswapStats.h" +#include "zfs/ZfsArcStats.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 PCPMachine_ { + Machine super; + int smaps_flag; + double period; + 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 */ + + ZfsArcStats zfs; + /*ZramStats zram; -- not needed, calculated in-line in Platform.c */ + ZswapStats zswap; +} PCPMachine; + +#endif diff --git a/pcp/PCPProcess.c b/pcp/PCPProcess.c new file mode 100644 index 0000000..69e2972 --- /dev/null +++ b/pcp/PCPProcess.c @@ -0,0 +1,310 @@ +/* +htop - PCPProcess.c +(C) 2014 Hisham H. Muhammad +(C) 2020-2021 htop dev team +(C) 2020-2021 Red Hat, Inc. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "pcp/PCPProcess.h" + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> + +#include "CRT.h" +#include "Macros.h" +#include "Process.h" +#include "ProvideCurses.h" +#include "RichString.h" +#include "XUtils.h" + +#include "pcp/PCPDynamicColumn.h" + + +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] = { .name = "TTY", .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, }, + [ELAPSED] = { .name = "ELAPSED", .title = "ELAPSED ", .description = "Time since 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_PRIV] = { .name = "M_PRIV", .title = " PRIV ", .description = "The private memory size of the process - resident set size minus shared memory", .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, .autoWidth = 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, .autoWidth = 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 (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, }, + [CONTAINER] = { .name = "CONTAINER", .title = "CONTAINER ", .description = "Name of the container the process is in (guessed by heuristics)", .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", .flags = 0, }, + [PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process", .flags = 0, }, + [CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, }, + [AUTOGROUP_ID] = { .name = "AUTOGROUP_ID", .title = "AGRP", .description = "The autogroup identifier of the process", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, }, + [AUTOGROUP_NICE] = { .name = "AUTOGROUP_NICE", .title = " ANI", .description = "Nice value (the higher the value, the more other processes take priority) associated with the process autogroup", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, }, +}; + +Process* PCPProcess_new(const Machine* host) { + PCPProcess* this = xCalloc(1, sizeof(PCPProcess)); + Object_setClass(this, Class(PCPProcess)); + Process_init(&this->super, host); + return &this->super; +} + +void Process_delete(Object* cast) { + PCPProcess* this = (PCPProcess*) cast; + Process_done((Process*)cast); + free(this->cgroup_short); + free(this->cgroup); + free(this->secattr); + free(this); +} + +static void PCPProcess_printDelay(float delay_percent, char* buffer, size_t n) { + if (isNonnegative(delay_percent)) { + xSnprintf(buffer, n, "%4.1f ", delay_percent); + } else { + xSnprintf(buffer, n, " N/A "); + } +} + +static double PCPProcess_totalIORate(const PCPProcess* pp) { + double totalRate = NAN; + if (isNonnegative(pp->io_rate_read_bps)) { + totalRate = pp->io_rate_read_bps; + if (isNonnegative(pp->io_rate_write_bps)) { + totalRate += pp->io_rate_write_bps; + } + } else if (isNonnegative(pp->io_rate_write_bps)) { + totalRate = pp->io_rate_write_bps; + } + return totalRate; +} + +static void PCPProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const PCPProcess* pp = (const PCPProcess*) super; + + bool coloring = super->host->settings->highlightMegabytes; + char buffer[256]; buffer[255] = '\0'; + int attr = CRT_colors[DEFAULT_COLOR]; + size_t n = sizeof(buffer) - 1; + + switch ((int)field) { + case CMINFLT: Row_printCount(str, pp->cminflt, coloring); return; + case CMAJFLT: Row_printCount(str, pp->cmajflt, coloring); return; + case M_DRS: Row_printBytes(str, pp->m_drs, coloring); return; + case M_DT: Row_printBytes(str, pp->m_dt, coloring); return; + case M_LRS: Row_printBytes(str, pp->m_lrs, coloring); return; + case M_TRS: Row_printBytes(str, pp->m_trs, coloring); return; + case M_SHARE: Row_printBytes(str, pp->m_share, coloring); return; + case M_PRIV: Row_printBytes(str, pp->m_priv, coloring); return; + case M_PSS: Row_printKBytes(str, pp->m_pss, coloring); return; + case M_SWAP: Row_printKBytes(str, pp->m_swap, coloring); return; + case M_PSSWP: Row_printKBytes(str, pp->m_psswp, coloring); return; + case UTIME: Row_printTime(str, pp->utime, coloring); return; + case STIME: Row_printTime(str, pp->stime, coloring); return; + case CUTIME: Row_printTime(str, pp->cutime, coloring); return; + case CSTIME: Row_printTime(str, pp->cstime, coloring); return; + case RCHAR: Row_printBytes(str, pp->io_rchar, coloring); return; + case WCHAR: Row_printBytes(str, pp->io_wchar, coloring); return; + case SYSCR: Row_printCount(str, pp->io_syscr, coloring); return; + case SYSCW: Row_printCount(str, pp->io_syscw, coloring); return; + case RBYTES: Row_printBytes(str, pp->io_read_bytes, coloring); return; + case WBYTES: Row_printBytes(str, pp->io_write_bytes, coloring); return; + case CNCLWB: Row_printBytes(str, pp->io_cancelled_write_bytes, coloring); return; + case IO_READ_RATE: Row_printRate(str, pp->io_rate_read_bps, coloring); return; + case IO_WRITE_RATE: Row_printRate(str, pp->io_rate_write_bps, coloring); return; + case IO_RATE: Row_printRate(str, PCPProcess_totalIORate(pp), coloring); return; + case CGROUP: xSnprintf(buffer, n, "%-35.35s ", pp->cgroup ? pp->cgroup : "N/A"); break; + case CCGROUP: xSnprintf(buffer, n, "%-35.35s ", pp->cgroup_short ? pp->cgroup_short : (pp->cgroup ? pp->cgroup : "N/A")); break; + case CONTAINER: xSnprintf(buffer, n, "%-35.35s ", pp->container_short ? pp->container_short : "N/A"); 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 AUTOGROUP_ID: + if (pp->autogroup_id != -1) { + xSnprintf(buffer, n, "%4ld ", pp->autogroup_id); + } else { + attr = CRT_colors[PROCESS_SHADOW]; + xSnprintf(buffer, n, " N/A "); + } + break; + case AUTOGROUP_NICE: + if (pp->autogroup_id != -1) { + xSnprintf(buffer, n, "%3d ", pp->autogroup_nice); + attr = pp->autogroup_nice < 0 ? CRT_colors[PROCESS_HIGH_PRIORITY] + : pp->autogroup_nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY] + : CRT_colors[PROCESS_SHADOW]; + } else { + attr = CRT_colors[PROCESS_SHADOW]; + xSnprintf(buffer, n, "N/A "); + } + break; + default: + Process_writeField(&pp->super, str, field); + return; + } + + RichString_appendWide(str, attr, buffer); +} + +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_PRIV: + return SPACESHIP_NUMBER(p1->m_priv, p2->m_priv); + 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 compareRealNumbers(p1->io_rate_read_bps, p2->io_rate_read_bps); + case IO_WRITE_RATE: + return compareRealNumbers(p1->io_rate_write_bps, p2->io_rate_write_bps); + case IO_RATE: + return compareRealNumbers(PCPProcess_totalIORate(p1), PCPProcess_totalIORate(p2)); + case CGROUP: + return SPACESHIP_NULLSTR(p1->cgroup, p2->cgroup); + case CCGROUP: + return SPACESHIP_NULLSTR(p1->cgroup_short, p2->cgroup_short); + case CONTAINER: + return SPACESHIP_NULLSTR(p1->container_short, p2->container_short); + case OOM: + return SPACESHIP_NUMBER(p1->oom, p2->oom); + case PERCENT_CPU_DELAY: + return compareRealNumbers(p1->cpu_delay_percent, p2->cpu_delay_percent); + case PERCENT_IO_DELAY: + return compareRealNumbers(p1->blkio_delay_percent, p2->blkio_delay_percent); + case PERCENT_SWAP_DELAY: + return compareRealNumbers(p1->swapin_delay_percent, p2->swapin_delay_percent); + case CTXT: + return SPACESHIP_NUMBER(p1->ctxt_diff, p2->ctxt_diff); + case SECATTR: + return SPACESHIP_NULLSTR(p1->secattr, p2->secattr); + case AUTOGROUP_ID: + return SPACESHIP_NUMBER(p1->autogroup_id, p2->autogroup_id); + case AUTOGROUP_NICE: + return SPACESHIP_NUMBER(p1->autogroup_nice, p2->autogroup_nice); + default: + if (key < LAST_PROCESSFIELD) + return Process_compareByKey_Base(v1, v2, key); + return PCPDynamicColumn_compareByKey(p1, p2, key); + } +} + +const ProcessClass PCPProcess_class = { + .super = { + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = PCPProcess_rowWriteField, + }, + .compareByKey = PCPProcess_compareByKey, +}; diff --git a/pcp/PCPProcess.h b/pcp/PCPProcess.h new file mode 100644 index 0000000..e953757 --- /dev/null +++ b/pcp/PCPProcess.h @@ -0,0 +1,103 @@ +#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 <stdbool.h> + +#include "Machine.h" +#include "Object.h" +#include "Process.h" + + +#define PROCESS_FLAG_LINUX_CGROUP 0x00000800 +#define PROCESS_FLAG_LINUX_OOM 0x00001000 +#define PROCESS_FLAG_LINUX_SMAPS 0x00002000 +#define PROCESS_FLAG_LINUX_CTXT 0x00004000 +#define PROCESS_FLAG_LINUX_SECATTR 0x00008000 +#define PROCESS_FLAG_LINUX_AUTOGROUP 0x00080000 + +typedef struct PCPProcess_ { + Process super; + + /* default result offset to use for searching proc metrics */ + unsigned int offset; + + 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_priv; + 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; + char* cgroup_short; + char* container_short; + long int autogroup_id; + int autogroup_nice; + unsigned int oom; + 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; + +extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD]; + +extern const ProcessClass PCPProcess_class; + +Process* PCPProcess_new(const Machine* host); + +void Process_delete(Object* cast); + +bool Process_isThread(const Process* this); + +#endif diff --git a/pcp/PCPProcessTable.c b/pcp/PCPProcessTable.c new file mode 100644 index 0000000..4999bdc --- /dev/null +++ b/pcp/PCPProcessTable.c @@ -0,0 +1,486 @@ +/* +htop - PCPProcessTable.c +(C) 2014 Hisham H. Muhammad +(C) 2020-2021 htop dev team +(C) 2020-2021 Red Hat, Inc. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "pcp/PCPProcessTable.h" + +#include <assert.h> +#include <limits.h> +#include <math.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> + +#include "Machine.h" +#include "Macros.h" +#include "Object.h" +#include "Platform.h" +#include "Process.h" +#include "Settings.h" +#include "XUtils.h" + +#include "linux/CGroupUtils.h" +#include "pcp/Metric.h" +#include "pcp/PCPMachine.h" +#include "pcp/PCPProcess.h" + + +ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) { + PCPProcessTable* this = xCalloc(1, sizeof(PCPProcessTable)); + Object_setClass(this, Class(ProcessTable)); + + ProcessTable* super = &this->super; + ProcessTable_init(super, Class(PCPProcess), host, pidMatchList); + + return super; +} + +void ProcessTable_delete(Object* cast) { + PCPProcessTable* this = (PCPProcessTable*) cast; + ProcessTable_done(&this->super); + free(this); +} + +static inline long Metric_instance_s32(int metric, int pid, int offset, long fallback) { + pmAtomValue value; + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_32)) + return value.l; + return fallback; +} + +static inline long long Metric_instance_s64(int metric, int pid, int offset, long long fallback) { + pmAtomValue value; + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_64)) + return value.l; + return fallback; +} + +static inline unsigned long Metric_instance_u32(int metric, int pid, int offset, unsigned long fallback) { + pmAtomValue value; + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U32)) + return value.ul; + return fallback; +} + +static inline unsigned long long Metric_instance_u64(int metric, int pid, int offset, unsigned long long fallback) { + pmAtomValue value; + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U64)) + return value.ull; + return fallback; +} + +static inline unsigned long long Metric_instance_time(int metric, int pid, int offset) { + pmAtomValue value; + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U64)) + return value.ull / 10; + return 0; +} + +static inline unsigned long long Metric_instance_ONE_K(int metric, int pid, int offset) { + pmAtomValue value; + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U64)) + return value.ull / ONE_K; + return ULLONG_MAX; +} + +static inline char Metric_instance_char(int metric, int pid, int offset, char fallback) { + pmAtomValue value; + if (Metric_instance(metric, pid, offset, &value, PM_TYPE_STRING)) { + char uchar = value.cp[0]; + free(value.cp); + return uchar; + } + return fallback; +} + +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; +} + +static inline ProcessState PCPProcessTable_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 PCPProcessTable_updateID(Process* process, int pid, int offset) { + Process_setThreadGroup(process, Metric_instance_u32(PCP_PROC_TGID, pid, offset, 1)); + Process_setParent(process, Metric_instance_u32(PCP_PROC_PPID, pid, offset, 1)); + process->state = PCPProcessTable_getProcessState(Metric_instance_char(PCP_PROC_STATE, pid, offset, '?')); +} + +static void PCPProcessTable_updateInfo(PCPProcess* pp, int pid, int offset, char* command, size_t commLen) { + Process* process = &pp->super; + pmAtomValue value; + + if (!Metric_instance(PCP_PROC_CMD, pid, offset, &value, PM_TYPE_STRING)) + value.cp = xStrdup("<unknown>"); + String_safeStrncpy(command, value.cp, commLen); + free(value.cp); + + process->pgrp = Metric_instance_u32(PCP_PROC_PGRP, pid, offset, 0); + process->session = Metric_instance_u32(PCP_PROC_SESSION, pid, offset, 0); + process->tty_nr = Metric_instance_u32(PCP_PROC_TTY, pid, offset, 0); + process->tpgid = Metric_instance_u32(PCP_PROC_TTYPGRP, pid, offset, 0); + process->minflt = Metric_instance_u32(PCP_PROC_MINFLT, pid, offset, 0); + pp->cminflt = Metric_instance_u32(PCP_PROC_CMINFLT, pid, offset, 0); + process->majflt = Metric_instance_u32(PCP_PROC_MAJFLT, pid, offset, 0); + pp->cmajflt = Metric_instance_u32(PCP_PROC_CMAJFLT, pid, offset, 0); + pp->utime = Metric_instance_time(PCP_PROC_UTIME, pid, offset); + pp->stime = Metric_instance_time(PCP_PROC_STIME, pid, offset); + pp->cutime = Metric_instance_time(PCP_PROC_CUTIME, pid, offset); + pp->cstime = Metric_instance_time(PCP_PROC_CSTIME, pid, offset); + process->priority = Metric_instance_u32(PCP_PROC_PRIORITY, pid, offset, 0); + process->nice = Metric_instance_s32(PCP_PROC_NICE, pid, offset, 0); + process->nlwp = Metric_instance_u32(PCP_PROC_THREADS, pid, offset, 0); + process->starttime_ctime = Metric_instance_time(PCP_PROC_STARTTIME, pid, offset); + process->processor = Metric_instance_u32(PCP_PROC_PROCESSOR, pid, offset, 0); + + process->time = pp->utime + pp->stime; +} + +static void PCPProcessTable_updateIO(PCPProcess* pp, int pid, int offset, unsigned long long now) { + pmAtomValue value; + + pp->io_rchar = Metric_instance_ONE_K(PCP_PROC_IO_RCHAR, pid, offset); + pp->io_wchar = Metric_instance_ONE_K(PCP_PROC_IO_WCHAR, pid, offset); + pp->io_syscr = Metric_instance_u64(PCP_PROC_IO_SYSCR, pid, offset, ULLONG_MAX); + pp->io_syscw = Metric_instance_u64(PCP_PROC_IO_SYSCW, pid, offset, ULLONG_MAX); + pp->io_cancelled_write_bytes = Metric_instance_ONE_K(PCP_PROC_IO_CANCELLED, pid, offset); + + if (Metric_instance(PCP_PROC_IO_READB, pid, offset, &value, PM_TYPE_U64)) { + unsigned long long last_read = pp->io_read_bytes; + pp->io_read_bytes = value.ull / ONE_K; + pp->io_rate_read_bps = ONE_K * (pp->io_read_bytes - last_read) / + (now - pp->io_last_scan_time); + } else { + pp->io_read_bytes = ULLONG_MAX; + pp->io_rate_read_bps = NAN; + } + + if (Metric_instance(PCP_PROC_IO_WRITEB, pid, offset, &value, PM_TYPE_U64)) { + unsigned long long last_write = pp->io_write_bytes; + pp->io_write_bytes = value.ull; + pp->io_rate_write_bps = ONE_K * (pp->io_write_bytes - last_write) / + (now - pp->io_last_scan_time); + } else { + pp->io_write_bytes = ULLONG_MAX; + pp->io_rate_write_bps = NAN; + } + + pp->io_last_scan_time = now; +} + +static void PCPProcessTable_updateMemory(PCPProcess* pp, int pid, int offset) { + pp->super.m_virt = Metric_instance_u32(PCP_PROC_MEM_SIZE, pid, offset, 0); + pp->super.m_resident = Metric_instance_u32(PCP_PROC_MEM_RSS, pid, offset, 0); + pp->m_share = Metric_instance_u32(PCP_PROC_MEM_SHARE, pid, offset, 0); + pp->m_priv = pp->super.m_resident - pp->m_share; + pp->m_trs = Metric_instance_u32(PCP_PROC_MEM_TEXTRS, pid, offset, 0); + pp->m_lrs = Metric_instance_u32(PCP_PROC_MEM_LIBRS, pid, offset, 0); + pp->m_drs = Metric_instance_u32(PCP_PROC_MEM_DATRS, pid, offset, 0); + pp->m_dt = Metric_instance_u32(PCP_PROC_MEM_DIRTY, pid, offset, 0); +} + +static void PCPProcessTable_updateSmaps(PCPProcess* pp, pid_t pid, int offset) { + pp->m_pss = Metric_instance_u64(PCP_PROC_SMAPS_PSS, pid, offset, 0); + pp->m_swap = Metric_instance_u64(PCP_PROC_SMAPS_SWAP, pid, offset, 0); + pp->m_psswp = Metric_instance_u64(PCP_PROC_SMAPS_SWAPPSS, pid, offset, 0); +} + +static void PCPProcessTable_readOomData(PCPProcess* pp, int pid, int offset) { + pp->oom = Metric_instance_u32(PCP_PROC_OOMSCORE, pid, offset, 0); +} + +static void PCPProcessTable_readAutogroup(PCPProcess* pp, int pid, int offset) { + pp->autogroup_id = Metric_instance_s64(PCP_PROC_AUTOGROUP_ID, pid, offset, -1); + pp->autogroup_nice = Metric_instance_s32(PCP_PROC_AUTOGROUP_NICE, pid, offset, 0); +} + +static void PCPProcessTable_readCtxtData(PCPProcess* pp, 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; + + pp->ctxt_diff = ctxt > pp->ctxt_total ? ctxt - pp->ctxt_total : 0; + pp->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 PCPProcessTable_updateTTY(Process* process, int pid, int offset) { + process->tty_name = setString(PCP_PROC_TTYNAME, pid, offset, process->tty_name); +} + +static void PCPProcessTable_readCGroups(PCPProcess* pp, int pid, int offset) { + pp->cgroup = setString(PCP_PROC_CGROUPS, pid, offset, pp->cgroup); + + if (pp->cgroup) { + char* cgroup_short = CGroup_filterName(pp->cgroup); + if (cgroup_short) { + Row_updateFieldWidth(CCGROUP, strlen(cgroup_short)); + free_and_xStrdup(&pp->cgroup_short, cgroup_short); + free(cgroup_short); + } else { + //CCGROUP is alias to normal CGROUP if shortening fails + Row_updateFieldWidth(CCGROUP, strlen(pp->cgroup)); + free(pp->cgroup_short); + pp->cgroup_short = NULL; + } + + char* container_short = CGroup_filterName(pp->cgroup); + if (container_short) { + Row_updateFieldWidth(CONTAINER, strlen(container_short)); + free_and_xStrdup(&pp->container_short, container_short); + free(container_short); + } else { + Row_updateFieldWidth(CONTAINER, strlen("N/A")); + free(pp->container_short); + pp->container_short = NULL; + } + } else { + free(pp->cgroup_short); + pp->cgroup_short = NULL; + + free(pp->container_short); + pp->container_short = NULL; + } +} + +static void PCPProcessTable_readSecattrData(PCPProcess* pp, int pid, int offset) { + pp->secattr = setString(PCP_PROC_LABELS, pid, offset, pp->secattr); +} + +static void PCPProcessTable_readCwd(PCPProcess* pp, int pid, int offset) { + pp->super.procCwd = setString(PCP_PROC_CWD, pid, offset, pp->super.procCwd); +} + +static void PCPProcessTable_updateUsername(Process* process, int pid, int offset, UsersTable* users) { + process->st_uid = Metric_instance_u32(PCP_PROC_ID_UID, pid, offset, 0); + process->user = setUser(users, process->st_uid, pid, offset); +} + +static void PCPProcessTable_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 != ZOMBIE) + process->isKernelThread = true; + Process_updateCmdline(process, NULL, 0, 0); + return; + } + + char* command = value.cp; + int length = strlen(command); + if (command[0] != '(') { + process->isKernelThread = false; + } else { + ++command; + --length; + if (command[length - 1] == ')') + command[--length] = '\0'; + process->isKernelThread = true; + } + + int tokenEnd = 0; + int tokenStart = 0; + bool argSepSpace = false; + + 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; + /* special-case arguments for problematic situations like "find /" */ + if (command[i] <= ' ') + argSepSpace = true; + } + tokenEnd = length; + if (argSepSpace) + tokenStart = 0; + + Process_updateCmdline(process, command, tokenStart, tokenEnd); + free(value.cp); + + Process_updateComm(process, comm); + + if (Metric_instance(PCP_PROC_EXE, pid, offset, &value, PM_TYPE_STRING)) { + Process_updateExe(process, value.cp[0] ? value.cp : NULL); + free(value.cp); + } +} + +static bool PCPProcessTable_updateProcesses(PCPProcessTable* this) { + ProcessTable* pt = (ProcessTable*) this; + Machine* host = pt->super.host; + PCPMachine* phost = (PCPMachine*) host; + + const Settings* settings = host->settings; + bool hideKernelThreads = settings->hideKernelThreads; + bool hideUserlandThreads = settings->hideUserlandThreads; + uint32_t flags = settings->ss->flags; + + unsigned long long now = (unsigned long long)(phost->timestamp * 1000); + int pid = -1, offset = -1; + + /* for every process ... */ + while (Metric_iterate(PCP_PROC_PID, &pid, &offset)) { + + bool preExisting; + Process* proc = ProcessTable_getProcess(pt, pid, &preExisting, PCPProcess_new); + PCPProcess* pp = (PCPProcess*) proc; + PCPProcessTable_updateID(proc, pid, offset); + proc->isUserlandThread = Process_getPid(proc) != Process_getThreadGroup(proc); + pp->offset = offset >= 0 ? offset : 0; + + /* + * These conditions will not trigger on first occurrence, cause we need to + * add the process to the ProcessTable 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->super.updated = true; + proc->super.show = false; + if (proc->state == RUNNING) + pt->runningTasks++; + pt->kernelThreads++; + pt->totalTasks++; + continue; + } + if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) { + proc->super.updated = true; + proc->super.show = false; + if (proc->state == RUNNING) + pt->runningTasks++; + pt->userlandThreads++; + pt->totalTasks++; + continue; + } + + if (flags & PROCESS_FLAG_IO) + PCPProcessTable_updateIO(pp, pid, offset, now); + + PCPProcessTable_updateMemory(pp, pid, offset); + + if ((flags & PROCESS_FLAG_LINUX_SMAPS) && !Process_isKernelThread(proc)) { + if (Metric_enabled(PCP_PROC_SMAPS_PSS)) { + PCPProcessTable_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; + + PCPProcessTable_updateInfo(pp, pid, offset, command, sizeof(command)); + proc->starttime_ctime += Platform_getBootTime(); + if (tty_nr != proc->tty_nr) + PCPProcessTable_updateTTY(proc, pid, offset); + + proc->percent_cpu = NAN; + if (phost->period > 0.0) { + float percent_cpu = saturatingSub(pp->utime + pp->stime, lasttimes) / phost->period * 100.0; + proc->percent_cpu = MINIMUM(percent_cpu, host->activeCPUs * 100.0F); + } + proc->percent_mem = proc->m_resident / (double) host->totalMem * 100.0; + Process_updateCPUFieldWidths(proc->percent_cpu); + + PCPProcessTable_updateUsername(proc, pid, offset, host->usersTable); + + if (!preExisting) { + PCPProcessTable_updateCmdline(proc, pid, offset, command); + Process_fillStarttimeBuffer(proc); + ProcessTable_add(pt, proc); + } else if (settings->updateProcessNames && proc->state != ZOMBIE) { + PCPProcessTable_updateCmdline(proc, pid, offset, command); + } + + if (flags & PROCESS_FLAG_LINUX_CGROUP) + PCPProcessTable_readCGroups(pp, pid, offset); + + if (flags & PROCESS_FLAG_LINUX_OOM) + PCPProcessTable_readOomData(pp, pid, offset); + + if (flags & PROCESS_FLAG_LINUX_CTXT) + PCPProcessTable_readCtxtData(pp, pid, offset); + + if (flags & PROCESS_FLAG_LINUX_SECATTR) + PCPProcessTable_readSecattrData(pp, pid, offset); + + if (flags & PROCESS_FLAG_CWD) + PCPProcessTable_readCwd(pp, pid, offset); + + if (flags & PROCESS_FLAG_LINUX_AUTOGROUP) + PCPProcessTable_readAutogroup(pp, pid, offset); + + 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]) { + Process_updateCmdline(proc, command, 0, strlen(command)); + } + + if (Process_isKernelThread(proc)) { + pt->kernelThreads++; + } else { + pt->userlandThreads++; + } + } + + /* Set at the end when we know if a new entry is a thread */ + proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || + (hideUserlandThreads && Process_isUserlandThread(proc))); + + pt->totalTasks++; + if (proc->state == RUNNING) + pt->runningTasks++; + proc->super.updated = true; + } + return true; +} + +void ProcessTable_goThroughEntries(ProcessTable* super) { + PCPProcessTable* this = (PCPProcessTable*) super; + PCPProcessTable_updateProcesses(this); +} diff --git a/pcp/PCPProcessTable.h b/pcp/PCPProcessTable.h new file mode 100644 index 0000000..e55c85b --- /dev/null +++ b/pcp/PCPProcessTable.h @@ -0,0 +1,24 @@ +#ifndef HEADER_PCPProcessTable +#define HEADER_PCPProcessTable +/* +htop - PCPProcessTable.h +(C) 2014 Hisham H. Muhammad +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include <stdbool.h> +#include <sys/types.h> + +#include "Hashtable.h" +#include "ProcessTable.h" +#include "UsersTable.h" + +#include "pcp/Platform.h" + + +typedef struct PCPProcessTable_ { + ProcessTable super; +} PCPProcessTable; + +#endif diff --git a/pcp/Platform.c b/pcp/Platform.c new file mode 100644 index 0000000..0b5f334 --- /dev/null +++ b/pcp/Platform.c @@ -0,0 +1,924 @@ +/* +htop - linux/Platform.c +(C) 2014 Hisham H. Muhammad +(C) 2020-2022 htop dev team +(C) 2020-2022 Red Hat, Inc. +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "pcp/Platform.h" + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "BatteryMeter.h" +#include "CPUMeter.h" +#include "ClockMeter.h" +#include "DateMeter.h" +#include "DateTimeMeter.h" +#include "DiskIOMeter.h" +#include "DynamicColumn.h" +#include "DynamicMeter.h" +#include "DynamicScreen.h" +#include "FileDescriptorMeter.h" +#include "HostnameMeter.h" +#include "LoadAverageMeter.h" +#include "Macros.h" +#include "MemoryMeter.h" +#include "MemorySwapMeter.h" +#include "Meter.h" +#include "NetworkIOMeter.h" +#include "ProcessTable.h" +#include "Settings.h" +#include "SwapMeter.h" +#include "SysArchMeter.h" +#include "TasksMeter.h" +#include "UptimeMeter.h" +#include "XUtils.h" + +#include "linux/PressureStallMeter.h" +#include "linux/ZramMeter.h" +#include "linux/ZramStats.h" +#include "pcp/Metric.h" +#include "pcp/PCPDynamicColumn.h" +#include "pcp/PCPDynamicMeter.h" +#include "pcp/PCPDynamicScreen.h" +#include "pcp/PCPMachine.h" +#include "pcp/PCPProcessTable.h" +#include "zfs/ZfsArcMeter.h" +#include "zfs/ZfsArcStats.h" +#include "zfs/ZfsCompressedArcMeter.h" + + +Platform* pcp; + +const ScreenDefaults Platform_defaultScreens[] = { + { + .name = "Main", + .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command", + .sortKey = "PERCENT_CPU", + }, + { + .name = "I/O", + .columns = "PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command", + .sortKey = "IO_RATE", + }, +}; + +const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens); + +const SignalItem Platform_signals[] = { + { .name = " 0 Cancel", .number = 0 }, +}; + +const unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals); + +const MeterClass* const Platform_meterTypes[] = { + &CPUMeter_class, + &DynamicMeter_class, + &ClockMeter_class, + &DateMeter_class, + &DateTimeMeter_class, + &LoadAverageMeter_class, + &LoadMeter_class, + &MemoryMeter_class, + &SwapMeter_class, + &MemorySwapMeter_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, + &PressureStallIRQFullMeter_class, + &PressureStallMemorySomeMeter_class, + &PressureStallMemoryFullMeter_class, + &ZfsArcMeter_class, + &ZfsCompressedArcMeter_class, + &ZramMeter_class, + &DiskIOMeter_class, + &NetworkIOMeter_class, + &SysArchMeter_class, + &FileDescriptorMeter_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_UNAME_DISTRO] = "kernel.uname.distro", + [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_SHARED] = "mem.util.shmem", + [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_IRQFULL] = "kernel.all.pressure.irq.full.avg", + [PCP_PSI_MEMSOME] = "kernel.all.pressure.memory.some.avg", + [PCP_PSI_MEMFULL] = "kernel.all.pressure.memory.full.avg", + + [PCP_ZFS_ARC_ANON_SIZE] = "zfs.arc.anon_size", + [PCP_ZFS_ARC_BONUS_SIZE] = "zfs.arc.bonus_size", + [PCP_ZFS_ARC_COMPRESSED_SIZE] = "zfs.arc.compressed_size", + [PCP_ZFS_ARC_UNCOMPRESSED_SIZE] = "zfs.arc.uncompressed_size", + [PCP_ZFS_ARC_C_MIN] = "zfs.arc.c_min", + [PCP_ZFS_ARC_C_MAX] = "zfs.arc.c_max", + [PCP_ZFS_ARC_DBUF_SIZE] = "zfs.arc.dbuf_size", + [PCP_ZFS_ARC_DNODE_SIZE] = "zfs.arc.dnode_size", + [PCP_ZFS_ARC_HDR_SIZE] = "zfs.arc.hdr_size", + [PCP_ZFS_ARC_MFU_SIZE] = "zfs.arc.mfu.size", + [PCP_ZFS_ARC_MRU_SIZE] = "zfs.arc.mru.size", + [PCP_ZFS_ARC_SIZE] = "zfs.arc.size", + + [PCP_ZRAM_CAPACITY] = "zram.capacity", + [PCP_ZRAM_ORIGINAL] = "zram.mm_stat.data_size.original", + [PCP_ZRAM_COMPRESSED] = "zram.mm_stat.data_size.compressed", + [PCP_MEM_ZSWAP] = "mem.util.zswap", + [PCP_MEM_ZSWAPPED] = "mem.util.zswapped", + [PCP_VFS_FILES_COUNT] = "vfs.files.count", + [PCP_VFS_FILES_MAX] = "vfs.files.max", + + [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_EXE] = "proc.psinfo.exe", + [PCP_PROC_CWD] = "proc.psinfo.cwd", + [PCP_PROC_AUTOGROUP_ID] = "proc.autogroup.id", + [PCP_PROC_AUTOGROUP_NICE] = "proc.autogroup.nice", + [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 +}; + +#ifndef HAVE_PMLOOKUPDESCS +/* + * pmLookupDescs(3) exists in latest versions of libpcp (5.3.6+), + * but for older versions we provide an implementation here. This + * involves multiple round trips to pmcd though, which the latest + * libpcp version avoids by using a protocol extension. In time, + * perhaps in a few years, we could remove this back-compat code. + */ +int pmLookupDescs(int numpmid, pmID* pmids, pmDesc* descs) { + int count = 0; + + for (int i = 0; i < numpmid; i++) { + /* expect some metrics to be missing - e.g. PMDA not available */ + if (pmids[i] == PM_ID_NULL) + continue; + + int sts = pmLookupDesc(pmids[i], &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)); + pmids[i] = PM_ID_NULL; + continue; + } + + count++; + } + return count; +} +#endif + +size_t Platform_addMetric(Metric id, const char* name) { + unsigned int i = (unsigned int)id; + + if (i >= PCP_METRIC_COUNT && i >= pcp->totalMetrics) { + /* added via configuration files */ + size_t j = pcp->totalMetrics + 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->totalMetrics; +} + +/* global state from the environment and command line arguments */ +pmOptions opts; + +bool Platform_init(void) { + const char* source; + if (opts.context == PM_CONTEXT_ARCHIVE) { + source = opts.archives[0]; + } else if (opts.context == PM_CONTEXT_HOST) { + source = opts.nhosts > 0 ? opts.hosts[0] : "local:"; + } else { + opts.context = PM_CONTEXT_HOST; + source = "local:"; + } + + int sts; + sts = pmNewContext(opts.context, source); + /* with no host requested, fallback to PM_CONTEXT_LOCAL shared libraries */ + if (sts < 0 && opts.context == PM_CONTEXT_HOST && opts.nhosts == 0) { + opts.context = PM_CONTEXT_LOCAL; + sts = pmNewContext(opts.context, NULL); + } + if (sts < 0) { + fprintf(stderr, "Cannot setup PCP metric source: %s\n", pmErrStr(sts)); + return false; + } + /* setup timezones and other general startup preparation completion */ + if (pmGetContextOptions(sts, &opts) < 0 || opts.errors) { + pmflush(); + return false; + } + + 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)); + + if (opts.context == PM_CONTEXT_ARCHIVE) { + gettimeofday(&pcp->offset, NULL); + pmtimevalDec(&pcp->offset, &opts.start); + } + + for (unsigned int i = 0; i < PCP_METRIC_COUNT; i++) + Platform_addMetric(i, Platform_metricNames[i]); + pcp->meters.offset = PCP_METRIC_COUNT; + + PCPDynamicMeters_init(&pcp->meters); + + pcp->columns.offset = PCP_METRIC_COUNT + pcp->meters.cursor; + PCPDynamicColumns_init(&pcp->columns); + PCPDynamicScreens_init(&pcp->screens, &pcp->columns); + + sts = pmLookupName(pcp->totalMetrics, pcp->names, pcp->pmids); + if (sts < 0) { + fprintf(stderr, "Error: cannot lookup metric names: %s\n", pmErrStr(sts)); + Platform_done(); + return false; + } + + sts = pmLookupDescs(pcp->totalMetrics, pcp->pmids, pcp->descs); + if (sts < 1) { + if (sts < 0) + fprintf(stderr, "Error: cannot lookup descriptors: %s\n", pmErrStr(sts)); + else /* ensure we have at least one valid metric to work with */ + fprintf(stderr, "Error: cannot find a single valid metric, exiting\n"); + Platform_done(); + return false; + } + + /* 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_enable(PCP_UNAME_DISTRO, true); + + /* enable metrics for all dynamic columns (including those from dynamic screens) */ + for (size_t i = pcp->columns.offset; i < pcp->columns.offset + pcp->columns.count; i++) + Metric_enable(i, 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); + Metric_enable(PCP_UNAME_DISTRO, false); + + /* first sample (fetch) performed above, save constants */ + Platform_getBootTime(); + Platform_getRelease(0); + Platform_getMaxCPU(); + Platform_getMaxPid(); + + return true; +} + +void Platform_dynamicColumnsDone(Hashtable* columns) { + PCPDynamicColumns_done(columns); +} + +void Platform_dynamicMetersDone(Hashtable* meters) { + PCPDynamicMeters_done(meters); +} + +void Platform_dynamicScreensDone(Hashtable* screens) { + PCPDynamicScreens_done(screens); +} + +void Platform_done(void) { + pmDestroyContext(pcp->context); + if (pcp->result) + pmFreeResult(pcp->result); + free(pcp->release); + 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; + } +} + +unsigned int Platform_getMaxCPU(void) { + if (pcp->ncpu) + return pcp->ncpu; + + pmAtomValue value; + if (Metric_values(PCP_HINV_NCPU, &value, 1, PM_TYPE_U32) != NULL) + pcp->ncpu = value.ul; + else + pcp->ncpu = 1; + return pcp->ncpu; +} + +pid_t Platform_getMaxPid(void) { + if (pcp->pidmax) + return pcp->pidmax; + + pmAtomValue value; + if (Metric_values(PCP_PID_MAX, &value, 1, PM_TYPE_32) == NULL) + return UINT_MAX; + 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, const Settings* settings, 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 (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; + this->curItems = 5; + + v[CPU_METER_STEAL] = values[CPU_STEAL_PERIOD].ull / total * 100.0; + v[CPU_METER_GUEST] = values[CPU_GUEST_PERIOD].ull / total * 100.0; + if (settings->accountGuestInCPUMeter) { + this->curItems = 7; + } + + v[CPU_METER_IOWAIT] = values[CPU_IOWAIT_PERIOD].ull / total * 100.0; + } else { + v[CPU_METER_KERNEL] = values[CPU_SYSTEM_ALL_PERIOD].ull / total * 100.0; + value = values[CPU_STEAL_PERIOD].ull + values[CPU_GUEST_PERIOD].ull; + v[CPU_METER_IRQ] = value / total * 100.0; + this->curItems = 4; + } + + percent = sumPositiveValues(v, this->curItems); + percent = MINIMUM(percent, 100.0); + + if (settings->detailedCPUTime) { + this->curItems = 8; + } + + v[CPU_METER_FREQUENCY] = values[CPU_FREQUENCY].d; + v[CPU_METER_TEMPERATURE] = NAN; + + return percent; +} + +double Platform_setCPUValues(Meter* this, int cpu) { + const PCPMachine* phost = (const PCPMachine*) this->host; + const Settings* settings = this->host->settings; + + if (cpu <= 0) /* use aggregate values */ + return Platform_setOneCPUValues(this, settings, phost->cpu); + return Platform_setOneCPUValues(this, settings, phost->percpu[cpu - 1]); +} + +void Platform_setMemoryValues(Meter* this) { + const Machine* host = this->host; + const PCPMachine* phost = (const PCPMachine*) host; + + this->total = host->totalMem; + this->values[MEMORY_METER_USED] = host->usedMem; + this->values[MEMORY_METER_SHARED] = host->sharedMem; + this->values[MEMORY_METER_COMPRESSED] = 0; + this->values[MEMORY_METER_BUFFERS] = host->buffersMem; + this->values[MEMORY_METER_CACHE] = host->cachedMem; + this->values[MEMORY_METER_AVAILABLE] = host->availableMem; + + if (phost->zfs.enabled != 0) { + // ZFS does not shrink below the value of zfs_arc_min. + unsigned long long int shrinkableSize = 0; + if (phost->zfs.size > phost->zfs.min) + shrinkableSize = phost->zfs.size - phost->zfs.min; + this->values[MEMORY_METER_USED] -= shrinkableSize; + this->values[MEMORY_METER_CACHE] += shrinkableSize; + this->values[MEMORY_METER_AVAILABLE] += shrinkableSize; + } + + if (phost->zswap.usedZswapOrig > 0 || phost->zswap.usedZswapComp > 0) { + this->values[MEMORY_METER_USED] -= phost->zswap.usedZswapComp; + this->values[MEMORY_METER_COMPRESSED] += phost->zswap.usedZswapComp; + } +} + +void Platform_setSwapValues(Meter* this) { + const Machine* host = this->host; + const PCPMachine* phost = (const PCPMachine*) host; + + this->total = host->totalSwap; + this->values[SWAP_METER_USED] = host->usedSwap; + this->values[SWAP_METER_CACHE] = host->cachedSwap; + this->values[SWAP_METER_FRONTSWAP] = 0; /* frontswap -- memory that is accounted to swap but resides elsewhere */ + + if (phost->zswap.usedZswapOrig > 0 || phost->zswap.usedZswapComp > 0) { + /* refer to linux/Platform.c::Platform_setSwapValues for details */ + this->values[SWAP_METER_USED] -= phost->zswap.usedZswapOrig; + if (this->values[SWAP_METER_USED] < 0) { + /* subtract the overflow from SwapCached */ + this->values[SWAP_METER_CACHE] += this->values[SWAP_METER_USED]; + this->values[SWAP_METER_USED] = 0; + } + this->values[SWAP_METER_FRONTSWAP] += phost->zswap.usedZswapOrig; + } +} + +void Platform_setZramValues(Meter* this) { + int i, count = Metric_instanceCount(PCP_ZRAM_CAPACITY); + if (!count) { + this->total = 0; + this->values[0] = 0; + this->values[1] = 0; + return; + } + + 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); + + if (stats.usedZramComp > stats.usedZramOrig) { + stats.usedZramComp = stats.usedZramOrig; + } + + this->total = stats.totalZram; + this->values[0] = stats.usedZramComp; + this->values[1] = stats.usedZramOrig - stats.usedZramComp; +} + +void Platform_setZfsArcValues(Meter* this) { + const PCPMachine* phost = (const PCPMachine*) this->host; + + ZfsArcMeter_readStats(this, &phost->zfs); +} + +void Platform_setZfsCompressedArcValues(Meter* this) { + const PCPMachine* phost = (const PCPMachine*) this->host; + + ZfsCompressedArcMeter_readStats(this, &phost->zfs); +} + +void Platform_getHostname(char* buffer, size_t size) { + const char* hostname = pmGetContextHostName(pcp->context); + String_safeStrncpy(buffer, hostname, size); +} + +void Platform_getRelease(char** string) { + /* fast-path - previously-formatted string */ + if (string) { + *string = pcp->release; + return; + } + + /* first call, extract just-sampled values */ + pmAtomValue sysname, release, machine, distro; + if (!Metric_values(PCP_UNAME_SYSNAME, &sysname, 1, PM_TYPE_STRING)) + sysname.cp = NULL; + if (!Metric_values(PCP_UNAME_RELEASE, &release, 1, PM_TYPE_STRING)) + release.cp = NULL; + if (!Metric_values(PCP_UNAME_MACHINE, &machine, 1, PM_TYPE_STRING)) + machine.cp = NULL; + if (!Metric_values(PCP_UNAME_DISTRO, &distro, 1, PM_TYPE_STRING)) + distro.cp = NULL; + + size_t length = 16; /* padded for formatting characters */ + if (sysname.cp) + length += strlen(sysname.cp); + if (release.cp) + length += strlen(release.cp); + if (machine.cp) + length += strlen(machine.cp); + if (distro.cp) + length += strlen(distro.cp); + pcp->release = xCalloc(1, length); + + if (sysname.cp) { + strcat(pcp->release, sysname.cp); + strcat(pcp->release, " "); + } + if (release.cp) { + strcat(pcp->release, release.cp); + strcat(pcp->release, " "); + } + if (machine.cp) { + strcat(pcp->release, "["); + strcat(pcp->release, machine.cp); + strcat(pcp->release, "] "); + } + if (distro.cp) { + if (pcp->release[0] != '\0') { + strcat(pcp->release, "@ "); + strcat(pcp->release, distro.cp); + } else { + strcat(pcp->release, distro.cp); + } + strcat(pcp->release, " "); + } + + if (pcp->release) /* cull trailing space */ + pcp->release[strlen(pcp->release)] = '\0'; + + free(distro.cp); + free(machine.cp); + free(release.cp); + free(sysname.cp); +} + +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; +} + +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, "irq")) + metric = PCP_PSI_IRQFULL; + 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) { + memset(data, 0, sizeof(*data)); + + 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(NetworkIOData* data) { + memset(data, 0, sizeof(*data)); + + pmAtomValue value; + if (Metric_values(PCP_NET_RECVB, &value, 1, PM_TYPE_U64) != NULL) + data->bytesReceived = value.ull; + if (Metric_values(PCP_NET_SENDB, &value, 1, PM_TYPE_U64) != NULL) + data->bytesTransmitted = value.ull; + if (Metric_values(PCP_NET_RECVP, &value, 1, PM_TYPE_U64) != NULL) + data->packetsReceived = value.ull; + if (Metric_values(PCP_NET_SENDP, &value, 1, PM_TYPE_U64) != NULL) + data->packetsTransmitted = value.ull; + return true; +} + +void Platform_getFileDescriptors(double* used, double* max) { + *used = NAN; + *max = 65536; + + pmAtomValue value; + if (Metric_values(PCP_VFS_FILES_COUNT, &value, 1, PM_TYPE_32) != NULL) + *used = value.l; + if (Metric_values(PCP_VFS_FILES_MAX, &value, 1, PM_TYPE_32) != NULL) + *max = value.l; +} + +void Platform_getBattery(double* level, ACPresence* isOnAC) { + *level = NAN; + *isOnAC = AC_ERROR; +} + +void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { + printf( +" --host=HOSTSPEC metrics source is PMCD at HOSTSPEC [see PCPIntro(1)]\n" +" --hostzone set reporting timezone to local time of metrics source\n" +" --timezone=TZ set reporting timezone\n"); +} + +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 STATUS_ERROR_EXIT; + __pmAddOptHost(&opts, optarg); + return STATUS_OK; + + case PLATFORM_LONGOPT_HOSTZONE: /* --hostzone */ + if (opts.timezone) { + pmprintf("%s: at most one of -Z and -z allowed\n", pmGetProgname()); + opts.errors++; + } else { + opts.tzflag = 1; + } + return STATUS_OK; + + case PLATFORM_LONGOPT_TIMEZONE: /* --timezone=TZ */ + if (argv[optind][0] == '\0') + 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 STATUS_OK; + + default: + break; + } + + return STATUS_ERROR_EXIT; +} + +void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) { + if (gettimeofday(tv, NULL) == 0) { + /* shift by start offset to stay in lock-step with realtime (archives) */ + if (pcp->offset.tv_sec || pcp->offset.tv_usec) + pmtimevalDec(tv, &pcp->offset); + *msec = ((uint64_t)tv->tv_sec * 1000) + ((uint64_t)tv->tv_usec / 1000); + } else { + memset(tv, 0, sizeof(struct timeval)); + *msec = 0; + } +} + +void Platform_gettime_monotonic(uint64_t* msec) { + if (pcp->result) { + struct timeval* tv = &pcp->result->timestamp; + *msec = ((uint64_t)tv->tv_sec * 1000) + ((uint64_t)tv->tv_usec / 1000); + } else { + *msec = 0; + } +} + +Hashtable* Platform_dynamicMeters(void) { + return pcp->meters.table; +} + +void Platform_dynamicMeterInit(Meter* meter) { + PCPDynamicMeter* this = Hashtable_get(pcp->meters.table, meter->param); + if (this) + PCPDynamicMeter_enable(this); +} + +void Platform_dynamicMeterUpdateValues(Meter* meter) { + PCPDynamicMeter* this = Hashtable_get(pcp->meters.table, meter->param); + if (this) + PCPDynamicMeter_updateValues(this, meter); +} + +void Platform_dynamicMeterDisplay(const Meter* meter, RichString* out) { + PCPDynamicMeter* this = Hashtable_get(pcp->meters.table, meter->param); + if (this) + PCPDynamicMeter_display(this, meter, out); +} + +Hashtable* Platform_dynamicColumns(void) { + return pcp->columns.table; +} + +const char* Platform_dynamicColumnName(unsigned int key) { + PCPDynamicColumn* this = Hashtable_get(pcp->columns.table, key); + if (this) { + Metric_enable(this->id, true); + if (this->super.caption) + return this->super.caption; + if (this->super.heading) + return this->super.heading; + return this->super.name; + } + return NULL; +} + +bool Platform_dynamicColumnWriteField(const Process* proc, RichString* str, unsigned int key) { + PCPDynamicColumn* this = Hashtable_get(pcp->columns.table, key); + if (this) { + PCPDynamicColumn_writeField(this, proc, str); + return true; + } + return false; +} + +Hashtable* Platform_dynamicScreens(void) { + return pcp->screens.table; +} + +void Platform_defaultDynamicScreens(Settings* settings) { + PCPDynamicScreen_appendScreens(&pcp->screens, settings); +} + +void Platform_addDynamicScreen(ScreenSettings* ss) { + PCPDynamicScreen_addDynamicScreen(&pcp->screens, ss); +} + +void Platform_addDynamicScreenAvailableColumns(Panel* availableColumns, const char* screen) { + Hashtable* screens = pcp->screens.table; + PCPDynamicScreens_addAvailableColumns(availableColumns, screens, screen); +} + +void Platform_updateTables(Machine* host) { + PCPDynamicScreen_appendTables(&pcp->screens, host); + PCPDynamicColumns_setupWidths(&pcp->columns); +} diff --git a/pcp/Platform.h b/pcp/Platform.h new file mode 100644 index 0000000..f43ed54 --- /dev/null +++ b/pcp/Platform.h @@ -0,0 +1,172 @@ +#ifndef HEADER_Platform +#define HEADER_Platform +/* +htop - pcp/Platform.h +(C) 2014 Hisham H. Muhammad +(C) 2020-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. +*/ + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <pcp/pmapi.h> +#include <sys/time.h> +#include <sys/types.h> + +/* use htop config.h values for these macros, not pcp values */ +#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 "Hashtable.h" +#include "Meter.h" +#include "NetworkIOMeter.h" +#include "Process.h" +#include "ProcessLocksScreen.h" +#include "RichString.h" +#include "SignalsPanel.h" +#include "CommandLine.h" + +#include "pcp/Metric.h" +#include "pcp/PCPDynamicColumn.h" +#include "pcp/PCPDynamicMeter.h" +#include "pcp/PCPDynamicScreen.h" + + +typedef struct Platform_ { + int context; /* PMAPI(3) context identifier */ + size_t totalMetrics; /* 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 */ + PCPDynamicMeters meters; /* dynamic meters via configuration files */ + PCPDynamicColumns columns; /* dynamic columns via configuration files */ + PCPDynamicScreens screens; /* dynamic screens via configuration files */ + struct timeval offset; /* time offset used in archive mode only */ + long long btime; /* boottime in seconds since the epoch */ + char* release; /* uname and distro from this context */ + int pidmax; /* maximum platform process identifier */ + unsigned int ncpu; /* maximum processor count configured */ +} Platform; + +extern const ScreenDefaults Platform_defaultScreens[]; + +extern const unsigned int Platform_numberOfDefaultScreens; + +extern const SignalItem Platform_signals[]; + +extern const unsigned int Platform_numberOfSignals; + +extern const MeterClass* const Platform_meterTypes[]; + +bool 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); + +unsigned int Platform_getMaxCPU(void); + +pid_t 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); + +void Platform_setZfsArcValues(Meter* this); + +void Platform_setZfsCompressedArcValues(Meter* this); + +char* Platform_getProcessEnv(pid_t pid); + +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(NetworkIOData* data); + +void Platform_getBattery(double* percent, ACPresence* isOnAC); + +void Platform_getHostname(char* buffer, size_t size); + +void Platform_getRelease(char** string); + +enum { + PLATFORM_LONGOPT_HOST = 128, + PLATFORM_LONGOPT_TIMEZONE, + PLATFORM_LONGOPT_HOSTZONE, +}; + +#define PLATFORM_LONG_OPTIONS \ + {PMLONGOPT_HOST, optional_argument, 0, PLATFORM_LONGOPT_HOST}, \ + {PMLONGOPT_TIMEZONE, optional_argument, 0, PLATFORM_LONGOPT_TIMEZONE}, \ + {PMLONGOPT_HOSTZONE, optional_argument, 0, PLATFORM_LONGOPT_HOSTZONE}, \ + +void Platform_longOptionsUsage(const char* name); + +CommandLineStatus Platform_getLongOption(int opt, int argc, char** argv); + +extern pmOptions opts; + +size_t Platform_addMetric(Metric id, const char* name); + +void Platform_getFileDescriptors(double* used, double* max); + +void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec); + +void Platform_gettime_monotonic(uint64_t* msec); + +Hashtable* Platform_dynamicMeters(void); + +void Platform_dynamicMetersDone(Hashtable* meters); + +void Platform_dynamicMeterInit(Meter* meter); + +void Platform_dynamicMeterUpdateValues(Meter* meter); + +void Platform_dynamicMeterDisplay(const Meter* meter, RichString* out); + +Hashtable* Platform_dynamicColumns(void); + +void Platform_dynamicColumnsDone(Hashtable* columns); + +const char* Platform_dynamicColumnName(unsigned int key); + +bool Platform_dynamicColumnWriteField(const Process* proc, RichString* str, unsigned int key); + +Hashtable* Platform_dynamicScreens(void); + +void Platform_defaultDynamicScreens(Settings* settings); + +void Platform_addDynamicScreen(ScreenSettings* ss); + +void Platform_addDynamicScreenAvailableColumns(Panel* availableColumns, const char* screen); + +void Platform_dynamicScreensDone(Hashtable* screens); + +void Platform_updateTables(Machine* host); + +#endif diff --git a/pcp/ProcessField.h b/pcp/ProcessField.h new file mode 100644 index 0000000..6342561 --- /dev/null +++ b/pcp/ProcessField.h @@ -0,0 +1,54 @@ +#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, \ + AUTOGROUP_ID = 127, \ + AUTOGROUP_NICE = 128, \ + CCGROUP = 129, \ + CONTAINER = 130, \ + M_PRIV = 131, \ + // End of list + + +#endif /* HEADER_PCPProcessField */ diff --git a/pcp/columns/container b/pcp/columns/container new file mode 100644 index 0000000..519288f --- /dev/null +++ b/pcp/columns/container @@ -0,0 +1,10 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[container] +heading = Container +caption = CONTAINER +width = -12 +metric = proc.id.container +description = Name of processes container via cgroup heuristics diff --git a/pcp/columns/delayacct b/pcp/columns/delayacct new file mode 100644 index 0000000..016904c --- /dev/null +++ b/pcp/columns/delayacct @@ -0,0 +1,10 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[blkio] +heading = BLKIOD +caption = BLKIO_TIME +width = 6 +metric = proc.psinfo.delayacct_blkio_time +description = Aggregated block I/O delays diff --git a/pcp/columns/fdcount b/pcp/columns/fdcount new file mode 100644 index 0000000..e679480 --- /dev/null +++ b/pcp/columns/fdcount @@ -0,0 +1,10 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[fds] +heading = FDS +caption = FDCOUNT +width = 4 +metric = proc.fd.count +description = Open file descriptors diff --git a/pcp/columns/guest b/pcp/columns/guest new file mode 100644 index 0000000..89bb926 --- /dev/null +++ b/pcp/columns/guest @@ -0,0 +1,17 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[guest] +heading = GUEST +caption = GUEST_TIME +width = 6 +metric = proc.psinfo.guest_time +description = Guest time for the process + +[cguest] +heading = CGUEST +caption = CGUEST_TIME +width = 6 +metric = proc.psinfo.guest_time + proc.psinfo.cguest_time +description = Cumulative guest time for the process and its children diff --git a/pcp/columns/memory b/pcp/columns/memory new file mode 100644 index 0000000..305a654 --- /dev/null +++ b/pcp/columns/memory @@ -0,0 +1,39 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[vmdata] +heading = VDATA +width = 6 +metric = proc.memory.vmdata +description = Virtual memory used for data + +[vmstack] +heading = VSTACK +width = -6 +metric = proc.memory.vmstack +description = Virtual memory used for stack + +[vmexe] +heading = VEXEC +width = 6 +metric = proc.memory.vmexe +description = Virtual memory used for non-library executable code + +[vmlib] +heading = VLIBS +width = 6 +metric = proc.memory.vmlib +description = Virtual memory used for libraries + +[vmswap] +heading = VSWAP +width = 6 +metric = proc.memory.vmswap +description = Virtual memory size currently swapped out + +[vmlock] +heading = VLOCK +width = 6 +metric = proc.memory.vmlock +description = Locked virtual memory diff --git a/pcp/columns/sched b/pcp/columns/sched new file mode 100644 index 0000000..36b8b55 --- /dev/null +++ b/pcp/columns/sched @@ -0,0 +1,10 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[rundelay] +heading = RUNQ +caption = RUN_DELAY +width = 4 +metric = proc.schedstat.run_delay +description = Run queue time diff --git a/pcp/columns/swap b/pcp/columns/swap new file mode 100644 index 0000000..234b3db --- /dev/null +++ b/pcp/columns/swap @@ -0,0 +1,15 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[swap] +heading = SWAP +width = 5 +metric = proc.psinfo.nswap +description = Count of swap operations for the process + +[cswap] +heading = CSWAP +width = 5 +metric = proc.psinfo.nswap + proc.psinfo.cnswap +description = Cumulative swap operations for the process and its children diff --git a/pcp/columns/tcp b/pcp/columns/tcp new file mode 100644 index 0000000..f9a1819 --- /dev/null +++ b/pcp/columns/tcp @@ -0,0 +1,31 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[tcp_send_calls] +heading = TCPS +caption = TCP_SEND +width = 6 +metric = bcc.proc.net.tcp.send.calls +description = Count of TCP send calls + +[tcp_send_bytes] +heading = TCPSB +caption = TCP_SEND_BYTES +width = 6 +metric = bcc.proc.net.tcp.send.bytes +description = Cumulative bytes sent via TCP + +[tcp_recv_calls] +heading = TCPR +caption = TCP_RECV +width = 6 +metric = bcc.proc.net.tcp.recv.calls +description = Count of TCP recv calls + +[tcp_recv_bytes] +heading = TCPRB +caption = TCP_RECV_BYTES +width = 6 +metric = bcc.proc.net.tcp.recv.bytes +description = Cumulative bytes received via TCP diff --git a/pcp/columns/udp b/pcp/columns/udp new file mode 100644 index 0000000..060f048 --- /dev/null +++ b/pcp/columns/udp @@ -0,0 +1,31 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[udp_send_calls] +heading = UDPS +caption = UDP_SEND +width = 6 +metric = bcc.proc.net.udp.send.calls +description = Count of UDP send calls + +[udp_send_bytes] +heading = UDPSB +caption = UDP_SEND_BYTES +width = 6 +metric = bcc.proc.net.udp.send.bytes +description = Cumulative bytes sent via UDP + +[udp_recv_calls] +heading = UDPR +caption = UDP_RECV +width = 6 +metric = bcc.proc.net.udp.recv.calls +description = Count of UDP recv calls + +[udp_recv_bytes] +heading = UDPRB +caption = UDP_RECV_BYTES +width = 6 +metric = bcc.proc.net.udp.recv.bytes +description = Cumulative bytes received via UDP diff --git a/pcp/columns/wchan b/pcp/columns/wchan new file mode 100644 index 0000000..893de58 --- /dev/null +++ b/pcp/columns/wchan @@ -0,0 +1,17 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[wchan] +heading = WCHAN +caption = WCHAN_ADDRESS +width = 8 +metric = proc.psinfo.wchan +description = Wait channel, kernel address process is blocked or sleeping on + +[wchans] +heading = WCHANS +caption = WCHAN_SYMBOL +width = -12 +metric = proc.psinfo.wchan_s +description = Wait channel, kernel symbol process is blocked or sleeping on diff --git a/pcp/meters/entropy b/pcp/meters/entropy new file mode 100644 index 0000000..0bef0cf --- /dev/null +++ b/pcp/meters/entropy @@ -0,0 +1,9 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[entropy] +caption = Entropy +avail.metric = kernel.all.entropy.avail / kernel.all.entropy.poolsize * 100 +avail.label = avail +avail.suffix = % diff --git a/pcp/meters/freespace b/pcp/meters/freespace new file mode 100644 index 0000000..074af6d --- /dev/null +++ b/pcp/meters/freespace @@ -0,0 +1,11 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[freespace] +caption = Freespace +description = Filesystem space +used.metric = sum(filesys.used) +used.color = blue +free.metric = sum(filesys.free) +free.color = green diff --git a/pcp/meters/ipc b/pcp/meters/ipc new file mode 100644 index 0000000..4162f39 --- /dev/null +++ b/pcp/meters/ipc @@ -0,0 +1,13 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[ipc] +caption = SysV IPC +description = SysV IPC counts +msg.metric = ipc.msg.used_queues +msg.color = blue +sem.metric = ipc.sem.used_sem +sem.color = green +shm.metric = ipc.shm.used_ids +shm.color = cyan diff --git a/pcp/meters/locks b/pcp/meters/locks new file mode 100644 index 0000000..5d09510 --- /dev/null +++ b/pcp/meters/locks @@ -0,0 +1,15 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[locks] +caption = File locks +description = VFS file locks +posix.metric = vfs.locks.posix.count +posix.color = blue +flock.metric = vfs.locks.flock.count +flock.color = green +readlock.metric = vfs.locks.posix.read + vfs.locks.flock.read +readlock.color = red +writelock.metric = vfs.locks.posix.write + vfs.locks.flock.write +writelock.color = yellow diff --git a/pcp/meters/memcache b/pcp/meters/memcache new file mode 100644 index 0000000..0f2fac2 --- /dev/null +++ b/pcp/meters/memcache @@ -0,0 +1,11 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[memcache] +caption = Memcache +description = Memcache Hits +hit.metric = sum(memcache.hits) +hit.color = green +miss.metric = sum(memcache.misses) +miss.color = blue diff --git a/pcp/meters/mysql b/pcp/meters/mysql new file mode 100644 index 0000000..a9e75e4 --- /dev/null +++ b/pcp/meters/mysql @@ -0,0 +1,71 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[mysql_io] +caption = MySQL I/O +recv.metric = mysql.status.bytes_received +recv.color = green +sent.metric = mysql.status.bytes_sent +sent.color = blue + +[mysql_keys] +caption = MySQL keys +description = MySQL key status +key_blocks_used.metric = mysql.status.key_blocks_used +key_blocks_used.label = color +key_blocks_used.label = used +key_reads.metric = mysql.status.key_reads +key_reads.label = read +key_reads.color = green +key_writes.metric = mysql.status.key_writes +key_writes.label = writ +key_writes.color = blue +key_read_requests.metric = mysql.status.key_read_requests +key_read_requests.label = rreq +key_read_requests.color = green +key_write_requests.metric = mysql.status.key_write_requests +key_write_requests.label = wreq +key_write_requests.color = blue + +[innodb_buffer] +caption = InnoDB pool +description = InnoDB buffer pool +created.metric = mysql.status.innodb_pages_created +created.label = cr +created.color = yellow +read.metric = mysql.status.innodb_pages_read +read.label = rd +read.color = greed +written.metric = mysql.status.innodb_pages_written +written.label = wr +written.color = red + +[innodb_io] +caption = InnoDB I/O +description = InnoDB I/O operations +read.metric = mysql.status.innodb_data_read +read.label = rd +read.color = green +written.metric = mysql.status.innodb_data.writes +written.label = wr +written.color = blue +sync.metric = mysql.status.innodb_data_fsyncs +sync.label = sync +sync.color = cyan + +[innodb_ops] +caption = InnoDB ops +description = InnoDB operations +inserted.metric = mysql.status.innodb_rows_inserted +inserted.label = ins +inserted.color = blue +updated.metric = mysql.status.innodb_rows_updated +updated.label = upd +updated.color = cyan +deleted.metric = mysql.status.innodb_rows_deleted +deleted.label = del +deleted.color = red +read.metric = mysql.status.innodb_rows_read +read.label = rd +read.color = green diff --git a/pcp/meters/postfix b/pcp/meters/postfix new file mode 100644 index 0000000..cda6837 --- /dev/null +++ b/pcp/meters/postfix @@ -0,0 +1,20 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[postfix] +caption = Postfix +incoming.metric = sum(postfix.queues.incoming) +incoming.color = green +incoming.label = in +active.metric = sum(postfix.queues.active) +active.color = blue +active.label = act +deferred.metric = sum(postfix.queues.deferred) +deferred.color = cyan +deferred.label = dfr +bounce.metric = sum(postfix.queues.maildrop) +bounce.color = red +bounce.label = bnc +hold.metric = sum(postfix.queues.hold) +hold.color = yellow diff --git a/pcp/meters/redis b/pcp/meters/redis new file mode 100644 index 0000000..a72f727 --- /dev/null +++ b/pcp/meters/redis @@ -0,0 +1,39 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[redisxact] +caption = Redis xact +description = Redis transactions +tps.metric = redis.instantaneous_ops_per_sec +tps.color = green + +[redismem] +caption = Redis mem +description = Redis memory +lua.metric = redis.used_memory_lua +lua.color = magenta +used.metric = redis.used_memory +used.color = blue + +[redisclient] +caption = Redis clients +description = Redis clients +type = bar +blocked.metric = redis.blocked_clients +blocked.color = blue +blocked.label = blk +clients.metric = redis.connected_clients +clients.color = green +clients.label = conn + +[redisconn] +caption = Redis conn +description = Redis connections +type = bar +reject.metric = redis.rejected_connections +reject.color = magenta +reject.label = fail/s +total.metric = redis.total_connections_received +total.color = blue +total.label = conn/s diff --git a/pcp/meters/tcp b/pcp/meters/tcp new file mode 100644 index 0000000..c95736f --- /dev/null +++ b/pcp/meters/tcp @@ -0,0 +1,21 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[tcp] +caption = TCP +description = TCP sockets +listen.metric = network.tcpconn.listen +listen.color = green +listen.label = lis +active.metric = network.tcpconn.established +active.color = blue +active.label = act +syn.metric = network.tcpconn.syn_sent + network.tcpconn.syn_recv + network.tcpconn.last_ack +syn.color = cyan +wait.metric = network.tcpconn.time_wait +wait.color = red +wait.label = tim +close.metric = network.tcpconn.fin_wait1 + network.tcpconn.fin_wait2 + network.tcpconn.close + network.tcpconn.close_wait + network.tcpconn.closing +close.color = yellow +close.label = clo diff --git a/pcp/screens/biosnoop b/pcp/screens/biosnoop new file mode 100644 index 0000000..e6cdf89 --- /dev/null +++ b/pcp/screens/biosnoop @@ -0,0 +1,41 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[biosnoop] +heading = BioSnoop +caption = BPF block I/O snoop +default = false + +pid.heading = PID +pid.caption = Process identifier +pid.metric = bpf.biosnoop.pid +pid.format = process + +disk.heading = DISK +disk.caption = Device name +disk.width = -7 +disk.metric = bpf.biosnoop.disk + +rwbs.heading = TYPE +rwbs.caption = I/O type string +rwbs.width = -4 +rwbs.metric = bpf.biosnoop.rwbs + +bytes.heading = BYTES +bytes.caption = I/O size in bytes +bytes.metric = bpf.biosnoop.bytes + +lat.heading = LAT +lat.caption = I/O latency +lat.metric = bpf.biosnoop.lat + +sector.heading = SECTOR +sector.caption = Device sector +sector.metric = bpf.biosnoop.sector + +comm.heading = Command +comm.caption = Process command name +comm.width = -16 +comm.metric = bpf.biosnoop.comm +comm.format = process diff --git a/pcp/screens/cgroups b/pcp/screens/cgroups new file mode 100644 index 0000000..0ddc65c --- /dev/null +++ b/pcp/screens/cgroups @@ -0,0 +1,45 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[cgroups] +heading = CGroups +caption = Control Groups +default = true + +user_cpu.heading = UTIME +user_cpu.caption = User CPU Time +user_cpu.metric = 1000 * rate(cgroup.cpu.stat.user) +user_cpu.width = 6 + +system_cpu.heading = STIME +system_cpu.caption = Kernel CPU Time +system_cpu.metric = 1000 * rate(cgroup.cpu.stat.system) +system_cpu.width = 6 + +cpu_usage.heading = CPU% +cpu_usage.caption = CPU Utilization +cpu_usage.metric = 100 * (rate(cgroup.cpu.stat.usage) / hinv.ncpu) +cpu_usage.format = percent + +cpu_psi.heading = CPU-PSI +cpu_psi.caption = CPU Pressure Stall Information +cpu_psi.metric = 1000 * rate(cgroup.pressure.cpu.some.total) +cpu_psi.width = 7 + +mem_psi.heading = MEM-PSI +mem_psi.caption = Memory Pressure Stall Information +mem_psi.metric = 1000 * rate(cgroup.pressure.memory.some.total) +mem_psi.width = 7 + +io_psi.heading = I/O-PSI +io_psi.caption = I/O Pressure Stall Information +io_psi.metric = 1000 * rate(cgroup.pressure.io.some.total) +io_psi.width = 7 + +name.heading = Control group +name.caption = Control group name +name.width = -64 +name.metric = cgroup.cpu.stat.system +name.instances = true +name.format = cgroup diff --git a/pcp/screens/cgroupsio b/pcp/screens/cgroupsio new file mode 100644 index 0000000..3a431db --- /dev/null +++ b/pcp/screens/cgroupsio @@ -0,0 +1,49 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[cgroupsio] +heading = CGroupsIO +caption = Control Groups I/O +default = false + +iops.heading = IOPS +iops.caption = I/O operations +iops.metric = rate(cgroup.io.stat.rios) + rate(cgroup.io.stat.wios) + rate(cgroup.io.stat.dios) + +readops.heading = RDIO +readops.caption = Read operations +readops.metric = rate(cgroup.io.stat.rios) +readops.default = false + +writeops.heading = WRIO +writeops.caption = Write operations +writeops.metric = rate(cgroup.io.stat.wios) +writeops.default = false + +directops.heading = DIO +directops.caption = Direct I/O operations +directops.metric = rate(cgroup.io.stat.dios) +directops.default = false + +totalbytes.heading = R/W/D +totalbytes.caption = Disk throughput +totalbytes.metric = rate(cgroup.io.stat.rbytes) + rate(cgroup.io.stat.wbytes) + rate(cgroup.io.stat.dbytes) + +readbytes.heading = RBYTE +readbytes.caption = Disk read throughput +readbytes.metric = rate(cgroup.io.stat.rbytes) + +writebytes.heading = WBYTE +writebytes.caption = Disk throughput +writebytes.metric = rate(cgroup.io.stat.wbytes) + +directio.heading = DBYTE +directio.caption = Direct I/O throughput +directio.metric = rate(cgroup.io.stat.dbytes) + +name.heading = Control group device +name.caption = Control group device +name.width = -64 +name.metric = cgroup.io.stat.rbytes +name.instances = true diff --git a/pcp/screens/cgroupsmem b/pcp/screens/cgroupsmem new file mode 100644 index 0000000..17bc1e3 --- /dev/null +++ b/pcp/screens/cgroupsmem @@ -0,0 +1,48 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[cgroupsmem] +heading = CGroupsMem +caption = Control Groups Memory +default = false + +current.heading = MEM +current.caption = Current memory +current.metric = cgroup.memory.current + +usage.heading = USAGE +usage.caption = Memory usage +usage.metric = cgroup.memory.usage + +container.heading = CONTAINER +container.caption = Container Name +container.metric = cgroup.memory.id.container + +resident.heading = RSS +resident.metric = cgroup.memory.stat.rss + +cresident.heading = CRSS +cresident.metric = cgroup.memory.stat.total.rss + +anonmem.heading = ANON +anonmem.metric = cgroup.memory.stat.anon + +filemem.heading = FILE +filemem.metric = cgroup.memory.stat.file + +shared.heading = SHMEM +shared.metric = cgroup.memory.stat.shmem + +swap.heading = SWAP +swap.metric = cgroup.memory.stat.swap + +pgfault.heading = FAULTS +pgfault.metric = cgroup.memory.stat.pgfaults + +name.heading = Control group +name.caption = Control group name +name.width = -64 +name.metric = cgroup.memory.current +name.instances = true +name.format = cgroup diff --git a/pcp/screens/devices b/pcp/screens/devices new file mode 100644 index 0000000..7399f82 --- /dev/null +++ b/pcp/screens/devices @@ -0,0 +1,114 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[disks] +heading = Disks +caption = Disk devices + +diskdev.heading = Device +diskdev.metric = disk.dev.read +diskdev.instances = true +diskdev.format = device +diskdev.width = -8 + +total.heading = TPS +total.metric = rate(disk.dev.read) + rate(disk.dev.write) + rate(disk.dev.discard) +total.caption = Rate of read requests + +read.heading = RR/S +read.metric = rate(disk.dev.read) +read.caption = Rate of read requests + +read_bytes.heading = RRB/S +read_bytes.metric = rate(disk.dev.read_bytes) +read_bytes.caption = Read throughput from the device + +read_merge.heading = RRQM/S +read_merge.metric = rate(disk.dev.read_merge) +read_merge.caption = Rate reads merged before queued +read_merge.default = false + +read_merge_pct.heading = RRQM% +read_merge_pct.metric = 100 * rate(disk.dev.read_merge) / rate(disk.dev.read) +read_merge_pct.caption = Percentage reads merged before queued +read_merge_pct.format = percent + +read_await.heading = RAWAIT +read_await.metric = disk.dev.r_await +read_await.default = false + +read_avqsz.heading = RARQSZ +read_avqsz.metric = disk.dev.r_avg_rqsz +read_avqsz.default = false + +write.heading = WR/S +write.metric = rate(disk.dev.write) +write.caption = Rate of write requests + +write_bytes.heading = WRB/S +write_bytes.metric = rate(disk.dev.write_bytes) +write_bytes.caption = Write throughput to the device + +write_merge.heading = WRQM/S +write_merge.metric = rate(disk.dev.write_merge) +write_merge.caption = Rate writes merged before queued +write_merge.default = false + +write_merge_pct.heading = WRQM% +write_merge_pct.metric = 100 * rate(disk.dev.write_merge) / rate(disk.dev.write) +write_merge_pct.caption = Percentage writes merged before queued +write_merge_pct.format = percent + +write_await.heading = WAWAIT +write_await.metric = disk.dev.w_await +write_await.default = false + +write_avqsz.heading = WARQSZ +write_avqsz.metric = disk.dev.w_avg_rqsz +write_avqsz.default = false + +discard.heading = DR/S +discard.metric = rate(disk.dev.discard) +discard.caption = Rate of discard requests + +discard_bytes.heading = DRB/S +discard_bytes.metric = rate(disk.dev.discard_bytes) +discard_bytes.caption = Discard request throughput +discard_bytes.default = false + +discard_merge.heading = DRQM/S +discard_merge.metric = rate(disk.dev.discard_merge) +discard_merge.caption = Rate discards merged before queued +discard_merge.default = false + +discard_merge_pct.heading = DRQM% +discard_merge_pct.metric = 100 * rate(disk.dev.discard_merge) / rate(disk.dev.discard) +discard_merge_pct.caption = Percentage discards merged before queued +discard_merge_pct.format = percent +discard_merge_pct.default = false + +discard_await.heading = DAWAIT +discard_await.metric = disk.dev.d_await +discard_await.default = false + +discard_avqsz.heading = DARQSZ +discard_avqsz.metric = disk.dev.d_avg_rqsz +discard_avqsz.default = false + +flush.heading = F/S +flush.metric = rate(disk.dev.flush) +flush.default = false +flush.caption = Flushes per second + +flush_await.heading = FAWAIT +flush_await.metric = disk.dev.f_await +flush_await.default = false + +qlen.heading = AQU-SZ +qlen.metric = disk.dev.avg_qlen + +util.heading = UTIL% +util.metric = 100 * disk.dev.util +util.caption = Perentage device utilization +util.format = percent diff --git a/pcp/screens/execsnoop b/pcp/screens/execsnoop new file mode 100644 index 0000000..d706e76 --- /dev/null +++ b/pcp/screens/execsnoop @@ -0,0 +1,37 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[execsnoop] +heading = ExecSnoop +caption = BPF exec(2) syscall snoop +default = false + +pid.heading = PID +pid.caption = Process Identifier +pid.metric = bpf.execsnoop.pid +pid.format = process + +ppid.heading = PPID +ppid.caption = Parent Process +ppid.metric = bpf.execsnoop.ppid +ppid.format = process + +uid.heading = UID +uid.caption = User Identifier +uid.metric = bpf.execsnoop.uid + +comm.heading = COMM +comm.caption = Command +comm.width = -16 +comm.metric = bpf.execsnoop.comm +comm.format = command + +ret.heading = RET +ret.caption = Return Code +ret.metric = bpf.execsnoop.ret + +path.heading = Arguments +path.caption = Arguments +path.width = -12 +path.metric = bpf.execsnoop.args diff --git a/pcp/screens/exitsnoop b/pcp/screens/exitsnoop new file mode 100644 index 0000000..6c6b867 --- /dev/null +++ b/pcp/screens/exitsnoop @@ -0,0 +1,48 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[exitsnoop] +heading = ExitSnoop +caption = BPF process exit(2) snoop +default = false + +pid.heading = PID +pid.caption = Process Identifier +pid.metric = bpf.exitsnoop.pid +pid.format = process + +ppid.heading = PPID +ppid.caption = Parent Process +ppid.metric = bpf.exitsnoop.ppid +ppid.format = process + +tid.heading = TID +tid.caption = Task Identifier +tid.metric = bpf.exitsnoop.tid +tid.format = process +tid.default = false + +signal.heading = SIG +signal.caption = Signal number +signal.metric = bpf.exitsnoop.sig + +exit.heading = EXIT +exit.caption = Exit Code +exit.metric = bpf.exitsnoop.exit_code + +core.heading = CORE +core.caption = Dumped core +core.metric = bpf.exitsnoop.coredump +core.default = false + +age.heading = AGE +age.caption = Process age +age.metric = bpf.exitsnoop.age +age.default = false + +comm.heading = Command +comm.caption = COMM +comm.width = -16 +comm.metric = bpf.exitsnoop.comm +comm.format = command diff --git a/pcp/screens/filesystems b/pcp/screens/filesystems new file mode 100644 index 0000000..06f3bf2 --- /dev/null +++ b/pcp/screens/filesystems @@ -0,0 +1,50 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[filesystems] +heading = Filesystems +caption = Mounted block device filesystems + +blockdev.heading = Device +blockdev.metric = filesys.mountdir +blockdev.instances = true +blockdev.width = -14 + +blocksize.heading = BSIZE +blocksize.metric = filesys.blocksize +blocksize.default = false + +capacity.heading = SIZE +capacity.metric = filesys.capacity + +used.heading = USED +used.metric = filesys.used + +free.heading = FREE +free.metric = filesys.free +free.default = false + +avail.heading = AVAIL +avail.metric = filesys.avail + +full.heading = USE% +full.metric = filesys.full +full.format = percent + +usedfiles.heading = USEDF +usedfiles.metric = filesys.usedfiles +usedfiles.default = false + +freefiles.heading = FREEF +freefiles.metric = filesys.freefiles +freefiles.default = false + +maxfiles.heading = MAXF +maxfiles.metric = filesys.maxfiles +maxfiles.default = false + +mountdir.heading = Mount point +mountdir.metric = filesys.mountdir +mountdir.format = path +mountdir.width = -33 diff --git a/pcp/screens/opensnoop b/pcp/screens/opensnoop new file mode 100644 index 0000000..ec209b0 --- /dev/null +++ b/pcp/screens/opensnoop @@ -0,0 +1,27 @@ +# +# pcp-htop(1) configuration file - see pcp-htop(5) +# + +[opensnoop] +heading = OpenSnoop +caption = BPF open(2) syscall snoop +default = false + +pid.heading = PID +pid.metric = bpf.opensnoop.pid +pid.format = process + +comm.heading = COMM +comm.metric = bpf.opensnoop.comm +comm.format = command + +fd.heading = FD +fd.metric = bpf.opensnoop.fd + +err.heading = ERR +err.metric = bpf.opensnoop.err + +file.heading = File name +file.width = -32 +file.metric = bpf.opensnoop.fname +file.format = path |