1 /*
2 htop - OpenBSDProcessList.c
3 (C) 2014 Hisham H. Muhammad
4 (C) 2015 Michael McConville
5 Released under the GNU GPLv2+, see the COPYING file
6 in the source distribution for its full text.
7 */
8
9 #include "openbsd/OpenBSDProcessList.h"
10
11 #include <kvm.h>
12 #include <limits.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <unistd.h>
16 #include <sys/mount.h>
17 #include <sys/param.h>
18 #include <sys/proc.h>
19 #include <sys/sched.h>
20 #include <sys/swap.h>
21 #include <sys/sysctl.h>
22 #include <sys/types.h>
23 #include <uvm/uvmexp.h>
24
25 #include "CRT.h"
26 #include "Macros.h"
27 #include "Object.h"
28 #include "Process.h"
29 #include "ProcessList.h"
30 #include "Settings.h"
31 #include "XUtils.h"
32 #include "openbsd/OpenBSDProcess.h"
33
34
35 static long fscale;
36 static int pageSize;
37 static int pageSizeKB;
38
OpenBSDProcessList_updateCPUcount(ProcessList * super)39 static void OpenBSDProcessList_updateCPUcount(ProcessList* super) {
40 OpenBSDProcessList* opl = (OpenBSDProcessList*) super;
41 const int nmib[] = { CTL_HW, HW_NCPU };
42 const int mib[] = { CTL_HW, HW_NCPUONLINE };
43 int r;
44 unsigned int value;
45 size_t size;
46 bool change = false;
47
48 size = sizeof(value);
49 r = sysctl(mib, 2, &value, &size, NULL, 0);
50 if (r < 0 || value < 1) {
51 value = 1;
52 }
53
54 if (value != super->activeCPUs) {
55 super->activeCPUs = value;
56 change = true;
57 }
58
59 size = sizeof(value);
60 r = sysctl(nmib, 2, &value, &size, NULL, 0);
61 if (r < 0 || value < 1) {
62 value = super->activeCPUs;
63 }
64
65 if (value != super->existingCPUs) {
66 opl->cpuData = xReallocArray(opl->cpuData, value + 1, sizeof(CPUData));
67 super->existingCPUs = value;
68 change = true;
69 }
70
71 if (change) {
72 CPUData* dAvg = &opl->cpuData[0];
73 memset(dAvg, '\0', sizeof(CPUData));
74 dAvg->totalTime = 1;
75 dAvg->totalPeriod = 1;
76 dAvg->online = true;
77
78 for (unsigned int i = 0; i < super->existingCPUs; i++) {
79 CPUData* d = &opl->cpuData[i + 1];
80 memset(d, '\0', sizeof(CPUData));
81 d->totalTime = 1;
82 d->totalPeriod = 1;
83
84 const int ncmib[] = { CTL_KERN, KERN_CPUSTATS, i };
85 struct cpustats cpu_stats;
86
87 size = sizeof(cpu_stats);
88 if (sysctl(ncmib, 3, &cpu_stats, &size, NULL, 0) < 0) {
89 CRT_fatalError("ncmib sysctl call failed");
90 }
91 d->online = (cpu_stats.cs_flags & CPUSTATS_ONLINE);
92 }
93 }
94 }
95
96
ProcessList_new(UsersTable * usersTable,Hashtable * dynamicMeters,Hashtable * dynamicColumns,Hashtable * pidMatchList,uid_t userId)97 ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) {
98 const int fmib[] = { CTL_KERN, KERN_FSCALE };
99 size_t size;
100 char errbuf[_POSIX2_LINE_MAX];
101
102 OpenBSDProcessList* opl = xCalloc(1, sizeof(OpenBSDProcessList));
103 ProcessList* pl = (ProcessList*) opl;
104 ProcessList_init(pl, Class(OpenBSDProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId);
105
106 OpenBSDProcessList_updateCPUcount(pl);
107
108 size = sizeof(fscale);
109 if (sysctl(fmib, 2, &fscale, &size, NULL, 0) < 0) {
110 CRT_fatalError("fscale sysctl call failed");
111 }
112
113 if ((pageSize = sysconf(_SC_PAGESIZE)) == -1)
114 CRT_fatalError("pagesize sysconf call failed");
115 pageSizeKB = pageSize / ONE_K;
116
117 opl->kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
118 if (opl->kd == NULL) {
119 CRT_fatalError("kvm_openfiles() failed");
120 }
121
122 opl->cpuSpeed = -1;
123
124 return pl;
125 }
126
ProcessList_delete(ProcessList * this)127 void ProcessList_delete(ProcessList* this) {
128 OpenBSDProcessList* opl = (OpenBSDProcessList*) this;
129
130 if (opl->kd) {
131 kvm_close(opl->kd);
132 }
133
134 free(opl->cpuData);
135
136 ProcessList_done(this);
137 free(this);
138 }
139
OpenBSDProcessList_scanMemoryInfo(ProcessList * pl)140 static void OpenBSDProcessList_scanMemoryInfo(ProcessList* pl) {
141 const int uvmexp_mib[] = { CTL_VM, VM_UVMEXP };
142 struct uvmexp uvmexp;
143 size_t size_uvmexp = sizeof(uvmexp);
144
145 if (sysctl(uvmexp_mib, 2, &uvmexp, &size_uvmexp, NULL, 0) < 0) {
146 CRT_fatalError("uvmexp sysctl call failed");
147 }
148
149 pl->totalMem = uvmexp.npages * pageSizeKB;
150 pl->usedMem = (uvmexp.npages - uvmexp.free - uvmexp.paging) * pageSizeKB;
151
152 // Taken from OpenBSD systat/iostat.c, top/machine.c and uvm_sysctl(9)
153 const int bcache_mib[] = { CTL_VFS, VFS_GENERIC, VFS_BCACHESTAT };
154 struct bcachestats bcstats;
155 size_t size_bcstats = sizeof(bcstats);
156
157 if (sysctl(bcache_mib, 3, &bcstats, &size_bcstats, NULL, 0) < 0) {
158 CRT_fatalError("cannot get vfs.bcachestat");
159 }
160
161 pl->cachedMem = bcstats.numbufpages * pageSizeKB;
162
163 /*
164 * Copyright (c) 1994 Thorsten Lockert <tholo@sigmasoft.com>
165 * All rights reserved.
166 *
167 * Taken almost directly from OpenBSD's top(1)
168 *
169 * Originally released under a BSD-3 license
170 * Modified through htop developers applying GPL-2
171 */
172 int nswap = swapctl(SWAP_NSWAP, 0, 0);
173 if (nswap > 0) {
174 struct swapent swdev[nswap];
175 int rnswap = swapctl(SWAP_STATS, swdev, nswap);
176
177 /* Total things up */
178 unsigned long long int total = 0, used = 0;
179 for (int i = 0; i < rnswap; i++) {
180 if (swdev[i].se_flags & SWF_ENABLE) {
181 used += (swdev[i].se_inuse / (1024 / DEV_BSIZE));
182 total += (swdev[i].se_nblks / (1024 / DEV_BSIZE));
183 }
184 }
185
186 pl->totalSwap = total;
187 pl->usedSwap = used;
188 } else {
189 pl->totalSwap = pl->usedSwap = 0;
190 }
191 }
192
OpenBSDProcessList_updateCwd(const struct kinfo_proc * kproc,Process * proc)193 static void OpenBSDProcessList_updateCwd(const struct kinfo_proc* kproc, Process* proc) {
194 const int mib[] = { CTL_KERN, KERN_PROC_CWD, kproc->p_pid };
195 char buffer[2048];
196 size_t size = sizeof(buffer);
197 if (sysctl(mib, 3, buffer, &size, NULL, 0) != 0) {
198 free(proc->procCwd);
199 proc->procCwd = NULL;
200 return;
201 }
202
203 /* Kernel threads return an empty buffer */
204 if (buffer[0] == '\0') {
205 free(proc->procCwd);
206 proc->procCwd = NULL;
207 return;
208 }
209
210 free_and_xStrdup(&proc->procCwd, buffer);
211 }
212
OpenBSDProcessList_updateProcessName(kvm_t * kd,const struct kinfo_proc * kproc,Process * proc)213 static void OpenBSDProcessList_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) {
214 Process_updateComm(proc, kproc->p_comm);
215
216 /*
217 * Like OpenBSD's top(1), we try to fall back to the command name
218 * (argv[0]) if we fail to construct the full command.
219 */
220 char** arg = kvm_getargv(kd, kproc, 500);
221 if (arg == NULL || *arg == NULL) {
222 Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));
223 return;
224 }
225
226 size_t len = 0;
227 for (int i = 0; arg[i] != NULL; i++) {
228 len += strlen(arg[i]) + 1; /* room for arg and trailing space or NUL */
229 }
230
231 /* don't use xMalloc here - we want to handle huge argv's gracefully */
232 char* s;
233 if ((s = malloc(len)) == NULL) {
234 Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));
235 return;
236 }
237
238 *s = '\0';
239
240 int start = 0;
241 int end = 0;
242 for (int i = 0; arg[i] != NULL; i++) {
243 size_t n = strlcat(s, arg[i], len);
244 if (i == 0) {
245 end = MINIMUM(n, len - 1);
246 /* check if cmdline ended earlier, e.g 'kdeinit5: Running...' */
247 for (int j = end; j > 0; j--) {
248 if (arg[0][j] == ' ' && arg[0][j - 1] != '\\') {
249 end = (arg[0][j - 1] == ':') ? (j - 1) : j;
250 }
251 }
252 }
253 /* the trailing space should get truncated anyway */
254 strlcat(s, " ", len);
255 }
256
257 Process_updateCmdline(proc, s, start, end);
258
259 free(s);
260 }
261
262 /*
263 * Taken from OpenBSD's ps(1).
264 */
getpcpu(const struct kinfo_proc * kp)265 static double getpcpu(const struct kinfo_proc* kp) {
266 if (fscale == 0)
267 return 0.0;
268
269 return 100.0 * (double)kp->p_pctcpu / fscale;
270 }
271
OpenBSDProcessList_scanProcs(OpenBSDProcessList * this)272 static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) {
273 const Settings* settings = this->super.settings;
274 const bool hideKernelThreads = settings->hideKernelThreads;
275 const bool hideUserlandThreads = settings->hideUserlandThreads;
276 int count = 0;
277
278 const struct kinfo_proc* kprocs = kvm_getprocs(this->kd, KERN_PROC_KTHREAD | KERN_PROC_SHOW_THREADS, 0, sizeof(struct kinfo_proc), &count);
279
280 for (int i = 0; i < count; i++) {
281 const struct kinfo_proc* kproc = &kprocs[i];
282
283 /* Ignore main threads */
284 if (kproc->p_tid != -1) {
285 Process* containingProcess = ProcessList_findProcess(&this->super, kproc->p_pid);
286 if (containingProcess) {
287 if (((OpenBSDProcess*)containingProcess)->addr == kproc->p_addr)
288 continue;
289
290 containingProcess->nlwp++;
291 }
292 }
293
294 bool preExisting = false;
295 Process* proc = ProcessList_getProcess(&this->super, (kproc->p_tid == -1) ? kproc->p_pid : kproc->p_tid, &preExisting, OpenBSDProcess_new);
296 OpenBSDProcess* fp = (OpenBSDProcess*) proc;
297
298 if (!preExisting) {
299 proc->ppid = kproc->p_ppid;
300 proc->tpgid = kproc->p_tpgid;
301 proc->tgid = kproc->p_pid;
302 proc->session = kproc->p_sid;
303 proc->pgrp = kproc->p__pgid;
304 proc->isKernelThread = proc->pgrp == 0;
305 proc->isUserlandThread = kproc->p_tid != -1;
306 proc->starttime_ctime = kproc->p_ustart_sec;
307 Process_fillStarttimeBuffer(proc);
308 ProcessList_add(&this->super, proc);
309
310 OpenBSDProcessList_updateProcessName(this->kd, kproc, proc);
311
312 if (settings->flags & PROCESS_FLAG_CWD) {
313 OpenBSDProcessList_updateCwd(kproc, proc);
314 }
315
316 proc->tty_nr = kproc->p_tdev;
317 const char* name = ((dev_t)kproc->p_tdev != NODEV) ? devname(kproc->p_tdev, S_IFCHR) : NULL;
318 if (!name || String_eq(name, "??")) {
319 free(proc->tty_name);
320 proc->tty_name = NULL;
321 } else {
322 free_and_xStrdup(&proc->tty_name, name);
323 }
324 } else {
325 if (settings->updateProcessNames) {
326 OpenBSDProcessList_updateProcessName(this->kd, kproc, proc);
327 }
328 }
329
330 fp->addr = kproc->p_addr;
331 proc->m_virt = kproc->p_vm_dsize * pageSizeKB;
332 proc->m_resident = kproc->p_vm_rssize * pageSizeKB;
333 proc->percent_mem = proc->m_resident / (float)this->super.totalMem * 100.0F;
334 proc->percent_cpu = CLAMP(getpcpu(kproc), 0.0F, this->super.activeCPUs * 100.0F);
335 proc->nice = kproc->p_nice - 20;
336 proc->time = 100 * (kproc->p_rtime_sec + ((kproc->p_rtime_usec + 500000) / 1000000));
337 proc->priority = kproc->p_priority - PZERO;
338 proc->processor = kproc->p_cpuid;
339 proc->minflt = kproc->p_uru_minflt;
340 proc->majflt = kproc->p_uru_majflt;
341 proc->nlwp = 1;
342
343 if (proc->st_uid != kproc->p_uid) {
344 proc->st_uid = kproc->p_uid;
345 proc->user = UsersTable_getRef(this->super.usersTable, proc->st_uid);
346 }
347
348 /* Taken from: https://github.com/openbsd/src/blob/6a38af0976a6870911f4b2de19d8da28723a5778/sys/sys/proc.h#L420 */
349 switch (kproc->p_stat) {
350 case SIDL: proc->state = IDLE; break;
351 case SRUN: proc->state = RUNNABLE; break;
352 case SSLEEP: proc->state = SLEEPING; break;
353 case SSTOP: proc->state = STOPPED; break;
354 case SZOMB: proc->state = ZOMBIE; break;
355 case SDEAD: proc->state = DEFUNCT; break;
356 case SONPROC: proc->state = RUNNING; break;
357 default: proc->state = UNKNOWN;
358 }
359
360 if (Process_isKernelThread(proc)) {
361 this->super.kernelThreads++;
362 } else if (Process_isUserlandThread(proc)) {
363 this->super.userlandThreads++;
364 }
365
366 this->super.totalTasks++;
367 if (proc->state == RUNNING) {
368 this->super.runningTasks++;
369 }
370
371 proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
372 proc->updated = true;
373 }
374 }
375
getKernelCPUTimes(unsigned int cpuId,u_int64_t * times)376 static void getKernelCPUTimes(unsigned int cpuId, u_int64_t* times) {
377 const int mib[] = { CTL_KERN, KERN_CPTIME2, cpuId };
378 size_t length = sizeof(*times) * CPUSTATES;
379 if (sysctl(mib, 3, times, &length, NULL, 0) == -1 || length != sizeof(*times) * CPUSTATES) {
380 CRT_fatalError("sysctl kern.cp_time2 failed");
381 }
382 }
383
kernelCPUTimesToHtop(const u_int64_t * times,CPUData * cpu)384 static void kernelCPUTimesToHtop(const u_int64_t* times, CPUData* cpu) {
385 unsigned long long totalTime = 0;
386 for (int i = 0; i < CPUSTATES; i++) {
387 totalTime += times[i];
388 }
389
390 unsigned long long sysAllTime = times[CP_INTR] + times[CP_SYS];
391
392 // XXX Not sure if CP_SPIN should be added to sysAllTime.
393 // See https://github.com/openbsd/src/commit/531d8034253fb82282f0f353c086e9ad827e031c
394 #ifdef CP_SPIN
395 sysAllTime += times[CP_SPIN];
396 #endif
397
398 cpu->totalPeriod = saturatingSub(totalTime, cpu->totalTime);
399 cpu->userPeriod = saturatingSub(times[CP_USER], cpu->userTime);
400 cpu->nicePeriod = saturatingSub(times[CP_NICE], cpu->niceTime);
401 cpu->sysPeriod = saturatingSub(times[CP_SYS], cpu->sysTime);
402 cpu->sysAllPeriod = saturatingSub(sysAllTime, cpu->sysAllTime);
403 #ifdef CP_SPIN
404 cpu->spinPeriod = saturatingSub(times[CP_SPIN], cpu->spinTime);
405 #endif
406 cpu->intrPeriod = saturatingSub(times[CP_INTR], cpu->intrTime);
407 cpu->idlePeriod = saturatingSub(times[CP_IDLE], cpu->idleTime);
408
409 cpu->totalTime = totalTime;
410 cpu->userTime = times[CP_USER];
411 cpu->niceTime = times[CP_NICE];
412 cpu->sysTime = times[CP_SYS];
413 cpu->sysAllTime = sysAllTime;
414 #ifdef CP_SPIN
415 cpu->spinTime = times[CP_SPIN];
416 #endif
417 cpu->intrTime = times[CP_INTR];
418 cpu->idleTime = times[CP_IDLE];
419 }
420
OpenBSDProcessList_scanCPUTime(OpenBSDProcessList * this)421 static void OpenBSDProcessList_scanCPUTime(OpenBSDProcessList* this) {
422 u_int64_t kernelTimes[CPUSTATES] = {0};
423 u_int64_t avg[CPUSTATES] = {0};
424
425 for (unsigned int i = 0; i < this->super.existingCPUs; i++) {
426 CPUData* cpu = &this->cpuData[i + 1];
427
428 if (!cpu->online) {
429 continue;
430 }
431
432 getKernelCPUTimes(i, kernelTimes);
433 kernelCPUTimesToHtop(kernelTimes, cpu);
434
435 avg[CP_USER] += cpu->userTime;
436 avg[CP_NICE] += cpu->niceTime;
437 avg[CP_SYS] += cpu->sysTime;
438 #ifdef CP_SPIN
439 avg[CP_SPIN] += cpu->spinTime;
440 #endif
441 avg[CP_INTR] += cpu->intrTime;
442 avg[CP_IDLE] += cpu->idleTime;
443 }
444
445 for (int i = 0; i < CPUSTATES; i++) {
446 avg[i] /= this->super.activeCPUs;
447 }
448
449 kernelCPUTimesToHtop(avg, &this->cpuData[0]);
450
451 {
452 const int mib[] = { CTL_HW, HW_CPUSPEED };
453 int cpuSpeed;
454 size_t size = sizeof(cpuSpeed);
455 if (sysctl(mib, 2, &cpuSpeed, &size, NULL, 0) == -1) {
456 this->cpuSpeed = -1;
457 } else {
458 this->cpuSpeed = cpuSpeed;
459 }
460 }
461 }
462
ProcessList_goThroughEntries(ProcessList * super,bool pauseProcessUpdate)463 void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
464 OpenBSDProcessList* opl = (OpenBSDProcessList*) super;
465
466 OpenBSDProcessList_updateCPUcount(super);
467 OpenBSDProcessList_scanMemoryInfo(super);
468 OpenBSDProcessList_scanCPUTime(opl);
469
470 // in pause mode only gather global data for meters (CPU/memory/...)
471 if (pauseProcessUpdate) {
472 return;
473 }
474
475 OpenBSDProcessList_scanProcs(opl);
476 }
477
ProcessList_isCPUonline(const ProcessList * super,unsigned int id)478 bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
479 assert(id < super->existingCPUs);
480
481 const OpenBSDProcessList* opl = (const OpenBSDProcessList*) super;
482 return opl->cpuData[id + 1].online;
483 }
484