git: f33a9999e2a4 - stable/13 - Augment systat(1) -swap to display large swap space processes

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Mon, 01 Nov 2021 00:46:01 UTC
The branch stable/13 has been updated by kib:

URL: https://cgit.FreeBSD.org/src/commit/?id=f33a9999e2a458afe4b9c8f5cee42b30feb308ee

commit f33a9999e2a458afe4b9c8f5cee42b30feb308ee
Author:     Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2021-10-26 08:43:08 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2021-11-01 00:44:51 +0000

    Augment systat(1) -swap to display large swap space processes
    
    (cherry picked from commit 57e5da2c98003e5ab77a337e9fbe22ab7e512ba7)
---
 usr.bin/systat/Makefile |   4 +-
 usr.bin/systat/proc.c   | 309 ++++++++++++++++++++++++++++++++++++++++++++++++
 usr.bin/systat/swap.c   |  24 ++--
 usr.bin/systat/sysput.c |  24 +---
 usr.bin/systat/systat.1 |  30 ++++-
 usr.bin/systat/systat.h |   6 +
 6 files changed, 361 insertions(+), 36 deletions(-)

diff --git a/usr.bin/systat/Makefile b/usr.bin/systat/Makefile
index ca3f7ed72ce4..bfbe1336d291 100644
--- a/usr.bin/systat/Makefile
+++ b/usr.bin/systat/Makefile
@@ -5,7 +5,7 @@
 
 PROG=	systat
 SRCS=	cmds.c cmdtab.c devs.c fetch.c iostat.c keyboard.c main.c sysput.c \
-	netcmds.c netstat.c pigs.c swap.c icmp.c \
+	netcmds.c netstat.c pigs.c proc.c swap.c icmp.c \
 	mode.c ip.c sctp.c tcp.c zarc.c \
 	vmstat.c convtbl.c ifcmds.c ifstat.c
 
@@ -16,6 +16,6 @@ CFLAGS+= -DINET6
 
 WARNS?=	1
 
-LIBADD=	ncursesw m devstat kvm util
+LIBADD=	ncursesw m devstat kvm util procstat
 
 .include <bsd.prog.mk>
