2014-11-27 18:27:34 +00:00
|
|
|
/*
|
2014-11-27 19:44:55 +00:00
|
|
|
htop - FreeBSDProcessList.c
|
2014-11-27 18:27:34 +00:00
|
|
|
(C) 2014 Hisham H. Muhammad
|
2021-09-22 09:33:00 +00:00
|
|
|
Released under the GNU GPLv2+, see the COPYING file
|
2014-11-27 18:27:34 +00:00
|
|
|
in the source distribution for its full text.
|
|
|
|
*/
|
|
|
|
|
kfreeBSD: include config.h for _GNU_SOURCE (part 2)
strcasestr(3) is a GNU extension and when compiling freebsd/Platform.c
on kfreebsd for Debian <string.h> is included before we define
_GNU_SOURCE, so the function is not available.
In file included from ./Object.h:16,
from ./ListItem.h:12,
from ./Meter.h:16,
from ./Header.h:10,
from ./Action.h:15,
from freebsd/Platform.h:13,
from freebsd/Platform.c:8:
./XUtils.h: In function ‘String_contains_i’:
./XUtils.h:43:11: warning: implicit declaration of function ‘strcasestr’; did you mean ‘strcasecmp’? [-Wimplicit-function-declaration]
43 | return strcasestr(s1, s2) != NULL;
| ^~~~~~~~~~
| strcasecmp
./XUtils.h:43:30: warning: comparison between pointer and integer
43 | return strcasestr(s1, s2) != NULL;
| ^~
In file included from ./Object.h:16,
from ./ProcessList.h:16,
from freebsd/FreeBSDProcessList.h:15,
from freebsd/FreeBSDProcessList.c:8:
./XUtils.h: In function ‘String_contains_i’:
./XUtils.h:43:11: warning: implicit declaration of function ‘strcasestr’; did you mean ‘strcasecmp’? [-Wimplicit-function-declaration]
43 | return strcasestr(s1, s2) != NULL;
| ^~~~~~~~~~
| strcasecmp
./XUtils.h:43:30: warning: comparison between pointer and integer
43 | return strcasestr(s1, s2) != NULL;
| ^~
2021-01-12 18:05:46 +00:00
|
|
|
#include "config.h" // IWYU pragma: keep
|
|
|
|
|
2021-04-29 18:13:36 +00:00
|
|
|
#include "freebsd/FreeBSDProcessList.h"
|
2014-11-27 18:27:34 +00:00
|
|
|
|
2020-09-19 11:55:23 +00:00
|
|
|
#include <assert.h>
|
2016-03-06 03:59:39 +00:00
|
|
|
#include <limits.h>
|
2020-12-22 19:02:01 +00:00
|
|
|
#include <math.h>
|
2020-09-19 11:55:23 +00:00
|
|
|
#include <stdlib.h>
|
2015-03-17 02:01:48 +00:00
|
|
|
#include <string.h>
|
2020-11-18 14:12:18 +00:00
|
|
|
#include <sys/_iovec.h>
|
|
|
|
#include <sys/errno.h>
|
|
|
|
#include <sys/param.h> // needs to be included before <sys/jail.h> for MAXPATHLEN
|
|
|
|
#include <sys/jail.h>
|
|
|
|
#include <sys/priority.h>
|
|
|
|
#include <sys/proc.h>
|
|
|
|
#include <sys/resource.h>
|
2020-09-19 11:55:23 +00:00
|
|
|
#include <sys/sysctl.h>
|
2020-11-18 14:12:18 +00:00
|
|
|
#include <sys/time.h>
|
2020-10-21 17:11:19 +00:00
|
|
|
#include <sys/types.h>
|
2020-09-19 11:55:23 +00:00
|
|
|
#include <sys/user.h>
|
|
|
|
|
2020-10-15 20:37:02 +00:00
|
|
|
#include "CRT.h"
|
2020-11-18 14:12:18 +00:00
|
|
|
#include "Compat.h"
|
2020-09-19 11:55:23 +00:00
|
|
|
#include "FreeBSDProcess.h"
|
|
|
|
#include "Macros.h"
|
2020-11-18 14:12:18 +00:00
|
|
|
#include "Object.h"
|
|
|
|
#include "Process.h"
|
2020-09-19 11:55:23 +00:00
|
|
|
#include "ProcessList.h"
|
2020-11-18 14:12:18 +00:00
|
|
|
#include "Settings.h"
|
2020-10-21 17:11:19 +00:00
|
|
|
#include "XUtils.h"
|
2021-03-03 22:00:34 +00:00
|
|
|
#include "generic/openzfs_sysctl.h"
|
2020-09-19 11:55:23 +00:00
|
|
|
#include "zfs/ZfsArcStats.h"
|
|
|
|
|
2014-11-27 18:27:34 +00:00
|
|
|
|
2015-12-13 03:11:35 +00:00
|
|
|
static int MIB_hw_physmem[2];
|
|
|
|
static int MIB_vm_stats_vm_v_page_count[4];
|
|
|
|
static int pageSize;
|
|
|
|
static int pageSizeKb;
|
|
|
|
|
2014-11-27 19:44:55 +00:00
|
|
|
static int MIB_vm_stats_vm_v_wire_count[4];
|
2015-12-13 03:11:35 +00:00
|
|
|
static int MIB_vm_stats_vm_v_active_count[4];
|
2014-11-27 20:31:39 +00:00
|
|
|
static int MIB_vm_stats_vm_v_cache_count[4];
|
2015-12-13 03:11:35 +00:00
|
|
|
static int MIB_vm_stats_vm_v_inactive_count[4];
|
|
|
|
static int MIB_vm_stats_vm_v_free_count[4];
|
|
|
|
|
|
|
|
static int MIB_vfs_bufspace[2];
|
|
|
|
|
2015-12-12 23:21:02 +00:00
|
|
|
static int MIB_kern_cp_time[2];
|
|
|
|
static int MIB_kern_cp_times[2];
|
2015-12-13 00:39:54 +00:00
|
|
|
static int kernelFScale;
|
2015-12-12 23:21:02 +00:00
|
|
|
|
PCP: support for 'dynamic columns' added at runtime
Implements support for arbitrary Performance Co-Pilot
metrics with per-process instance domains to form new
htop columns. The column-to-metric mappings are setup
using configuration files which will be documented via
man pages as part of a follow-up commit.
We provide an initial set of column configurations so
as to provide new capabilities to pcp-htop: including
configs for containers, open fd counts, scheduler run
queue time, tcp/udp bytes/calls sent/recv, delay acct,
virtual machine guests, detailed virtual memory, swap.
Note there is a change to the configuration file path
resolution algorithm introduced for 'dynamic meters'.
First, look in any custom PCP_HTOP_DIR location. Then
iterate, in priority order, users home directory, then
local sysadmins files in /etc/pcp/htop, then readonly
configuration files below /usr/share/pcp/htop. This
final location becomes the preferred place for our own
shipped meter and column files.
The Settings file (htoprc) writing code is updated to
not using the numeric identifier for dynamic columns.
The same strategy used for dynamic meters is used here
where we write Dynamic(name) so the name can be setup
once more at start. Regular (static) columns writing
to htoprc - i.e. numerically indexed - is unchanged.
2021-07-11 01:11:29 +00:00
|
|
|
ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* DynamicColumns, Hashtable* pidMatchList, uid_t userId) {
|
2016-03-06 03:59:39 +00:00
|
|
|
size_t len;
|
|
|
|
char errbuf[_POSIX2_LINE_MAX];
|
2016-02-02 14:53:02 +00:00
|
|
|
FreeBSDProcessList* fpl = xCalloc(1, sizeof(FreeBSDProcessList));
|
2014-11-27 20:31:39 +00:00
|
|
|
ProcessList* pl = (ProcessList*) fpl;
|
PCP: support for 'dynamic columns' added at runtime
Implements support for arbitrary Performance Co-Pilot
metrics with per-process instance domains to form new
htop columns. The column-to-metric mappings are setup
using configuration files which will be documented via
man pages as part of a follow-up commit.
We provide an initial set of column configurations so
as to provide new capabilities to pcp-htop: including
configs for containers, open fd counts, scheduler run
queue time, tcp/udp bytes/calls sent/recv, delay acct,
virtual machine guests, detailed virtual memory, swap.
Note there is a change to the configuration file path
resolution algorithm introduced for 'dynamic meters'.
First, look in any custom PCP_HTOP_DIR location. Then
iterate, in priority order, users home directory, then
local sysadmins files in /etc/pcp/htop, then readonly
configuration files below /usr/share/pcp/htop. This
final location becomes the preferred place for our own
shipped meter and column files.
The Settings file (htoprc) writing code is updated to
not using the numeric identifier for dynamic columns.
The same strategy used for dynamic meters is used here
where we write Dynamic(name) so the name can be setup
once more at start. Regular (static) columns writing
to htoprc - i.e. numerically indexed - is unchanged.
2021-07-11 01:11:29 +00:00
|
|
|
ProcessList_init(pl, Class(FreeBSDProcess), usersTable, dynamicMeters, DynamicColumns, pidMatchList, userId);
|
2014-11-27 18:27:34 +00:00
|
|
|
|
2015-12-13 03:11:35 +00:00
|
|
|
// physical memory in system: hw.physmem
|
|
|
|
// physical page size: hw.pagesize
|
|
|
|
// usable pagesize : vm.stats.vm.v_page_size
|
|
|
|
len = 2; sysctlnametomib("hw.physmem", MIB_hw_physmem, &len);
|
|
|
|
|
|
|
|
len = sizeof(pageSize);
|
2020-12-10 00:57:48 +00:00
|
|
|
if (sysctlbyname("vm.stats.vm.v_page_size", &pageSize, &len, NULL, 0) == -1)
|
|
|
|
CRT_fatalError("Cannot get pagesize by sysctl");
|
|
|
|
pageSizeKb = pageSize / ONE_K;
|
2015-12-13 03:11:35 +00:00
|
|
|
|
|
|
|
// usable page count vm.stats.vm.v_page_count
|
|
|
|
// actually usable memory : vm.stats.vm.v_page_count * vm.stats.vm.v_page_size
|
|
|
|
len = 4; sysctlnametomib("vm.stats.vm.v_page_count", MIB_vm_stats_vm_v_page_count, &len);
|
|
|
|
|
|
|
|
len = 4; sysctlnametomib("vm.stats.vm.v_wire_count", MIB_vm_stats_vm_v_wire_count, &len);
|
|
|
|
len = 4; sysctlnametomib("vm.stats.vm.v_active_count", MIB_vm_stats_vm_v_active_count, &len);
|
2014-11-27 20:31:39 +00:00
|
|
|
len = 4; sysctlnametomib("vm.stats.vm.v_cache_count", MIB_vm_stats_vm_v_cache_count, &len);
|
2015-12-13 03:11:35 +00:00
|
|
|
len = 4; sysctlnametomib("vm.stats.vm.v_inactive_count", MIB_vm_stats_vm_v_inactive_count, &len);
|
|
|
|
len = 4; sysctlnametomib("vm.stats.vm.v_free_count", MIB_vm_stats_vm_v_free_count, &len);
|
2015-09-30 20:04:26 +00:00
|
|
|
|
2015-12-13 03:11:35 +00:00
|
|
|
len = 2; sysctlnametomib("vfs.bufspace", MIB_vfs_bufspace, &len);
|
2014-11-27 20:31:39 +00:00
|
|
|
|
2019-09-03 18:21:33 +00:00
|
|
|
openzfs_sysctl_init(&fpl->zfs);
|
2019-07-07 23:27:00 +00:00
|
|
|
openzfs_sysctl_updateArcStats(&fpl->zfs);
|
2015-12-13 03:11:35 +00:00
|
|
|
|
2015-12-12 23:21:02 +00:00
|
|
|
int smp = 0;
|
2015-12-13 03:11:35 +00:00
|
|
|
len = sizeof(smp);
|
2015-12-12 23:21:02 +00:00
|
|
|
|
2015-12-13 03:11:35 +00:00
|
|
|
if (sysctlbyname("kern.smp.active", &smp, &len, NULL, 0) != 0 || len != sizeof(smp)) {
|
2015-12-12 23:21:02 +00:00
|
|
|
smp = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int cpus = 1;
|
2015-12-13 03:11:35 +00:00
|
|
|
len = sizeof(cpus);
|
2015-12-12 23:21:02 +00:00
|
|
|
|
|
|
|
if (smp) {
|
2015-12-13 03:11:35 +00:00
|
|
|
int err = sysctlbyname("kern.smp.cpus", &cpus, &len, NULL, 0);
|
2020-11-01 00:09:51 +00:00
|
|
|
if (err) {
|
|
|
|
cpus = 1;
|
|
|
|
}
|
2015-12-12 23:21:02 +00:00
|
|
|
} else {
|
|
|
|
cpus = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t sizeof_cp_time_array = sizeof(unsigned long) * CPUSTATES;
|
|
|
|
len = 2; sysctlnametomib("kern.cp_time", MIB_kern_cp_time, &len);
|
2021-11-16 14:27:10 +00:00
|
|
|
fpl->cp_time_o = xCalloc(CPUSTATES, sizeof(unsigned long));
|
|
|
|
fpl->cp_time_n = xCalloc(CPUSTATES, sizeof(unsigned long));
|
2015-12-12 23:21:02 +00:00
|
|
|
len = sizeof_cp_time_array;
|
|
|
|
|
2016-08-30 15:37:31 +00:00
|
|
|
// fetch initial single (or average) CPU clicks from kernel
|
2015-12-12 23:21:02 +00:00
|
|
|
sysctl(MIB_kern_cp_time, 2, fpl->cp_time_o, &len, NULL, 0);
|
|
|
|
|
2016-08-30 15:37:31 +00:00
|
|
|
// on smp box, fetch rest of initial CPU's clicks
|
2015-12-12 23:21:02 +00:00
|
|
|
if (cpus > 1) {
|
|
|
|
len = 2; sysctlnametomib("kern.cp_times", MIB_kern_cp_times, &len);
|
2016-02-02 14:53:02 +00:00
|
|
|
fpl->cp_times_o = xCalloc(cpus, sizeof_cp_time_array);
|
|
|
|
fpl->cp_times_n = xCalloc(cpus, sizeof_cp_time_array);
|
2015-12-12 23:21:02 +00:00
|
|
|
len = cpus * sizeof_cp_time_array;
|
|
|
|
sysctl(MIB_kern_cp_times, 2, fpl->cp_times_o, &len, NULL, 0);
|
|
|
|
}
|
|
|
|
|
2021-06-12 16:17:28 +00:00
|
|
|
pl->existingCPUs = MAXIMUM(cpus, 1);
|
|
|
|
// TODO: support offline CPUs and hot swapping
|
|
|
|
pl->activeCPUs = pl->existingCPUs;
|
2015-12-12 23:21:02 +00:00
|
|
|
|
|
|
|
if (cpus == 1 ) {
|
2020-10-31 21:14:27 +00:00
|
|
|
fpl->cpus = xRealloc(fpl->cpus, sizeof(CPUData));
|
2015-12-12 23:21:02 +00:00
|
|
|
} else {
|
2020-10-31 21:14:27 +00:00
|
|
|
// on smp we need CPUs + 1 to store averages too (as kernel kindly provides that as well)
|
2021-06-12 16:17:28 +00:00
|
|
|
fpl->cpus = xRealloc(fpl->cpus, (pl->existingCPUs + 1) * sizeof(CPUData));
|
2015-12-12 23:21:02 +00:00
|
|
|
}
|
|
|
|
|
2015-12-13 03:11:35 +00:00
|
|
|
|
|
|
|
len = sizeof(kernelFScale);
|
|
|
|
if (sysctlbyname("kern.fscale", &kernelFScale, &len, NULL, 0) == -1) {
|
2016-08-30 15:37:31 +00:00
|
|
|
//sane default for kernel provided CPU percentage scaling, at least on x86 machines, in case this sysctl call failed
|
2015-12-13 03:11:35 +00:00
|
|
|
kernelFScale = 2048;
|
|
|
|
}
|
|
|
|
|
2016-03-06 03:59:39 +00:00
|
|
|
fpl->kd = kvm_openfiles(NULL, "/dev/null", NULL, 0, errbuf);
|
|
|
|
if (fpl->kd == NULL) {
|
2021-01-07 15:08:43 +00:00
|
|
|
CRT_fatalError("kvm_openfiles() failed");
|
2016-03-06 03:59:39 +00:00
|
|
|
}
|
2015-12-13 03:11:35 +00:00
|
|
|
|
2014-11-27 20:31:39 +00:00
|
|
|
return pl;
|
|
|
|
}
|
2014-11-27 18:27:34 +00:00
|
|
|
|
2014-11-27 20:31:39 +00:00
|
|
|
void ProcessList_delete(ProcessList* this) {
|
|
|
|
const FreeBSDProcessList* fpl = (FreeBSDProcessList*) this;
|
2020-10-21 11:56:26 +00:00
|
|
|
|
2020-11-01 00:09:51 +00:00
|
|
|
if (fpl->kd) {
|
|
|
|
kvm_close(fpl->kd);
|
|
|
|
}
|
2015-09-30 20:04:26 +00:00
|
|
|
|
2016-01-03 21:59:44 +00:00
|
|
|
free(fpl->cp_time_o);
|
|
|
|
free(fpl->cp_time_n);
|
|
|
|
free(fpl->cp_times_o);
|
|
|
|
free(fpl->cp_times_n);
|
2016-01-04 20:04:50 +00:00
|
|
|
free(fpl->cpus);
|
2015-12-12 23:21:02 +00:00
|
|
|
|
2014-11-27 20:31:39 +00:00
|
|
|
ProcessList_done(this);
|
|
|
|
free(this);
|
2014-11-27 19:44:55 +00:00
|
|
|
}
|
|
|
|
|
2020-12-22 19:02:01 +00:00
|
|
|
static inline void FreeBSDProcessList_scanCPU(ProcessList* pl) {
|
2015-12-12 23:21:02 +00:00
|
|
|
const FreeBSDProcessList* fpl = (FreeBSDProcessList*) pl;
|
|
|
|
|
2021-06-12 16:17:28 +00:00
|
|
|
unsigned int cpus = pl->existingCPUs; // actual CPU count
|
|
|
|
unsigned int maxcpu = cpus; // max iteration (in case we have average + smp)
|
2015-12-12 23:21:02 +00:00
|
|
|
int cp_times_offset;
|
|
|
|
|
|
|
|
assert(cpus > 0);
|
|
|
|
|
|
|
|
size_t sizeof_cp_time_array;
|
|
|
|
|
2020-10-31 22:28:02 +00:00
|
|
|
unsigned long* cp_time_n; // old clicks state
|
|
|
|
unsigned long* cp_time_o; // current clicks state
|
2015-12-12 23:21:02 +00:00
|
|
|
|
|
|
|
unsigned long cp_time_d[CPUSTATES];
|
|
|
|
double cp_time_p[CPUSTATES];
|
|
|
|
|
|
|
|
// get averages or single CPU clicks
|
|
|
|
sizeof_cp_time_array = sizeof(unsigned long) * CPUSTATES;
|
|
|
|
sysctl(MIB_kern_cp_time, 2, fpl->cp_time_n, &sizeof_cp_time_array, NULL, 0);
|
|
|
|
|
|
|
|
// get rest of CPUs
|
|
|
|
if (cpus > 1) {
|
2020-10-31 21:14:27 +00:00
|
|
|
// on smp systems FreeBSD kernel concats all CPU states into one long array in
|
|
|
|
// kern.cp_times sysctl OID
|
|
|
|
// we store averages in fpl->cpus[0], and actual cores after that
|
|
|
|
maxcpu = cpus + 1;
|
|
|
|
sizeof_cp_time_array = cpus * sizeof(unsigned long) * CPUSTATES;
|
|
|
|
sysctl(MIB_kern_cp_times, 2, fpl->cp_times_n, &sizeof_cp_time_array, NULL, 0);
|
2015-12-12 23:21:02 +00:00
|
|
|
}
|
|
|
|
|
2021-02-17 16:38:35 +00:00
|
|
|
for (unsigned int i = 0; i < maxcpu; i++) {
|
2015-12-12 23:21:02 +00:00
|
|
|
if (cpus == 1) {
|
|
|
|
// single CPU box
|
|
|
|
cp_time_n = fpl->cp_time_n;
|
|
|
|
cp_time_o = fpl->cp_time_o;
|
|
|
|
} else {
|
|
|
|
if (i == 0 ) {
|
2020-10-31 21:14:27 +00:00
|
|
|
// average
|
|
|
|
cp_time_n = fpl->cp_time_n;
|
|
|
|
cp_time_o = fpl->cp_time_o;
|
2015-12-12 23:21:02 +00:00
|
|
|
} else {
|
2020-10-31 21:14:27 +00:00
|
|
|
// specific smp cores
|
|
|
|
cp_times_offset = i - 1;
|
|
|
|
cp_time_n = fpl->cp_times_n + (cp_times_offset * CPUSTATES);
|
|
|
|
cp_time_o = fpl->cp_times_o + (cp_times_offset * CPUSTATES);
|
2015-12-12 23:21:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// diff old vs new
|
2016-02-27 04:23:27 +00:00
|
|
|
unsigned long long total_o = 0;
|
|
|
|
unsigned long long total_n = 0;
|
|
|
|
unsigned long long total_d = 0;
|
2015-12-12 23:21:02 +00:00
|
|
|
for (int s = 0; s < CPUSTATES; s++) {
|
2020-10-31 21:14:27 +00:00
|
|
|
cp_time_d[s] = cp_time_n[s] - cp_time_o[s];
|
|
|
|
total_o += cp_time_o[s];
|
|
|
|
total_n += cp_time_n[s];
|
2015-12-12 23:21:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// totals
|
|
|
|
total_d = total_n - total_o;
|
2020-11-01 00:09:51 +00:00
|
|
|
if (total_d < 1 ) {
|
|
|
|
total_d = 1;
|
|
|
|
}
|
2015-12-12 23:21:02 +00:00
|
|
|
|
|
|
|
// save current state as old and calc percentages
|
|
|
|
for (int s = 0; s < CPUSTATES; ++s) {
|
2020-10-31 21:14:27 +00:00
|
|
|
cp_time_o[s] = cp_time_n[s];
|
|
|
|
cp_time_p[s] = ((double)cp_time_d[s]) / ((double)total_d) * 100;
|
2015-12-12 23:21:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
CPUData* cpuData = &(fpl->cpus[i]);
|
|
|
|
cpuData->userPercent = cp_time_p[CP_USER];
|
|
|
|
cpuData->nicePercent = cp_time_p[CP_NICE];
|
|
|
|
cpuData->systemPercent = cp_time_p[CP_SYS];
|
|
|
|
cpuData->irqPercent = cp_time_p[CP_INTR];
|
|
|
|
cpuData->systemAllPercent = cp_time_p[CP_SYS] + cp_time_p[CP_INTR];
|
2021-01-27 14:11:52 +00:00
|
|
|
// this one is not really used
|
|
|
|
//cpuData->idlePercent = cp_time_p[CP_IDLE];
|
2020-12-22 19:02:01 +00:00
|
|
|
|
|
|
|
cpuData->temperature = NAN;
|
|
|
|
cpuData->frequency = NAN;
|
|
|
|
|
2021-03-21 20:12:30 +00:00
|
|
|
const int coreId = (cpus == 1) ? 0 : ((int)i - 1);
|
2020-12-22 19:02:01 +00:00
|
|
|
if (coreId < 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// TODO: test with hyperthreading and multi-cpu systems
|
|
|
|
if (pl->settings->showCPUTemperature) {
|
|
|
|
int temperature;
|
|
|
|
size_t len = sizeof(temperature);
|
|
|
|
char mibBuffer[32];
|
|
|
|
xSnprintf(mibBuffer, sizeof(mibBuffer), "dev.cpu.%d.temperature", coreId);
|
|
|
|
int r = sysctlbyname(mibBuffer, &temperature, &len, NULL, 0);
|
|
|
|
if (r == 0)
|
|
|
|
cpuData->temperature = (double)(temperature - 2732) / 10.0; // convert from deci-Kelvin to Celsius
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: test with hyperthreading and multi-cpu systems
|
|
|
|
if (pl->settings->showCPUFrequency) {
|
|
|
|
int frequency;
|
|
|
|
size_t len = sizeof(frequency);
|
|
|
|
char mibBuffer[32];
|
|
|
|
xSnprintf(mibBuffer, sizeof(mibBuffer), "dev.cpu.%d.freq", coreId);
|
|
|
|
int r = sysctlbyname(mibBuffer, &frequency, &len, NULL, 0);
|
|
|
|
if (r == 0)
|
|
|
|
cpuData->frequency = frequency; // keep in MHz
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate max temperature and avg frequency for average meter and
|
|
|
|
// propagate frequency to all cores if only supplied for CPU 0
|
|
|
|
if (cpus > 1) {
|
|
|
|
if (pl->settings->showCPUTemperature) {
|
|
|
|
double maxTemp = NAN;
|
2021-02-17 16:38:35 +00:00
|
|
|
for (unsigned int i = 1; i < maxcpu; i++) {
|
2020-12-22 19:02:01 +00:00
|
|
|
const double coreTemp = fpl->cpus[i].temperature;
|
|
|
|
if (isnan(coreTemp))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
maxTemp = MAXIMUM(maxTemp, coreTemp);
|
|
|
|
}
|
|
|
|
|
|
|
|
fpl->cpus[0].temperature = maxTemp;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pl->settings->showCPUFrequency) {
|
|
|
|
const double coreZeroFreq = fpl->cpus[1].frequency;
|
|
|
|
double freqSum = coreZeroFreq;
|
|
|
|
if (!isnan(coreZeroFreq)) {
|
2021-02-17 16:38:35 +00:00
|
|
|
for (unsigned int i = 2; i < maxcpu; i++) {
|
2020-12-22 19:02:01 +00:00
|
|
|
if (isnan(fpl->cpus[i].frequency))
|
|
|
|
fpl->cpus[i].frequency = coreZeroFreq;
|
|
|
|
|
|
|
|
freqSum += fpl->cpus[i].frequency;
|
|
|
|
}
|
|
|
|
|
|
|
|
fpl->cpus[0].frequency = freqSum / (maxcpu - 1);
|
|
|
|
}
|
|
|
|
}
|
2015-12-12 23:21:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-27 19:44:55 +00:00
|
|
|
static inline void FreeBSDProcessList_scanMemoryInfo(ProcessList* pl) {
|
2015-12-13 03:11:35 +00:00
|
|
|
FreeBSDProcessList* fpl = (FreeBSDProcessList*) pl;
|
|
|
|
|
|
|
|
// @etosan:
|
|
|
|
// memory counter relationships seem to be these:
|
|
|
|
// total = active + wired + inactive + cache + free
|
|
|
|
// htop_used (unavail to anybody) = active + wired
|
|
|
|
// htop_cache (for cache meter) = buffers + cache
|
|
|
|
// user_free (avail to procs) = buffers + inactive + cache + free
|
|
|
|
//
|
|
|
|
// with ZFS ARC situation becomes bit muddled, as ARC behaves like "user_free"
|
|
|
|
// and belongs into cache, but is reported as wired by kernel
|
|
|
|
//
|
|
|
|
// htop_used = active + (wired - arc)
|
|
|
|
// htop_cache = buffers + cache + arc
|
2018-12-24 12:51:01 +00:00
|
|
|
u_long totalMem;
|
|
|
|
u_int memActive, memWire, cachedMem;
|
|
|
|
long buffersMem;
|
|
|
|
size_t len;
|
2015-12-13 03:11:35 +00:00
|
|
|
|
|
|
|
//disabled for now, as it is always smaller than phycal amount of memory...
|
|
|
|
//...to avoid "where is my memory?" questions
|
|
|
|
//sysctl(MIB_vm_stats_vm_v_page_count, 4, &(pl->totalMem), &len, NULL, 0);
|
|
|
|
//pl->totalMem *= pageSizeKb;
|
2018-12-24 12:51:01 +00:00
|
|
|
len = sizeof(totalMem);
|
|
|
|
sysctl(MIB_hw_physmem, 2, &(totalMem), &len, NULL, 0);
|
|
|
|
totalMem /= 1024;
|
|
|
|
pl->totalMem = totalMem;
|
|
|
|
|
|
|
|
len = sizeof(memActive);
|
|
|
|
sysctl(MIB_vm_stats_vm_v_active_count, 4, &(memActive), &len, NULL, 0);
|
|
|
|
memActive *= pageSizeKb;
|
|
|
|
fpl->memActive = memActive;
|
|
|
|
|
|
|
|
len = sizeof(memWire);
|
|
|
|
sysctl(MIB_vm_stats_vm_v_wire_count, 4, &(memWire), &len, NULL, 0);
|
|
|
|
memWire *= pageSizeKb;
|
|
|
|
fpl->memWire = memWire;
|
|
|
|
|
|
|
|
len = sizeof(buffersMem);
|
|
|
|
sysctl(MIB_vfs_bufspace, 2, &(buffersMem), &len, NULL, 0);
|
|
|
|
buffersMem /= 1024;
|
|
|
|
pl->buffersMem = buffersMem;
|
|
|
|
|
|
|
|
len = sizeof(cachedMem);
|
|
|
|
sysctl(MIB_vm_stats_vm_v_cache_count, 4, &(cachedMem), &len, NULL, 0);
|
|
|
|
cachedMem *= pageSizeKb;
|
|
|
|
pl->cachedMem = cachedMem;
|
2015-09-30 20:04:26 +00:00
|
|
|
|
2019-07-07 23:27:00 +00:00
|
|
|
if (fpl->zfs.enabled) {
|
|
|
|
fpl->memWire -= fpl->zfs.size;
|
|
|
|
pl->cachedMem += fpl->zfs.size;
|
2015-12-13 03:11:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pl->usedMem = fpl->memActive + fpl->memWire;
|
|
|
|
|
2014-11-27 20:31:39 +00:00
|
|
|
struct kvm_swap swap[16];
|
2020-10-03 20:00:27 +00:00
|
|
|
int nswap = kvm_getswapinfo(fpl->kd, swap, ARRAYSIZE(swap), 0);
|
2014-11-27 19:44:55 +00:00
|
|
|
pl->totalSwap = 0;
|
2014-11-27 20:31:39 +00:00
|
|
|
pl->usedSwap = 0;
|
|
|
|
for (int i = 0; i < nswap; i++) {
|
|
|
|
pl->totalSwap += swap[i].ksw_total;
|
|
|
|
pl->usedSwap += swap[i].ksw_used;
|
|
|
|
}
|
|
|
|
pl->totalSwap *= pageSizeKb;
|
|
|
|
pl->usedSwap *= pageSizeKb;
|
2014-11-27 18:27:34 +00:00
|
|
|
}
|
|
|
|
|
2021-05-18 20:30:56 +00:00
|
|
|
static void FreeBSDProcessList_updateExe(const struct kinfo_proc* kproc, Process* proc) {
|
2021-06-13 09:18:12 +00:00
|
|
|
if (Process_isKernelThread(proc)) {
|
2021-05-18 20:30:56 +00:00
|
|
|
Process_updateExe(proc, NULL);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-06-13 09:18:12 +00:00
|
|
|
const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, kproc->ki_pid };
|
|
|
|
char buffer[2048];
|
|
|
|
size_t size = sizeof(buffer);
|
|
|
|
if (sysctl(mib, 4, buffer, &size, NULL, 0) != 0) {
|
2021-05-18 20:30:56 +00:00
|
|
|
Process_updateExe(proc, NULL);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Process_updateExe(proc, buffer);
|
|
|
|
}
|
|
|
|
|
2021-05-25 17:07:00 +00:00
|
|
|
static void FreeBSDProcessList_updateCwd(const struct kinfo_proc* kproc, Process* proc) {
|
|
|
|
const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_CWD, kproc->ki_pid };
|
|
|
|
char buffer[2048];
|
|
|
|
size_t size = sizeof(buffer);
|
|
|
|
if (sysctl(mib, 4, buffer, &size, NULL, 0) != 0) {
|
|
|
|
free(proc->procCwd);
|
|
|
|
proc->procCwd = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Kernel threads return an empty buffer */
|
|
|
|
if (buffer[0] == '\0') {
|
|
|
|
free(proc->procCwd);
|
|
|
|
proc->procCwd = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
free_and_xStrdup(&proc->procCwd, buffer);
|
|
|
|
}
|
|
|
|
|
2021-05-18 20:30:56 +00:00
|
|
|
static void FreeBSDProcessList_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) {
|
|
|
|
Process_updateComm(proc, kproc->ki_comm);
|
|
|
|
|
2015-03-17 02:01:48 +00:00
|
|
|
char** argv = kvm_getargv(kd, kproc, 0);
|
2021-05-18 20:30:56 +00:00
|
|
|
if (!argv || !argv[0]) {
|
|
|
|
Process_updateCmdline(proc, kproc->ki_comm, 0, strlen(kproc->ki_comm));
|
|
|
|
return;
|
2015-03-17 02:01:48 +00:00
|
|
|
}
|
2021-05-18 20:30:56 +00:00
|
|
|
|
|
|
|
size_t len = 0;
|
2015-03-17 02:01:48 +00:00
|
|
|
for (int i = 0; argv[i]; i++) {
|
|
|
|
len += strlen(argv[i]) + 1;
|
|
|
|
}
|
2021-05-18 20:30:56 +00:00
|
|
|
|
|
|
|
char* cmdline = xMalloc(len);
|
|
|
|
char* at = cmdline;
|
|
|
|
int end = 0;
|
2015-03-17 02:01:48 +00:00
|
|
|
for (int i = 0; argv[i]; i++) {
|
|
|
|
at = stpcpy(at, argv[i]);
|
2021-05-18 20:30:56 +00:00
|
|
|
if (end == 0) {
|
|
|
|
end = at - cmdline;
|
2015-03-17 02:01:48 +00:00
|
|
|
}
|
2021-05-18 20:30:56 +00:00
|
|
|
*at++ = ' ';
|
2015-03-17 02:01:48 +00:00
|
|
|
}
|
|
|
|
at--;
|
|
|
|
*at = '\0';
|
2021-05-18 20:30:56 +00:00
|
|
|
|
|
|
|
Process_updateCmdline(proc, cmdline, 0, end);
|
2021-10-02 11:27:01 +00:00
|
|
|
|
|
|
|
free(cmdline);
|
2015-03-17 02:01:48 +00:00
|
|
|
}
|
2014-11-27 19:44:55 +00:00
|
|
|
|
2020-10-21 17:11:19 +00:00
|
|
|
static char* FreeBSDProcessList_readJailName(const struct kinfo_proc* kproc) {
|
2021-01-27 14:11:56 +00:00
|
|
|
if (kproc->ki_jid == 0)
|
|
|
|
return xStrdup("-");
|
2015-09-30 20:04:26 +00:00
|
|
|
|
2021-01-27 14:11:56 +00:00
|
|
|
char jnamebuf[MAXHOSTNAMELEN] = {0};
|
|
|
|
struct iovec jiov[4];
|
2020-11-21 23:57:18 +00:00
|
|
|
|
2020-10-04 12:30:35 +00:00
|
|
|
IGNORE_WCASTQUAL_BEGIN
|
2021-01-27 14:11:56 +00:00
|
|
|
*(const void**)&jiov[0].iov_base = "jid";
|
|
|
|
jiov[0].iov_len = sizeof("jid");
|
|
|
|
jiov[1].iov_base = (void*) &kproc->ki_jid;
|
|
|
|
jiov[1].iov_len = sizeof(kproc->ki_jid);
|
|
|
|
*(const void**)&jiov[2].iov_base = "name";
|
|
|
|
jiov[2].iov_len = sizeof("name");
|
|
|
|
jiov[3].iov_base = jnamebuf;
|
|
|
|
jiov[3].iov_len = sizeof(jnamebuf);
|
2020-10-04 12:30:35 +00:00
|
|
|
IGNORE_WCASTQUAL_END
|
2020-11-21 23:57:18 +00:00
|
|
|
|
2021-01-27 14:11:56 +00:00
|
|
|
int jid = jail_get(jiov, 4, 0);
|
|
|
|
if (jid == kproc->ki_jid)
|
|
|
|
return xStrdup(jnamebuf);
|
2020-11-21 23:57:18 +00:00
|
|
|
|
2021-01-27 14:11:56 +00:00
|
|
|
return NULL;
|
2015-09-30 20:04:26 +00:00
|
|
|
}
|
|
|
|
|
2020-10-27 20:26:28 +00:00
|
|
|
void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
|
|
|
|
FreeBSDProcessList* fpl = (FreeBSDProcessList*) super;
|
|
|
|
const Settings* settings = super->settings;
|
2015-03-17 02:01:48 +00:00
|
|
|
bool hideKernelThreads = settings->hideKernelThreads;
|
|
|
|
bool hideUserlandThreads = settings->hideUserlandThreads;
|
2015-09-30 20:04:26 +00:00
|
|
|
|
2019-07-07 23:27:00 +00:00
|
|
|
openzfs_sysctl_updateArcStats(&fpl->zfs);
|
2020-10-27 20:26:28 +00:00
|
|
|
FreeBSDProcessList_scanMemoryInfo(super);
|
2020-12-22 19:02:01 +00:00
|
|
|
FreeBSDProcessList_scanCPU(super);
|
2015-09-30 20:04:26 +00:00
|
|
|
|
2020-10-13 14:03:37 +00:00
|
|
|
// in pause mode only gather global data for meters (CPU/memory/...)
|
2020-11-01 00:09:51 +00:00
|
|
|
if (pauseProcessUpdate) {
|
2020-10-13 14:03:37 +00:00
|
|
|
return;
|
2020-11-01 00:09:51 +00:00
|
|
|
}
|
2020-10-13 14:03:37 +00:00
|
|
|
|
2015-03-17 02:01:48 +00:00
|
|
|
int count = 0;
|
2021-01-27 14:11:50 +00:00
|
|
|
const struct kinfo_proc* kprocs = kvm_getprocs(fpl->kd, KERN_PROC_PROC, 0, &count);
|
2015-09-30 20:04:26 +00:00
|
|
|
|
2015-03-17 02:01:48 +00:00
|
|
|
for (int i = 0; i < count; i++) {
|
2021-01-27 14:11:50 +00:00
|
|
|
const struct kinfo_proc* kproc = &kprocs[i];
|
2015-03-17 02:01:48 +00:00
|
|
|
bool preExisting = false;
|
2020-10-27 20:26:28 +00:00
|
|
|
Process* proc = ProcessList_getProcess(super, kproc->ki_pid, &preExisting, FreeBSDProcess_new);
|
2015-03-17 02:01:48 +00:00
|
|
|
FreeBSDProcess* fp = (FreeBSDProcess*) proc;
|
|
|
|
|
|
|
|
if (!preExisting) {
|
2015-09-30 20:04:26 +00:00
|
|
|
fp->jid = kproc->ki_jid;
|
|
|
|
proc->pid = kproc->ki_pid;
|
2021-06-13 09:20:54 +00:00
|
|
|
proc->isKernelThread = kproc->ki_pid != 1 && (kproc->ki_flag & P_SYSTEM);
|
2021-04-10 12:08:26 +00:00
|
|
|
proc->isUserlandThread = false;
|
2015-12-17 07:48:53 +00:00
|
|
|
proc->ppid = kproc->ki_ppid;
|
2015-03-17 02:01:48 +00:00
|
|
|
proc->tpgid = kproc->ki_tpgid;
|
|
|
|
proc->tgid = kproc->ki_pid;
|
|
|
|
proc->session = kproc->ki_sid;
|
|
|
|
proc->pgrp = kproc->ki_pgid;
|
|
|
|
proc->st_uid = kproc->ki_uid;
|
|
|
|
proc->starttime_ctime = kproc->ki_start.tv_sec;
|
2020-10-13 12:26:40 +00:00
|
|
|
Process_fillStarttimeBuffer(proc);
|
2020-10-27 20:26:28 +00:00
|
|
|
proc->user = UsersTable_getRef(super->usersTable, proc->st_uid);
|
|
|
|
ProcessList_add(super, proc);
|
2021-04-18 16:10:04 +00:00
|
|
|
|
2021-05-18 20:30:56 +00:00
|
|
|
FreeBSDProcessList_updateExe(kproc, proc);
|
|
|
|
FreeBSDProcessList_updateProcessName(fpl->kd, kproc, proc);
|
2021-04-18 16:10:04 +00:00
|
|
|
|
2021-05-25 17:07:00 +00:00
|
|
|
if (settings->flags & PROCESS_FLAG_CWD) {
|
|
|
|
FreeBSDProcessList_updateCwd(kproc, proc);
|
|
|
|
}
|
|
|
|
|
2015-09-30 20:04:26 +00:00
|
|
|
fp->jname = FreeBSDProcessList_readJailName(kproc);
|
2021-03-21 18:40:56 +00:00
|
|
|
|
|
|
|
proc->tty_nr = kproc->ki_tdev;
|
|
|
|
const char* name = (kproc->ki_tdev != NODEV) ? devname(kproc->ki_tdev, S_IFCHR) : NULL;
|
|
|
|
if (!name) {
|
|
|
|
free(proc->tty_name);
|
|
|
|
proc->tty_name = NULL;
|
|
|
|
} else {
|
|
|
|
free_and_xStrdup(&proc->tty_name, name);
|
|
|
|
}
|
2015-03-17 02:01:48 +00:00
|
|
|
} else {
|
2020-10-31 19:52:20 +00:00
|
|
|
if (fp->jid != kproc->ki_jid) {
|
2016-08-30 15:37:31 +00:00
|
|
|
// process can enter jail anytime
|
2015-10-06 17:50:19 +00:00
|
|
|
fp->jid = kproc->ki_jid;
|
|
|
|
free(fp->jname);
|
|
|
|
fp->jname = FreeBSDProcessList_readJailName(kproc);
|
|
|
|
}
|
2020-11-21 23:55:42 +00:00
|
|
|
// if there are reapers in the system, process can get reparented anytime
|
|
|
|
proc->ppid = kproc->ki_ppid;
|
2020-10-31 19:52:20 +00:00
|
|
|
if (proc->st_uid != kproc->ki_uid) {
|
2016-08-30 15:37:31 +00:00
|
|
|
// some processes change users (eg. to lower privs)
|
2015-12-11 10:01:24 +00:00
|
|
|
proc->st_uid = kproc->ki_uid;
|
2020-10-27 20:26:28 +00:00
|
|
|
proc->user = UsersTable_getRef(super->usersTable, proc->st_uid);
|
2015-12-11 10:01:24 +00:00
|
|
|
}
|
2015-03-17 02:01:48 +00:00
|
|
|
if (settings->updateProcessNames) {
|
2021-05-18 20:30:56 +00:00
|
|
|
FreeBSDProcessList_updateProcessName(fpl->kd, kproc, proc);
|
2015-03-17 02:01:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-13 03:11:35 +00:00
|
|
|
// from FreeBSD source /src/usr.bin/top/machine.c
|
2020-12-10 00:57:48 +00:00
|
|
|
proc->m_virt = kproc->ki_size / ONE_K;
|
|
|
|
proc->m_resident = kproc->ki_rssize * pageSizeKb;
|
2015-03-17 02:01:48 +00:00
|
|
|
proc->nlwp = kproc->ki_numthreads;
|
|
|
|
proc->time = (kproc->ki_runtime + 5000) / 10000;
|
2015-09-30 20:04:26 +00:00
|
|
|
|
2015-12-13 00:39:54 +00:00
|
|
|
proc->percent_cpu = 100.0 * ((double)kproc->ki_pctcpu / (double)kernelFScale);
|
2020-12-10 00:57:48 +00:00
|
|
|
proc->percent_mem = 100.0 * proc->m_resident / (double)(super->totalMem);
|
2016-02-13 15:24:57 +00:00
|
|
|
|
2021-03-30 07:58:09 +00:00
|
|
|
if (kproc->ki_stat == SRUN && kproc->ki_oncpu != NOCPU) {
|
2021-07-14 17:15:09 +00:00
|
|
|
proc->processor = kproc->ki_oncpu;
|
2021-03-30 07:58:09 +00:00
|
|
|
} else {
|
2021-07-14 17:15:09 +00:00
|
|
|
proc->processor = kproc->ki_lastcpu;
|
2021-03-30 07:58:09 +00:00
|
|
|
}
|
2021-01-27 14:11:58 +00:00
|
|
|
|
2021-01-27 14:12:04 +00:00
|
|
|
proc->majflt = kproc->ki_cow;
|
|
|
|
|
2015-03-17 02:01:48 +00:00
|
|
|
proc->priority = kproc->ki_pri.pri_level - PZERO;
|
2015-09-30 20:04:26 +00:00
|
|
|
|
2021-01-27 14:11:50 +00:00
|
|
|
if (String_eq("intr", kproc->ki_comm) && (kproc->ki_flag & P_SYSTEM)) {
|
2015-12-13 03:11:35 +00:00
|
|
|
proc->nice = 0; //@etosan: intr kernel process (not thread) has weird nice value
|
2015-09-30 20:04:26 +00:00
|
|
|
} else if (kproc->ki_pri.pri_class == PRI_TIMESHARE) {
|
2015-03-17 02:01:48 +00:00
|
|
|
proc->nice = kproc->ki_nice - NZERO;
|
|
|
|
} else if (PRI_IS_REALTIME(kproc->ki_pri.pri_class)) {
|
|
|
|
proc->nice = PRIO_MIN - 1 - (PRI_MAX_REALTIME - kproc->ki_pri.pri_level);
|
|
|
|
} else {
|
|
|
|
proc->nice = PRIO_MAX + 1 + kproc->ki_pri.pri_level - PRI_MIN_IDLE;
|
|
|
|
}
|
|
|
|
|
2021-10-11 22:45:09 +00:00
|
|
|
/* Taken from: https://github.com/freebsd/freebsd-src/blob/1ad2d87778970582854082bcedd2df0394fd4933/sys/sys/proc.h#L851 */
|
2015-03-17 02:01:48 +00:00
|
|
|
switch (kproc->ki_stat) {
|
2021-10-11 22:45:09 +00:00
|
|
|
case SIDL: proc->state = IDLE; break;
|
|
|
|
case SRUN: proc->state = RUNNING; break;
|
|
|
|
case SSLEEP: proc->state = SLEEPING; break;
|
|
|
|
case SSTOP: proc->state = STOPPED; break;
|
|
|
|
case SZOMB: proc->state = ZOMBIE; break;
|
|
|
|
case SWAIT: proc->state = WAITING; break;
|
|
|
|
case SLOCK: proc->state = BLOCKED; break;
|
|
|
|
default: proc->state = UNKNOWN;
|
2015-03-17 02:01:48 +00:00
|
|
|
}
|
|
|
|
|
2020-10-21 17:11:26 +00:00
|
|
|
if (Process_isKernelThread(proc))
|
2020-10-27 20:26:28 +00:00
|
|
|
super->kernelThreads++;
|
2015-09-30 20:04:26 +00:00
|
|
|
|
2021-06-13 09:19:50 +00:00
|
|
|
proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
|
|
|
|
|
2020-10-27 20:26:28 +00:00
|
|
|
super->totalTasks++;
|
2021-10-11 22:45:09 +00:00
|
|
|
if (proc->state == RUNNING)
|
2020-10-27 20:26:28 +00:00
|
|
|
super->runningTasks++;
|
2015-03-17 02:01:48 +00:00
|
|
|
proc->updated = true;
|
|
|
|
}
|
2014-11-27 18:27:34 +00:00
|
|
|
}
|
2021-06-12 20:04:37 +00:00
|
|
|
|
|
|
|
bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
|
|
|
|
assert(id < super->existingCPUs);
|
|
|
|
|
|
|
|
// TODO: support offline CPUs and hot swapping
|
|
|
|
(void) super; (void) id;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|