Changeset View
Standalone View
usr.bin/systat/proc.c
- This file was added.
/*- | |||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD | |||||
* | |||||
* Copyright (c) 2021 Yoshihiro Ota <ota@j.email.ne.jp> | |||||
* | |||||
kib: Add email? | |||||
* Redistribution and use in source and binary forms, with or without | |||||
Done Inline ActionsAlso by convention we've been dropping All rights reserved. as it is not necessary, feel free to take it out absent a reason you want it there (as an example, a company contribution guideline might specify it). emaste: Also by convention we've been dropping `All rights reserved.` as it is not necessary, feel free… | |||||
* 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> | |||||
Done Inline Actions$FreeBSD$ in copyright was only used for header. After git conversion, it does not make much sense to add the id to new files, I suggest to remove __FBSDID() below as well. kib: $FreeBSD$ in copyright was only used for header.
After git conversion, it does not make much… | |||||
Done Inline ActionsDo I keep sys/cdfs.h here? ota_j.email.ne.jp: Do I keep sys/cdfs.h here? | |||||
Done Inline ActionsNo need to keep it. sys/param.h includes sys/types.h which already includes sys/cdefs.h kib: No need to keep it. sys/param.h includes sys/types.h which already includes sys/cdefs.h | |||||
#include <sys/user.h> | |||||
#include <curses.h> | |||||
#include <libprocstat.h> | |||||
#include <libutil.h> | |||||
#include <pwd.h> | |||||
#include <stdbool.h> | |||||
#include <stdlib.h> | |||||
Done Inline Actionssys/param.h already includes sys/types.h kib: sys/param.h already includes sys/types.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 | |||||
Done Inline ActionsDon't call it pages, it is either 'swapped_pages' or 'swapped'. kib: Don't call it pages, it is either 'swapped_pages' or 'swapped'. | |||||
* 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 | |||||
Done Inline ActionsCOMMLEN + 1 kib: `COMMLEN + 1` | |||||
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); | |||||
} | |||||
Done Inline Actionsspace before : kib: space before : | |||||
static int | |||||
swobj_sort(const void *a, const void *b) | |||||
{ | |||||
Done Inline Actionsreturn (0); kib: `return (0);` | |||||
return ((((const struct swapvm *) a)->kvo_me > | |||||
Done Inline Actionsreturn (*aa > bb->kvo_me ? -1: 1); kib: `return (*aa > bb->kvo_me ? -1: 1);` | |||||
((const struct swapvm *) b)->kvo_me) ? -1 : 1); | |||||
} | |||||
static bool | |||||
get_swap_vmobjects(void) | |||||
{ | |||||
static int maxnobj; | |||||
int cnt, i, next_i, last_nswobj; | |||||
Done Inline Actionscontig line should be indented +4 spaces kib: contig line should be indented +4 spaces
`return ();` | |||||
struct kinfo_vmobject *kvo; | |||||
next_i = nswobj = 0; | |||||
kvo = kinfo_getswapvmobject(&cnt); | |||||
if (kvo == NULL) { | |||||
error("kinfo_getswapvmobject()"); | |||||
Done Inline ActionsWhy is max static? kib: Why is max static? | |||||
Done Inline ActionsThis tracks the max realloc size. ota_j.email.ne.jp: This tracks the max realloc size.
I renamed it to maxnobj in the latest version. | |||||
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; | |||||
Done Inline Actions&& should be on the previous line kib: && should be on the previous line | |||||
} | |||||
nswobj++; | |||||
Done Inline ActionsWhy do you skip objects with kvo_swapped == 0? They do participate in the shadow chain. kib: Why do you skip objects with kvo_swapped == 0? They do participate in the shadow chain. | |||||
} | |||||
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) | |||||
Done Inline ActionsBTW why realloc(3) and not reallocf(3)? kib: BTW why realloc(3) and not reallocf(3)? | |||||
Done Inline ActionsI've never known reallocf(). ota_j.email.ne.jp: I've never known reallocf().
It calls die() which calls exit() if allocation fails, anyway. | |||||
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; | |||||
Done Inline Actions... number of swap pages ... kib: ... number of swap pages ... | |||||
struct swapvm *vm; | |||||
Done Inline ActionsNewline after uint32_t kib: Newline after uint32_t | |||||
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); | |||||
Done Inline ActionsAfter adding pid check, the numbers look more reasonable - e.g. no process with more swap than its allocated. ota_j.email.ne.jp: After adding pid check, the numbers look more reasonable - e.g. no process with more swap than… | |||||
} | |||||
} | |||||
free(freep); | |||||
return (pages); | |||||
} | |||||
void | |||||
closeproc(WINDOW *w) | |||||
{ | |||||
Done Inline Actionsreturn (pages); kib: `return (pages);` | |||||
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 | |||||
Done Inline ActionsI do not quite follow. Assuming there is no sysctl errors, on the first call you return 0, and on the second and consequent calls, 1. Is this intended? Also, why not follow consistent style, and do just return (prstat != NULL); (or whatever, ==) in the first statement in the function? kib: I do not quite follow. Assuming there is no sysctl errors, on the first call you return 0, and… | |||||
Done Inline ActionsI had one wrong. ota_j.email.ne.jp: I had one wrong.
swapinit() returns 1 for success. | |||||
procgetinfo(void) | |||||
{ | |||||
Done Inline Actionsspace before '(' kib: space before '(' | |||||
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); | |||||
} | |||||
Done Inline ActionsExcess () kib: Excess () | |||||
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); | |||||
} | |||||
Done Inline Actionsreturn (aa->pages > bb->pages ? -1: 1); kib: `return (aa->pages > bb->pages ? -1: 1);` | |||||
Done Inline Actionsconst struct proc_usage *aa = *((const struct proc_usage **)a); kib: `const struct proc_usage *aa = *((const struct proc_usage **)a);` | |||||
Done Inline Actionsspace before : kib: space before : |
Add email?