diff --git a/usr.bin/systat/proc.c b/usr.bin/systat/proc.c
new file mode 100644
index 000000000000..58c3bea64239
--- /dev/null
+++ b/usr.bin/systat/proc.c
@@ -0,0 +1,309 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 Yoshihiro Ota <ota@j.email.ne.jp>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/user.h>
+
+#include <curses.h>
+#include <libprocstat.h>
+#include <libutil.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "systat.h"
+#include "extern.h"
+
+/*
+ * vm objects of swappable types
+ */
+static struct swapvm {
+	uint64_t kvo_me;
+	uint32_t swapped; /* in pages */
+	uint64_t next;
+	pid_t pid; /* to avoid double counting */
+} *swobj = NULL;
+static int nswobj = 0;
+
+static struct procstat *prstat = NULL;
+/*
+ *procstat_getvmmap() is an expensive call and the number of processes running
+ * may also be high.  So, maintain an array of pointers for ease of expanding
+ * an array and also swapping pointers are faster than struct.
+ */
+static struct proc_usage {
+	pid_t pid;
+	uid_t uid;
+	char command[COMMLEN + 1];
+	uint64_t total;
+	uint32_t pages;
+} **pu = NULL;
+static unsigned int nproc;
+static int proc_compar(const void *, const void *);
+
+static void
+display_proc_line(int idx, int y, uint64_t totalswappages)
+{
+	int offset = 0, rate;
+	const char *uname, *pname;
+	char buf[30];
+	uint64_t swapbytes;
+
+	wmove(wnd, y, 0);
+	wclrtoeol(wnd);
+	if (idx >= nproc)
+		return;
+
+	uname = user_from_uid(pu[idx]->uid, 0);
+	swapbytes = ptoa(pu[idx]->pages);
+
+	snprintf(buf, sizeof(buf), "%6d %-10s %-10.10s", pu[idx]->pid, uname,
+	    pu[idx]->command);
+	offset = 6 + 1 + 10 + 1 + 10 + 1;
+	mvwaddstr(wnd, y, 0, buf);
+	sysputuint64(wnd, y, offset, 4, swapbytes, 0);
+	offset += 4;
+	mvwaddstr(wnd, y, offset, " / ");
+	offset += 3;
+	sysputuint64(wnd, y, offset, 4, pu[idx]->total, 0);
+	offset += 4;
+
+	rate = pu[idx]->total > 1 ? 100 * swapbytes / pu[idx]->total : 0;
+	snprintf(buf, sizeof(buf), "%3d%%", rate);
+	mvwaddstr(wnd, y, offset, buf);
+	if (rate > 100) /* avoid running over the screen */
+		rate = 100;
+	sysputXs(wnd, y, offset + 5, rate / 10);
+
+	rate = 100 * pu[idx]->pages / totalswappages;
+	snprintf(buf, sizeof(buf), "%3d%%", rate);
+	mvwaddstr(wnd, y, offset + 16, buf);
+	if (rate > 100) /* avoid running over the screen */
+		rate = 100;
+	sysputXs(wnd, y, offset + 21, rate / 10);
+}
+
+static int
+swobj_search(const void *a, const void *b)
+{
+	const uint64_t *aa = a;
+	const struct swapvm *bb = b;
+
+	if (*aa == bb->kvo_me)
+		return (0);
+	return (*aa > bb->kvo_me ? -1 : 1);
+}
+
+static int
+swobj_sort(const void *a, const void *b)
+{
+
+	return ((((const struct swapvm *) a)->kvo_me >
+	    ((const struct swapvm *) b)->kvo_me) ? -1 : 1);
+}
+
+static bool
+get_swap_vmobjects(void)
+{
+	static int maxnobj;
+	int cnt, i, next_i, last_nswobj;
+	struct kinfo_vmobject *kvo;
+
+	next_i = nswobj = 0;
+	kvo = kinfo_getswapvmobject(&cnt);
+	if (kvo == NULL) {
+		error("kinfo_getswapvmobject()");
+		return (false);
+	}
+	do {
+		for (i = next_i; i < cnt; i++) {
+			if (kvo[i].kvo_type != KVME_TYPE_DEFAULT &&
+			    kvo[i].kvo_type != KVME_TYPE_SWAP)
+				continue;
+			if (nswobj < maxnobj) {
+				swobj[nswobj].kvo_me = kvo[i].kvo_me;
+				swobj[nswobj].swapped = kvo[i].kvo_swapped;
+				swobj[nswobj].next = kvo[i].kvo_backing_obj;
+				swobj[nswobj].pid = 0;
+				next_i = i + 1;
+			}
+			nswobj++;
+		}
+		if (nswobj <= maxnobj)
+			break;
+		/* allocate memory and fill skipped elements */
+		last_nswobj = maxnobj;
+		maxnobj = nswobj;
+		nswobj = last_nswobj;
+		/* allocate more memory and fill missed ones */
+		if ((swobj = reallocf(swobj, maxnobj * sizeof(*swobj))) ==
+		    NULL) {
+			error("Out of memory");
+			die(0);
+		}
+	} while (i <= cnt); /* extra safety guard */
+	free(kvo);
+	if (nswobj > 1)
+		qsort(swobj, nswobj, sizeof(swobj[0]), swobj_sort);
+	return (nswobj > 0);
+}
+
+/* This returns the number of swap pages a process uses. */
+static uint32_t
+per_proc_swap_usage(struct kinfo_proc *kipp)
+{
+	int i, cnt;
+	uint32_t pages = 0;
+	uint64_t vmobj;
+	struct kinfo_vmentry *freep, *kve;
+	struct swapvm *vm;
+
+	freep = procstat_getvmmap(prstat, kipp, &cnt);
+	if (freep == NULL)
+		return (pages);
+
+	for (i = 0; i < cnt; i++) {
+		kve = &freep[i];
+		if (kve->kve_type == KVME_TYPE_DEFAULT ||
+		    kve->kve_type == KVME_TYPE_SWAP) {
+			vmobj = kve->kve_obj;
+			do {
+				vm = bsearch(&vmobj, swobj, nswobj,
+				    sizeof(swobj[0]), swobj_search);
+				if (vm != NULL && vm->pid != kipp->ki_pid) {
+					pages += vm->swapped;
+					vmobj = vm->next;
+					vm->pid = kipp->ki_pid;
+				} else
+					break;
+			} while (vmobj != 0);
+		}
+	}
+	free(freep);
+	return (pages);
+}
+
+void
+closeproc(WINDOW *w)
+{
+
+	if (prstat != NULL)
+		procstat_close(prstat);
+	prstat = NULL;
+	if (w == NULL)
+		return;
+	wclear(w);
+	wrefresh(w);
+	delwin(w);
+}
+
+void
+procshow(int col, int hight, uint64_t totalswappages)
+{
+	int i, y;
+
+	for (i = 0, y = col + 1 /* HEADING */; i < hight; i++, y++)
+		display_proc_line(i, y, totalswappages);
+}
+
+int
+procinit(void)
+{
+
+	if (prstat == NULL)
+		prstat = procstat_open_sysctl();
+	return (prstat != NULL);
+}
+
+void
+procgetinfo(void)
+{
+	static unsigned int maxnproc = 0;
+	int cnt, i;
+	uint32_t pages;
+	struct kinfo_proc *kipp;
+
+	nproc = 0;
+	if ( ! get_swap_vmobjects() ) /* call failed or nothing is paged-out */
+		return;
+
+	kipp = procstat_getprocs(prstat, KERN_PROC_PROC, 0, &cnt);
+	if (kipp == NULL) {
+		error("procstat_getprocs()");
+		return;
+	}
+	if (maxnproc < cnt) {
+		if ((pu = realloc(pu, cnt * sizeof(*pu))) == NULL) {
+			error("Out of memory");
+			die(0);
+		}
+		memset(&pu[maxnproc], 0, (cnt - maxnproc) * sizeof(pu[0]));
+		maxnproc = cnt;
+	}
+
+	for (i = 0; i < cnt; i++) {
+		pages = per_proc_swap_usage(&kipp[i]);
+		if (pages == 0)
+			continue;
+		if (pu[nproc] == NULL &&
+		    (pu[nproc] = malloc(sizeof(**pu))) == NULL) {
+			error("Out of memory");
+			die(0);
+		}
+		strlcpy(pu[nproc]->command, kipp[i].ki_comm,
+		    sizeof(pu[nproc]->command));
+		pu[nproc]->pid = kipp[i].ki_pid;
+		pu[nproc]->uid = kipp[i].ki_uid;
+		pu[nproc]->pages = pages;
+		pu[nproc]->total = kipp[i].ki_size;
+		nproc++;
+	}
+	if (nproc > 1)
+		qsort(pu, nproc, sizeof(*pu), proc_compar);
+}
+
+void
+proclabel(int col)
+{
+
+	wmove(wnd, col, 0);
+	wclrtoeol(wnd);
+	mvwaddstr(wnd, col, 0,
+	    "Pid    Username   Command     Swap/Total "
+	    "Per-Process    Per-System");
+}
+
+int
+proc_compar(const void *a, const void *b)
+{
+	const struct proc_usage *aa = *((const struct proc_usage **)a);
+	const struct proc_usage *bb = *((const struct proc_usage **)b);
+
+	return (aa->pages > bb->pages ? -1 : 1);
+}
diff --git a/usr.bin/systat/swap.c b/usr.bin/systat/swap.c
index 29b04df0157f..f28059bfecd4 100644
--- a/usr.bin/systat/swap.c
+++ b/usr.bin/systat/swap.c
@@ -103,6 +103,7 @@ initswap(void)
 	}
 	pathlen = 80 - 50 /* % */ - 5 /* Used */ - 5 /* Size */ - 3 /* space */;
 	dsinit(12);
