summaryrefslogtreecommitdiffstats
path: root/pcp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--pcp-htop.5.in237
-rw-r--r--pcp-htop.c27
-rw-r--r--pcp/InDomTable.c99
-rw-r--r--pcp/InDomTable.h34
-rw-r--r--pcp/Instance.c163
-rw-r--r--pcp/Instance.h37
-rw-r--r--pcp/Metric.c200
-rw-r--r--pcp/Metric.h189
-rw-r--r--pcp/PCPDynamicColumn.c516
-rw-r--r--pcp/PCPDynamicColumn.h54
-rw-r--r--pcp/PCPDynamicMeter.c473
-rw-r--r--pcp/PCPDynamicMeter.h50
-rw-r--r--pcp/PCPDynamicScreen.c407
-rw-r--r--pcp/PCPDynamicScreen.h56
-rw-r--r--pcp/PCPMachine.c345
-rw-r--r--pcp/PCPMachine.h71
-rw-r--r--pcp/PCPProcess.c310
-rw-r--r--pcp/PCPProcess.h103
-rw-r--r--pcp/PCPProcessTable.c486
-rw-r--r--pcp/PCPProcessTable.h24
-rw-r--r--pcp/Platform.c924
-rw-r--r--pcp/Platform.h172
-rw-r--r--pcp/ProcessField.h54
-rw-r--r--pcp/columns/container10
-rw-r--r--pcp/columns/delayacct10
-rw-r--r--pcp/columns/fdcount10
-rw-r--r--pcp/columns/guest17
-rw-r--r--pcp/columns/memory39
-rw-r--r--pcp/columns/sched10
-rw-r--r--pcp/columns/swap15
-rw-r--r--pcp/columns/tcp31
-rw-r--r--pcp/columns/udp31
-rw-r--r--pcp/columns/wchan17
-rw-r--r--pcp/meters/entropy9
-rw-r--r--pcp/meters/freespace11
-rw-r--r--pcp/meters/ipc13
-rw-r--r--pcp/meters/locks15
-rw-r--r--pcp/meters/memcache11
-rw-r--r--pcp/meters/mysql71
-rw-r--r--pcp/meters/postfix20
-rw-r--r--pcp/meters/redis39
-rw-r--r--pcp/meters/tcp21
-rw-r--r--pcp/screens/biosnoop41
-rw-r--r--pcp/screens/cgroups45
-rw-r--r--pcp/screens/cgroupsio49
-rw-r--r--pcp/screens/cgroupsmem48
-rw-r--r--pcp/screens/devices114
-rw-r--r--pcp/screens/execsnoop37
-rw-r--r--pcp/screens/exitsnoop48
-rw-r--r--pcp/screens/filesystems50
-rw-r--r--pcp/screens/opensnoop27
51 files changed, 5890 insertions, 0 deletions
diff --git a/pcp-htop.5.in b/pcp-htop.5.in
new file mode 100644
index 0000000..ebb4748
--- /dev/null
+++ b/pcp-htop.5.in
@@ -0,0 +1,237 @@
+.TH "PCP-HTOP" "5" "2024" "@PACKAGE_STRING@" "File Formats"
+.SH "NAME"
+\f3pcp-htop\f1 \- pcp-htop configuration file
+.SH "DESCRIPTION"
+.B pcp-htop
+is a customizable performance metrics reporting tool.
+It has a dynamic architecture, where a set of configuration files
+provide additional, optional meters and columns to extend the fixed
+set of display options provided by regular
+.BR htop .
+.LP
+These configuration files can be provided from both system-wide
+locations (first
+.I @sysconfdir@/pcp/htop
+then
+.IR @datarootdir@/pcp/htop )
+and below the user's home directory (usually
+.IR ~/.config/htop ).
+Within these locations the
+.I meters
+and
+.I columns
+are scanned for dynamic Meter and Column specifications.
+.LP
+Meters are displayed in the top part of the
+.B pcp-htop
+window, and columns are displayed in the lower part.
+Meters tend to display system-wide metrics, and Columns
+display metrics about individual processes.
+.LP
+The formats are similar but have slightly different requirements.
+Both formats follow the common ini-style. Blank lines are ignored.
+Lines starting with the "#" character are treated as comments.
+.SH "METERS"
+The following is an example configuration for a new Redis meter:
+.LP
+.ft CW
+.nf
+.in +0.5i
+[redisclient]
+caption = 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
+.in
+.fi
+.ft 1
+.LP
+A configuration file can contain multiple meter definitions.
+Each definition begins with a identifying name enclosed by
+square brackets \-
+.I redisclient
+in this example.
+The name is used internally within
+.B pcp-htop
+and must be unique, must begin with an alphabetic character,
+and may subsequently only contain alphanumeric characters or
+the underscore character.
+No whitespace or other characters are allowed.
+.LP
+There are several parameters that define the way the meter
+will be displayed to the user.
+.TP 5
+.B caption
+This value is displayed on the Setup screen once the meter
+has been selected.
+A truncated version of the
+.I caption
+will also be displayed (followed by a colon) on the primary
+display while the meter is updating.
+.TP
+.B description
+This can be used to provide more detail during the meter
+selection process on the Setup screen, and if present it is
+displayed in the "Available Meters" column.
+If not present, the
+.B caption
+will be used for this.
+If neither is present, the internal (mandatory)
+.B name
+will be used.
+.TP
+.B type
+This setting allows a preferred default meter type to be specified.
+The associated value must be one of
+.IR bar ,
+.IR text ,
+.IR graph ,
+or
+.IR led .
+If no value is provided for a dynamic meter, the default value of
+.IR text
+will be used.
+.TP
+.B maximum
+A numeric value can also be set to size the meter, such that
+values (e.g. for a
+.I bar
+type meter display) will be scaled within range zero to
+.IR maximum .
+.LP
+The remaining definition syntax describes the individual
+metric(s) which will be used to animate the meter.
+One or more metrics must be specified for each meter and
+there are several properties associated with each.
+Once again, these metrics must be named (the same rules
+described above for meters apply here) and the following
+properties can be configured:
+.TP 5
+.B name.metric
+This is the only mandatory field and associates a PCP metric
+with the meter.
+Values sampled for each metric at runtime provide the
+animation visible in the
+.B pcp-htop
+display.
+The metric specification can be either a PCP metric name as
+listed by
+.BR pminfo (1)
+or a "derived" metric expression.
+The format for derived metric expressions is described on the
+.BR pmRegisterDerived (3)
+manual page.
+.TP
+.B name.color
+Setting color to be used when rendering metric values.
+Possible values are
+.IR red ,
+.IR green ,
+.IR blue ,
+.IR cyan ,
+.IR magenta ,
+.IR yellow ,
+.IR gray ,
+.I darkgray
+or
+.IR white .
+.TP
+.B name.label
+An optional, short label to display before the metric value.
+The ":" character will be appended to the
+.I label
+before the metric value part of the display.
+.TP
+.B name.suffix
+An optional, short suffix to display after the metric value.
+Commonly used to indicate values as a percentage using a "%"
+.I suffix
+value and to provide the base unit of measurement.
+Note that since PCP maintains units for metrics, for those
+metrics that have dimension in "space" (bytes, kilobytes,
+megabytes, etc), a suffix will be automatically appended.
+.SH "COLUMNS"
+The following is an example configuration for a new column
+showing open file descriptors for each process:
+.LP
+.ft CW
+.nf
+.in +0.5i
+[openfds]
+heading = FDS
+caption = FDCOUNT
+description = Open file descriptors
+metric = proc.fd.count
+width = 3
+.in
+.fi
+.ft 1
+.LP
+A configuration file can contain multiple column definitions.
+Each definition begins with a identifying name enclosed
+by square brackets \-
+.I openfds
+in this example, and the same rules apply as described above
+for meter names.
+.LP
+Each column must specify a metric.
+Optional parameters can also be set.
+.TP 5
+.B metric
+As with meters, the metric value must be either a PCP metric
+name as listed by
+.BR pminfo (1)
+or a derived metric.
+The metric must have an instance domain (set of values) and
+that instance domain must map to the set of processes with
+the instance identifier being PIDs (process identifiers).
+Typically this will be metrics from the
+.I proc
+or
+.I hotproc
+namespace (\c
+.BR pmdaproc (1)),
+but metrics from other domains (\c
+.BR pmdabcc (1),
+.BR pmdabpf (1),
+etc) that have per-process values are equally applicable.
+.TP
+.B width
+Column width to use when displaying values for the metric.
+A negative value can be used to specify left alignment.
+An upper column limit of 28 characters is enforced.
+The default column width is 5 characters.
+.TP
+.B heading
+The short title that will be displayed at the head of the
+column \- usually a short, cryptic, all uppercase string.
+.TP
+.B caption
+A short identifying word presented to users on the Setup
+screen under both the Available and Active Columns lists.
+.TP
+.B description
+Text that assists users to understand the meaning of this
+column when it is being presented via the Setup screen in
+the Available Columns list.
+.SH "SEE ALSO"
+.BR pcp-htop (1),
+.BR pminfo (1),
+.BR pmcd (1),
+.BR pmdaproc (1),
+.BR pmdabcc (1),
+.BR pmdabpf (1)
+and
+.BR pmRegisterDerived (3).
+.SH "AUTHORS"
+.B htop
+was originally developed by Hisham Muhammad.
+Nowadays it is maintained by the community at <htop@groups.io>.
+.LP
+.B pcp-htop
+is maintained as a collaboration between the <htop@groups.io> and <pcp@groups.io>
+communities, and forms part of the Performance Co-Pilot suite of tools.
diff --git a/pcp-htop.c b/pcp-htop.c
new file mode 100644
index 0000000..ed585ec
--- /dev/null
+++ b/pcp-htop.c
@@ -0,0 +1,27 @@
+/*
+htop - pcp-htop.c
+(C) 2004-2011 Hisham H. Muhammad
+(C) 2020-2021 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/pmapi.h>
+
+#include "CommandLine.h"
+#include "Platform.h"
+
+
+const char* program = "pcp-htop";
+
+int main(int argc, char** argv) {
+ pmSetProgname(program);
+
+ /* extract environment variables */
+ opts.flags |= PM_OPTFLAG_ENV_ONLY;
+ (void)pmGetOptions(argc, argv, &opts);
+
+ return CommandLine_run(argc, argv);
+}
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(&note,
+ "%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(&note,
+ "%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(&note,
+ "%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(&timestamp) != true)
+ return;
+
+ double sample = host->timestamp;
+ host->timestamp = pmtimevalToReal(&timestamp);
+ 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(&timestamp, NULL);
+ this->timestamp = pmtimevalToReal(&timestamp);
+
+ 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