1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 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 */ 46 static 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; 52 static int nswobj = 0; 53 54 static 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 */ 60 static 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; 67 static int nproc; 68 static int proc_compar(const void *, const void *); 69 70 static void 71 display_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 112 static int 113 swobj_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 123 static int 124 swobj_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 131 static bool 132 get_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. */ 178 static uint32_t 179 per_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 free(freep); 209 return (pages); 210 } 211 212 void 213 procshow(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 221 int 222 procinit(void) 223 { 224 225 if (prstat == NULL) 226 prstat = procstat_open_sysctl(); 227 return (prstat != NULL); 228 } 229 230 void 231 procgetinfo(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 } 276 277 void 278 proclabel(int lcol) 279 { 280 281 wmove(wnd, lcol, 0); 282 wclrtoeol(wnd); 283 mvwaddstr(wnd, lcol, 0, 284 "Pid Username Command Swap/Total " 285 "Per-Process Per-System"); 286 } 287 288 int 289 proc_compar(const void *a, const void *b) 290 { 291 const struct proc_usage *aa = *((const struct proc_usage **)a); 292 const struct proc_usage *bb = *((const struct proc_usage **)b); 293 294 return (aa->pages > bb->pages ? -1 : 1); 295 } 296