Augment systat(1) -swap to display large swap space processes

This change updates the systat(1) -swap display to use libprocstat to
obtain and display per-process swap space usage infomation following its
existing swap devise/file statistics. It also incorporates the disk I/O
information from the -vmstat display.

The new screen looks like below with 'systat -swap':
                    /0   /1   /2   /3   /4   /5   /6   /7   /8   /9 /10
     Load Average   |

Device/Path       Size  Used |0%  /10  /20  /30  /40  / 60\  70\  80\ 90\ 100|
ada0s1b          2048M 2034M
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
zvol/sys/tempora 1024M 1015M
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
zvol/sys/swap    1024M 1014M
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Total            4096M 4063M
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Pid    Username   Command     Swap/Total Per-Process    Per-System
 24153 hiro       seamonkey   98M /   1G  7%              2%
 23677 hiro       xfce4-pane  28M /  81M 34% XXX          0%
 23629 hiro       xfce4-sess  25M / 118M 21% XX           0%
 23681 hiro       xfdesktop   20M /  58M 34% XXX          0%
 23678 hiro       thunar      15M /  43M 36% XXX          0%
 23658 hiro       at-spi-bus  14M /  23M 63% XXXXXX       0%
 23660 hiro       gvfsd       12M /  21M 56% XXXXX        0%

Disks  ada0  ada1  ada2   cd0 pass0 pass1 pass2 pass3
KB/t   8.00  0.00  0.00  0.00  0.00  0.00  0.00  0.00
tps       0     0     0     0     1     0     0     0
MB/s   0.00  0.00  0.00  0.00  0.00  0.00  0.00  0.00
%busy     0     0     0     0     0     0     0     0

Submitted by:	Yoshihiro Ota
MFC after:	1 week
Differential revision:	https://reviews.freebsd.org/D29754
This commit is contained in:
Konstantin Belousov 2021-10-26 11:43:08 +03:00
parent f2069331e5
commit 57e5da2c98
6 changed files with 361 additions and 36 deletions

View File

@ -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= tinfow ncursesw m devstat kvm util
LIBADD= tinfow ncursesw m devstat kvm util procstat
.include <bsd.prog.mk>

309
usr.bin/systat/proc.c Normal file
View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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,

View File

@ -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);