+	procinit();
 	once = 1;
 
 	return (1);
@@ -125,14 +126,13 @@ fetchswap(void)
 	cur_dev.dinfo = tmp_dinfo;
 
 	last_dev.snap_time = cur_dev.snap_time;
-	dsgetinfo( &cur_dev );
+	dsgetinfo(&cur_dev);
+	procgetinfo();
 }
 
 void
 labelswap(void)
 {
-	const char *name;
-	int i;
 
 	werase(wnd);
 
@@ -146,18 +146,13 @@ labelswap(void)
 	mvwprintw(wnd, 0, 0, "%*s%5s %5s %s",
 	    -pathlen, "Device/Path", "Size", "Used",
 	    "|0%  /10  /20  /30  /40  / 60\\  70\\  80\\  90\\ 100|");
-
-	for (i = 0; i <= kvnsw; ++i) {
-		name = i == kvnsw ? "Total" : kvmsw[i].ksw_devname;
-		mvwprintw(wnd, 1 + i, 0, "%-*.*s", pathlen, pathlen - 1, name);
-	}
 }
 
 void
 showswap(void)
 {
-	int count;
-	int i;
+	const char *name;
+	int count, i;
 
 	if (kvnsw != okvnsw)
 		labelswap();
@@ -167,7 +162,10 @@ showswap(void)
 	if (kvnsw <= 0)
 		return;
 
-	for (i = 0; i <= kvnsw; ++i) {
+	for (i = (kvnsw == 1 ? 0 : kvnsw); i >= 0; i--) {
+		name = i == kvnsw ? "Total" : kvmsw[i].ksw_devname;
+		mvwprintw(wnd, 1 + i, 0, "%-*.*s", pathlen, pathlen - 1, name);
+
 		sysputpage(wnd, i + 1, pathlen, 5, kvmsw[i].ksw_total, 0);
 		sysputpage(wnd, i + 1, pathlen + 5 + 1, 5, kvmsw[i].ksw_used,
 		    0);
@@ -178,4 +176,8 @@ showswap(void)
 		}
 		wclrtoeol(wnd);
 	}
+	count = kvnsw == 1 ? 2 : 3;
+	proclabel(kvnsw + count);
+	procshow(kvnsw + count, LINES - 5 - kvnsw + 3 - DISKHIGHT + 1,
+	    kvmsw[kvnsw].ksw_total);
 }
