1/*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2021 Yoshihiro Ota <ota@j.email.ne.jp> 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28#include <sys/param.h> 29#include <sys/sysctl.h> 30#include <sys/user.h> 31 32#include <curses.h> 33#include <libprocstat.h> 34#include <libutil.h> 35#include <pwd.h> 36#include <stdbool.h> 37#include <stdlib.h> 38#include <string.h> 39 40#include "systat.h" 41#include "extern.h" 42 43/* 44 * vm objects of swappable types 45 */ 46static struct swapvm { 47 uint64_t kvo_me; 48 uint32_t swapped; /* in pages */ 49 uint64_t next; 50 pid_t pid; /* to avoid double counting */ 51} *swobj = NULL; 52static int nswobj = 0; 53 54static struct procstat *prstat = NULL; 55/* 56 *procstat_getvmmap() is an expensive call and the number of processes running 57 * may also be high. So, maintain an array of pointers for ease of expanding 58 * an array and also swapping pointers are faster than struct. 59 */ 60static struct proc_usage { 61 pid_t pid; 62 uid_t uid; 63 char command[COMMLEN + 1]; 64 uint64_t total; 65 uint32_t pages; 66} **pu = NULL; 67static int nproc; 68static int proc_compar(const void *, const void *); 69 70static void 71display_proc_line(int idx, int y, uint64_t totalswappages) 72{ 73 int offset = 0, rate; 74 const char *uname; 75 char buf[30]; 76 uint64_t swapbytes; 77 78 wmove(wnd, y, 0); 79 wclrtoeol(wnd); 80 if (idx >= nproc) 81 return; 82 83 uname = user_from_uid(pu[idx]->uid, 0); 84 swapbytes = ptoa(pu[idx]->pages); 85 86 snprintf(buf, sizeof(buf), "%6d %-10s %-10.10s", pu[idx]->pid, uname, 87 pu[idx]->command); 88 offset = 6 + 1 + 10 + 1 + 10 + 1; 89 mvwaddstr(wnd, y, 0, buf); 90 sysputuint64(wnd, y, offset, 4, swapbytes, 0); 91 offset += 4; 92 mvwaddstr(wnd, y, offset, " / "); 93 offset += 3; 94 sysputuint64(wnd, y, offset, 4, pu[idx]->total, 0); 95 offset += 4; 96 97 rate = pu[idx]->total > 1 ? 100 * swapbytes / pu[idx]->total : 0; 98 snprintf(buf, sizeof(buf), "%3d%%", rate); 99 mvwaddstr(wnd, y, offset, buf); 100 if (rate > 100) /* avoid running over the screen */ 101 rate = 100; 102 sysputXs(wnd, y, offset + 5, rate / 10); 103 104 rate = 100 * pu[idx]->pages / totalswappages; 105 snprintf(buf, sizeof(buf), "%3d%%", rate); 106 mvwaddstr(wnd, y, offset + 16, buf); 107 if (rate > 100) /* avoid running over the screen */ 108 rate = 100; 109 sysputXs(wnd, y, offset + 21, rate / 10); 110} 111 112static int 113swobj_search(const void *a, const void *b) 114{ 115 const uint64_t *aa = a; 116 const struct swapvm *bb = b; 117 118 if (*aa == bb->kvo_me) 119 return (0); 120 return (*aa > bb->kvo_me ? -1 : 1); 121} 122 123static int 124swobj_sort(const void *a, const void *b) 125{ 126 127 return ((((const struct swapvm *) a)->kvo_me > 128 ((const struct swapvm *) b)->kvo_me) ? -1 : 1); 129} 130 131static bool 132get_swap_vmobjects(void) 133{ 134 static int maxnobj; 135 int cnt, i, next_i, last_nswobj; 136 struct kinfo_vmobject *kvo; 137 138 next_i = nswobj = 0; 139 kvo = kinfo_getswapvmobject(&cnt); 140 if (kvo == NULL) { 141 error("kinfo_getswapvmobject()"); 142 return (false); 143 } 144 do { 145 for (i = next_i; i < cnt; i++) { 146 if (kvo[i].kvo_type != KVME_TYPE_DEFAULT && 147 kvo[i].kvo_type != KVME_TYPE_SWAP) 148 continue; 149 if (nswobj < maxnobj) { 150 swobj[nswobj].kvo_me = kvo[i].kvo_me; 151 swobj[nswobj].swapped = kvo[i].kvo_swapped; 152 swobj[nswobj].next = kvo[i].kvo_backing_obj; 153 swobj[nswobj].pid = 0; 154 next_i = i + 1; 155 } 156 nswobj++; 157 } 158 if (nswobj <= maxnobj) 159 break; 160 /* allocate memory and fill skipped elements */ 161 last_nswobj = maxnobj; 162 maxnobj = nswobj; 163 nswobj = last_nswobj; 164 /* allocate more memory and fill missed ones */ 165 if ((swobj = reallocf(swobj, maxnobj * sizeof(*swobj))) == 166 NULL) { 167 error("Out of memory"); 168 die(0); 169 } 170 } while (i <= cnt); /* extra safety guard */ 171 free(kvo); 172 if (nswobj > 1) 173 qsort(swobj, nswobj, sizeof(swobj[0]), swobj_sort); 174 return (nswobj > 0); 175} 176 177/* This returns the number of swap pages a process uses. */ 178static uint32_t 179per_proc_swap_usage(struct kinfo_proc *kipp) 180{ 181 int i, cnt; 182 uint32_t pages = 0; 183 uint64_t vmobj; 184 struct kinfo_vmentry *freep, *kve; 185 struct swapvm *vm; 186 187 freep = procstat_getvmmap(prstat, kipp, &cnt); 188 if (freep == NULL) 189 return (pages); 190 191 for (i = 0; i < cnt; i++) { 192 kve = &freep[i]; 193 if (kve->kve_type == KVME_TYPE_DEFAULT || 194 kve->kve_type == KVME_TYPE_SWAP) { 195 vmobj = kve->kve_obj; 196 do { 197 vm = bsearch(&vmobj, swobj, nswobj, 198 sizeof(swobj[0]), swobj_search); 199 if (vm != NULL && vm->pid != kipp->ki_pid) { 200 pages += vm->swapped; 201 vmobj = vm->next; 202 vm->pid = kipp->ki_pid; 203 } else 204 break; 205 } while (vmobj != 0); 206 } 207 } 208 procstat_freevmmap(prstat, freep); 209 return (pages); 210} 211 212void 213procshow(int lcol, int hight, uint64_t totalswappages) 214{ 215 int i, y; 216 217 for (i = 0, y = lcol + 1 /* HEADING */; i < hight; i++, y++) 218 display_proc_line(i, y, totalswappages); 219} 220 221int 222procinit(void) 223{ 224 225 if (prstat == NULL) 226 prstat = procstat_open_sysctl(); 227 return (prstat != NULL); 228} 229 230void 231procgetinfo(void) 232{ 233 static int maxnproc = 0; 234 int cnt, i; 235 uint32_t pages; 236 struct kinfo_proc *kipp; 237 238 nproc = 0; 239 if ( ! get_swap_vmobjects() ) /* call failed or nothing is paged-out */ 240 return; 241 242 kipp = procstat_getprocs(prstat, KERN_PROC_PROC, 0, &cnt); 243 if (kipp == NULL) { 244 error("procstat_getprocs()"); 245 return; 246 } 247 if (maxnproc < cnt) { 248 if ((pu = realloc(pu, cnt * sizeof(*pu))) == NULL) { 249 error("Out of memory"); 250 die(0); 251 } 252 memset(&pu[maxnproc], 0, (cnt - maxnproc) * sizeof(pu[0])); 253 maxnproc = cnt; 254 } 255 256 for (i = 0; i < cnt; i++) { 257 pages = per_proc_swap_usage(&kipp[i]); 258 if (pages == 0) 259 continue; 260 if (pu[nproc] == NULL && 261 (pu[nproc] = malloc(sizeof(**pu))) == NULL) { 262 error("Out of memory"); 263 die(0); 264 } 265 strlcpy(pu[nproc]->command, kipp[i].ki_comm, 266 sizeof(pu[nproc]->command)); 267 pu[nproc]->pid = kipp[i].ki_pid; 268 pu[nproc]->uid = kipp[i].ki_uid; 269 pu[nproc]->pages = pages; 270 pu[nproc]->total = kipp[i].ki_size; 271 nproc++; 272 } 273 if (nproc > 1) 274 qsort(pu, nproc, sizeof(*pu), proc_compar); 275 procstat_freeprocs(prstat, kipp); 276} 277 278void 279proclabel(int lcol) 280{ 281 282 wmove(wnd, lcol, 0); 283 wclrtoeol(wnd); 284 mvwaddstr(wnd, lcol, 0, 285 "Pid Username Command Swap/Total " 286 "Per-Process Per-System"); 287} 288 289int 290proc_compar(const void *a, const void *b) 291{ 292 const struct proc_usage *aa = *((const struct proc_usage **)a); 293 const struct proc_usage *bb = *((const struct proc_usage **)b); 294 295 return (aa->pages > bb->pages ? -1 : 1); 296} 297