diff --git a/usr.bin/systat/sysput.c b/usr.bin/systat/sysput.c
index e089c5c2ff55..6e14f9eb752f 100644
--- a/usr.bin/systat/sysput.c
+++ b/usr.bin/systat/sysput.c
@@ -31,10 +31,11 @@ __FBSDID("$FreeBSD$");
 #include <sys/types.h>
 #include <sys/sysctl.h>
 
-#include <inttypes.h>
-#include <string.h>
 #include <err.h>
+#include <inttypes.h>
 #include <libutil.h>
+#include <machine/param.h>
+#include <string.h>
 
 #include "systat.h"
 #include "extern.h"
@@ -103,26 +104,9 @@ sysputwuint64(WINDOW *wd, int row, int col, int width, uint64_t val, int flags)
 		sysputuint64(wd, row, col, width, val, flags);
 }
 
-static int
-calc_page_shift()
-{
-	u_int page_size;
-	int shifts;
-
-	shifts = 0;
-	GETSYSCTL("vm.stats.vm.v_page_size", page_size);
-	for(; page_size > 1; page_size >>= 1)
-		shifts++;
-	return shifts;
-}
-
 void
 sysputpage(WINDOW *wd, int row, int col, int width, uint64_t pages, int flags)
 {
-	static int shifts = 0;
 
-	if (shifts == 0)
-		shifts = calc_page_shift();
-	pages <<= shifts;
-	sysputuint64(wd, row, col, width, pages, flags);
+	sysputuint64(wd, row, col, width, ptoa(pages), flags);
 }
diff --git a/usr.bin/systat/systat.1 b/usr.bin/systat/systat.1
index 5ad8078a85a4..1f6ddaeafb1a 100644
--- a/usr.bin/systat/systat.1
+++ b/usr.bin/systat/systat.1
@@ -279,9 +279,11 @@ not display kilobytes per transaction).
 .El
 .It Ic swap
 Show information about swap space usage on all the
-swap areas compiled into the kernel.
-The first column is the device name of the partition.
-The next column is the total space available in the partition.
+swap areas compiled into the kernel and processes that are swapped out
+as well as a summary of disk activity.
+.Pp
+The swap areas are displayed first with their name, sizes and
+usage percentage.
 The
 .Ar Used
 column indicates the total blocks used so far;
@@ -289,6 +291,28 @@ the graph shows the percentage of space in use on each partition.
 If there are more than one swap partition in use,
 a total line is also shown.
 Areas known to the kernel, but not in use are shown as not available.
+.Pp
+Below the swap space statistics,
+processes are listed in order of higher swap area usage.
+Pid, username, a part of command line, the total use of swap space
+in bytes, the size of process, as well as per-process swap usage percentage and
+per-system swap space percentage are shown per process.
+.Pp
+At the bottom left is the disk usage display.
+It reports the number of
+kilobytes per transaction, transactions per second, megabytes
+per second and the percentage of the time the disk was busy averaged
+over the refresh period of the display (by default, five seconds).
+The system keeps statistics on most every storage device.
+In general, up
+to seven devices are displayed.
+The devices displayed by default are the
+first devices in the kernel's device list.
+See
+.Xr devstat 3
+and
+.Xr devstat 9
+for details on the devstat system.
 .It Ic vmstat
 Take over the entire display and show a (rather crowded) compendium
 of statistics related to virtual memory usage, process scheduling,
diff --git a/usr.bin/systat/systat.h b/usr.bin/systat/systat.h
index 92233e058172..4ced36c32932 100644
--- a/usr.bin/systat/systat.h
+++ b/usr.bin/systat/systat.h
@@ -32,6 +32,7 @@
  * $FreeBSD$
  */
 
+#include <sys/stdint.h>
 #include <curses.h>
 
 struct  cmdtab {
@@ -72,3 +73,8 @@ extern int use_kvm;
 extern void putint(int, int, int, int);
 extern void putfloat(double, int, int, int, int, int);
 extern void putlongdouble(long double, int, int, int, int, int);
+
+int procinit(void);
+void procgetinfo(void);
+void proclabel(int col);
+void procshow(int col, int hight, uint64_t totalswappages);