Move all APIs with a virHostCPU name prefix out into new util/virhostcpu.h & util/virhostcpu.c files Signed-off-by: Daniel P. Berrange <berrange@xxxxxxxxxx> --- cfg.mk | 6 +- po/POTFILES.in | 1 + src/Makefile.am | 3 +- src/libvirt_linux.syms | 2 +- src/lxc/lxc_controller.c | 2 +- src/lxc/lxc_driver.c | 1 + src/nodeinfo.c | 1171 +----------------- src/nodeinfo.h | 13 - src/openvz/openvz_driver.c | 1 + src/qemu/qemu_driver.c | 1 + src/qemu/qemu_process.c | 2 +- src/uml/uml_driver.c | 1 + src/util/vircgroup.c | 3 +- src/util/virhostcpu.c | 1299 ++++++++++++++++++++ src/util/virhostcpu.h | 54 + src/{nodeinfopriv.h => util/virhostcpupriv.h} | 12 +- [...snipped countless more test data renames....] rename tests/{nodeinfodata => virhostcpudata}/linux-x86_64-test7.expected (100%) rename tests/{nodeinfodata => virhostcpudata}/linux-x86_64-test8.cpuinfo (100%) rename tests/{nodeinfodata => virhostcpudata}/linux-x86_64-test8.expected (100%) rename tests/{nodeinfomock.c => virhostcpumock.c} (95%) rename tests/{nodeinfotest.c => virhostcputest.c} (89%) diff --git a/cfg.mk b/cfg.mk index cd3b515..b1b8bdc 100644 --- a/cfg.mk +++ b/cfg.mk @@ -1161,7 +1161,7 @@ exclude_file_name_regexp--sc_prohibit_close = \ (\.p[yl]$$|\.spec\.in$$|^docs/|^(src/util/virfile\.c|src/libvirt-stream\.c|tests/vir.+mock\.c)$$) exclude_file_name_regexp--sc_prohibit_empty_lines_at_EOF = \ - (^tests/(qemuhelp|nodeinfo|virpcitest)data/|\.diff$$) + (^tests/(qemuhelp|virhostcpu|virpcitest)data/|\.diff$$) _src2=src/(util/vircommand|libvirt|lxc/lxc_controller|locking/lock_daemon|logging/log_daemon) exclude_file_name_regexp--sc_prohibit_fork_wrappers = \ @@ -1208,7 +1208,7 @@ exclude_file_name_regexp--sc_require_config_h_first = \ ^(examples/|tools/virsh-edit\.c$$) exclude_file_name_regexp--sc_trailing_blank = \ - /qemuhelpdata/|/sysinfodata/.*\.data|/nodeinfodata/.*\.cpuinfo$$ + /qemuhelpdata/|/sysinfodata/.*\.data|/virhostcpudata/.*\.cpuinfo$$ exclude_file_name_regexp--sc_unmarked_diagnostics = \ ^(docs/apibuild.py|tests/virt-aa-helper-test)$$ @@ -1239,7 +1239,7 @@ exclude_file_name_regexp--sc_prohibit_mixed_case_abbreviations = \ ^src/(vbox/vbox_CAPI.*.h|esx/esx_vi.(c|h)|esx/esx_storage_backend_iscsi.c)$$ exclude_file_name_regexp--sc_prohibit_empty_first_line = \ - ^(README|daemon/THREADS\.txt|src/esx/README|docs/library.xen|tests/(vmwarever|nodeinfo)data/.*)$$ + ^(README|daemon/THREADS\.txt|src/esx/README|docs/library.xen|tests/(vmwarever|virhostcpu)data/.*)$$ exclude_file_name_regexp--sc_prohibit_useless_translation = \ ^tests/virpolkittest.c diff --git a/po/POTFILES.in b/po/POTFILES.in index ca23526..b0cd8cf 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -195,6 +195,7 @@ src/util/virfile.c src/util/virfirewall.c src/util/virhash.c src/util/virhook.c +src/util/virhostcpu.c src/util/virhostdev.c src/util/viridentity.c src/util/virinitctl.c diff --git a/src/Makefile.am b/src/Makefile.am index f5e57c0..8d2a8e0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -118,6 +118,7 @@ UTIL_SOURCES = \ util/virhash.c util/virhash.h \ util/virhashcode.c util/virhashcode.h \ util/virhook.c util/virhook.h \ + util/virhostcpu.c util/virhostcpu.h util/virhostcpupriv.h \ util/virhostdev.c util/virhostdev.h \ util/viridentity.c util/viridentity.h \ util/virinitctl.c util/virinitctl.h \ @@ -190,7 +191,7 @@ util/virkeymaps.h: $(srcdir)/util/keymaps.csv \ <$(srcdir)/util/keymaps.csv >util/virkeymaps.h # Internal generic driver infrastructure -NODE_INFO_SOURCES = nodeinfo.h nodeinfo.c nodeinfopriv.h +NODE_INFO_SOURCES = nodeinfo.h nodeinfo.c DATATYPES_SOURCES = datatypes.h datatypes.c DRIVER_SOURCES = \ driver.c driver.h \ diff --git a/src/libvirt_linux.syms b/src/libvirt_linux.syms index a2fc076..a864b78 100644 --- a/src/libvirt_linux.syms +++ b/src/libvirt_linux.syms @@ -2,7 +2,7 @@ # Linux-specific private symbols. # -# nodeinfo.h +# util/virhostcpu.h virHostCPUGetInfoPopulateLinux; virHostCPUGetStatsLinux; virHostCPUSetSysFSSystemPathLinux; diff --git a/src/lxc/lxc_controller.c b/src/lxc/lxc_controller.c index 45ad25e..d59192d 100644 --- a/src/lxc/lxc_controller.c +++ b/src/lxc/lxc_controller.c @@ -59,7 +59,7 @@ #include "virfile.h" #include "virpidfile.h" #include "vircommand.h" -#include "nodeinfo.h" +#include "virhostcpu.h" #include "virrandom.h" #include "virprocess.h" #include "virnuma.h" diff --git a/src/lxc/lxc_driver.c b/src/lxc/lxc_driver.c index c5e9a5f..2cecabc 100644 --- a/src/lxc/lxc_driver.c +++ b/src/lxc/lxc_driver.c @@ -51,6 +51,7 @@ #include "virnetdevveth.h" #include "virnetdevopenvswitch.h" #include "nodeinfo.h" +#include "virhostcpu.h" #include "viruuid.h" #include "virstats.h" #include "virhook.h" diff --git a/src/nodeinfo.c b/src/nodeinfo.c index a3d0024..8bf495f 100644 --- a/src/nodeinfo.c +++ b/src/nodeinfo.c @@ -46,10 +46,9 @@ # include <sys/resource.h> #endif -#include "c-ctype.h" #include "viralloc.h" -#include "nodeinfopriv.h" #include "nodeinfo.h" +#include "virhostcpu.h" #include "physmem.h" #include "virerror.h" #include "count-one-bits.h" @@ -66,126 +65,9 @@ VIR_LOG_INIT("nodeinfo"); -#if defined(__FreeBSD__) || defined(__APPLE__) -static int -virHostCPUGetCountAppleFreeBSD(void) -{ - int ncpu_mib[2] = { CTL_HW, HW_NCPU }; - unsigned long ncpu; - size_t ncpu_len = sizeof(ncpu); - - if (sysctl(ncpu_mib, 2, &ncpu, &ncpu_len, NULL, 0) == -1) { - virReportSystemError(errno, "%s", _("Cannot obtain CPU count")); - return -1; - } - - return ncpu; -} -#endif /* defined(__FreeBSD__) || defined(__APPLE__) */ - #ifdef __FreeBSD__ -# define BSD_CPU_STATS_ALL 4 # define BSD_MEMORY_STATS_ALL 4 -# define TICK_TO_NSEC (1000ull * 1000ull * 1000ull / (stathz ? stathz : hz)) - -static int -virHostCPUGetStatsFreebsd(int cpuNum, - virNodeCPUStatsPtr params, - int *nparams) -{ - const char *sysctl_name; - long *cpu_times; - struct clockinfo clkinfo; - size_t i, j, cpu_times_size, clkinfo_size; - int cpu_times_num, offset, hz, stathz, ret = -1; - struct field_cpu_map { - const char *field; - int idx[CPUSTATES]; - } cpu_map[] = { - {VIR_NODE_CPU_STATS_KERNEL, {CP_SYS}}, - {VIR_NODE_CPU_STATS_USER, {CP_USER, CP_NICE}}, - {VIR_NODE_CPU_STATS_IDLE, {CP_IDLE}}, - {VIR_NODE_CPU_STATS_INTR, {CP_INTR}}, - {NULL, {0}} - }; - - if ((*nparams) == 0) { - *nparams = BSD_CPU_STATS_ALL; - return 0; - } - - if ((*nparams) != BSD_CPU_STATS_ALL) { - virReportInvalidArg(*nparams, - _("nparams in %s must be equal to %d"), - __FUNCTION__, BSD_CPU_STATS_ALL); - return -1; - } - - clkinfo_size = sizeof(clkinfo); - if (sysctlbyname("kern.clockrate", &clkinfo, &clkinfo_size, NULL, 0) < 0) { - virReportSystemError(errno, - _("sysctl failed for '%s'"), - "kern.clockrate"); - return -1; - } - - stathz = clkinfo.stathz; - hz = clkinfo.hz; - - if (cpuNum == VIR_NODE_CPU_STATS_ALL_CPUS) { - sysctl_name = "kern.cp_time"; - cpu_times_num = 1; - offset = 0; - } else { - sysctl_name = "kern.cp_times"; - cpu_times_num = virHostCPUGetCountAppleFreeBSD(); - - if (cpuNum >= cpu_times_num) { - virReportInvalidArg(cpuNum, - _("Invalid cpuNum in %s"), - __FUNCTION__); - return -1; - } - - offset = cpu_times_num * CPUSTATES; - } - - cpu_times_size = sizeof(long) * cpu_times_num * CPUSTATES; - - if (VIR_ALLOC_N(cpu_times, cpu_times_num * CPUSTATES) < 0) - goto cleanup; - - if (sysctlbyname(sysctl_name, cpu_times, &cpu_times_size, NULL, 0) < 0) { - virReportSystemError(errno, - _("sysctl failed for '%s'"), - sysctl_name); - goto cleanup; - } - - for (i = 0; cpu_map[i].field != NULL; i++) { - virNodeCPUStatsPtr param = ¶ms[i]; - - if (virStrcpyStatic(param->field, cpu_map[i].field) == NULL) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Field '%s' too long for destination"), - cpu_map[i].field); - goto cleanup; - } - - param->value = 0; - for (j = 0; j < ARRAY_CARDINALITY(cpu_map[i].idx); j++) - param->value += cpu_times[offset + cpu_map[i].idx[j]] * TICK_TO_NSEC; - } - - ret = 0; - - cleanup: - VIR_FREE(cpu_times); - - return ret; -} - static int virHostMemGetStatsFreeBSD(virNodeMemoryStatsPtr params, int *nparams) @@ -263,26 +145,13 @@ virHostMemGetStatsFreeBSD(virNodeMemoryStatsPtr params, #ifdef __linux__ # define SYSFS_SYSTEM_PATH "/sys/devices/system" -# define CPUINFO_PATH "/proc/cpuinfo" -# define PROCSTAT_PATH "/proc/stat" # define MEMINFO_PATH "/proc/meminfo" # define SYSFS_MEMORY_SHARED_PATH "/sys/kernel/mm/ksm" # define SYSFS_THREAD_SIBLINGS_LIST_LENGTH_MAX 8192 -# define LINUX_NB_CPU_STATS 4 # define LINUX_NB_MEMORY_STATS_ALL 4 # define LINUX_NB_MEMORY_STATS_CELL 2 -static const char *sysfs_system_path = SYSFS_SYSTEM_PATH; - -void virHostCPUSetSysFSSystemPathLinux(const char *path) -{ - if (path) - sysfs_system_path = path; - else - sysfs_system_path = SYSFS_SYSTEM_PATH; -} - /* Return the positive decimal contents of the given * DIR/cpu%u/FILE, or -1 on error. If DEFAULT_VALUE is non-negative * and the file could not be found, return that instead of an error; @@ -329,635 +198,6 @@ virNodeGetCpuValue(const char *dir, unsigned int cpu, const char *file, return value; } -static unsigned long -virHostCPUCountThreadSiblings(const char *dir, unsigned int cpu) -{ - unsigned long ret = 0; - char *path; - char *str = NULL; - size_t i; - - if (virAsprintf(&path, "%s/cpu%u/topology/thread_siblings", - dir, cpu) < 0) - return 0; - - if (!virFileExists(path)) { - /* If file doesn't exist, then pretend our only - * sibling is ourself */ - ret = 1; - goto cleanup; - } - - if (virFileReadAll(path, SYSFS_THREAD_SIBLINGS_LIST_LENGTH_MAX, &str) < 0) - goto cleanup; - - for (i = 0; str[i] != '\0'; i++) { - if (c_isxdigit(str[i])) - ret += count_one_bits(virHexToBin(str[i])); - } - - cleanup: - VIR_FREE(str); - VIR_FREE(path); - return ret; -} - -static int -virHostCPUParseSocket(const char *dir, - virArch arch, - unsigned int cpu) -{ - int ret = virNodeGetCpuValue(dir, cpu, "topology/physical_package_id", 0); - - if (ARCH_IS_ARM(arch) || ARCH_IS_PPC(arch) || ARCH_IS_S390(arch)) { - /* arm, ppc and s390(x) has -1 */ - if (ret < 0) - ret = 0; - } - - return ret; -} - -/* parses a node entry, returning number of processors in the node and - * filling arguments */ -static int -ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3) -ATTRIBUTE_NONNULL(4) ATTRIBUTE_NONNULL(6) -ATTRIBUTE_NONNULL(7) ATTRIBUTE_NONNULL(8) -ATTRIBUTE_NONNULL(9) -virHostCPUParseNode(const char *node, - virArch arch, - virBitmapPtr present_cpus_map, - virBitmapPtr online_cpus_map, - int threads_per_subcore, - int *sockets, - int *cores, - int *threads, - int *offline) -{ - /* Biggest value we can expect to be used as either socket id - * or core id. Bitmaps will need to be sized accordingly */ - const int ID_MAX = 4095; - int ret = -1; - int processors = 0; - DIR *cpudir = NULL; - struct dirent *cpudirent = NULL; - virBitmapPtr node_cpus_map = NULL; - virBitmapPtr sockets_map = NULL; - virBitmapPtr *cores_maps = NULL; - int npresent_cpus = virBitmapSize(present_cpus_map); - int sock_max = 0; - int sock; - int core; - size_t i; - int siblings; - unsigned int cpu; - int direrr; - - *threads = 0; - *cores = 0; - *sockets = 0; - - if (!(cpudir = opendir(node))) { - virReportSystemError(errno, _("cannot opendir %s"), node); - goto cleanup; - } - - /* Keep track of the CPUs that belong to the current node */ - if (!(node_cpus_map = virBitmapNew(npresent_cpus))) - goto cleanup; - - /* enumerate sockets in the node */ - if (!(sockets_map = virBitmapNew(ID_MAX + 1))) - goto cleanup; - - while ((direrr = virDirRead(cpudir, &cpudirent, node)) > 0) { - if (sscanf(cpudirent->d_name, "cpu%u", &cpu) != 1) - continue; - - if (!virBitmapIsBitSet(present_cpus_map, cpu)) - continue; - - /* Mark this CPU as part of the current node */ - if (virBitmapSetBit(node_cpus_map, cpu) < 0) - goto cleanup; - - if (!virBitmapIsBitSet(online_cpus_map, cpu)) - continue; - - /* Parse socket */ - if ((sock = virHostCPUParseSocket(node, arch, cpu)) < 0) - goto cleanup; - if (sock > ID_MAX) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Socket %d can't be handled (max socket is %d)"), - sock, ID_MAX); - goto cleanup; - } - - if (virBitmapSetBit(sockets_map, sock) < 0) - goto cleanup; - - if (sock > sock_max) - sock_max = sock; - } - - if (direrr < 0) - goto cleanup; - - sock_max++; - - /* allocate cores maps for each socket */ - if (VIR_ALLOC_N(cores_maps, sock_max) < 0) - goto cleanup; - - for (i = 0; i < sock_max; i++) - if (!(cores_maps[i] = virBitmapNew(ID_MAX + 1))) - goto cleanup; - - /* Iterate over all CPUs in the node, in ascending order */ - for (cpu = 0; cpu < npresent_cpus; cpu++) { - - /* Skip CPUs that are not part of the current node */ - if (!virBitmapIsBitSet(node_cpus_map, cpu)) - continue; - - if (!virBitmapIsBitSet(online_cpus_map, cpu)) { - if (threads_per_subcore > 0 && - cpu % threads_per_subcore != 0 && - virBitmapIsBitSet(online_cpus_map, - cpu - (cpu % threads_per_subcore))) { - /* Secondary offline threads are counted as online when - * subcores are in use and the corresponding primary - * thread is online */ - processors++; - } else { - /* But they are counted as offline otherwise */ - (*offline)++; - } - continue; - } - - processors++; - - /* Parse socket */ - if ((sock = virHostCPUParseSocket(node, arch, cpu)) < 0) - goto cleanup; - if (!virBitmapIsBitSet(sockets_map, sock)) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("CPU socket topology has changed")); - goto cleanup; - } - - /* Parse core */ - if (ARCH_IS_S390(arch)) { - /* logical cpu is equivalent to a core on s390 */ - core = cpu; - } else { - if ((core = virNodeGetCpuValue(node, cpu, - "topology/core_id", 0)) < 0) - goto cleanup; - } - if (core > ID_MAX) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Core %d can't be handled (max core is %d)"), - core, ID_MAX); - goto cleanup; - } - - if (virBitmapSetBit(cores_maps[sock], core) < 0) - goto cleanup; - - if (!(siblings = virHostCPUCountThreadSiblings(node, cpu))) - goto cleanup; - - if (siblings > *threads) - *threads = siblings; - } - - /* finalize the returned data */ - *sockets = virBitmapCountBits(sockets_map); - - for (i = 0; i < sock_max; i++) { - if (!virBitmapIsBitSet(sockets_map, i)) - continue; - - core = virBitmapCountBits(cores_maps[i]); - if (core > *cores) - *cores = core; - } - - if (threads_per_subcore > 0) { - /* The thread count ignores offline threads, which means that only - * only primary threads have been considered so far. If subcores - * are in use, we need to also account for secondary threads */ - *threads *= threads_per_subcore; - } - ret = processors; - - cleanup: - /* don't shadow a more serious error */ - if (cpudir && closedir(cpudir) < 0 && ret >= 0) { - virReportSystemError(errno, _("problem closing %s"), node); - ret = -1; - } - if (cores_maps) - for (i = 0; i < sock_max; i++) - virBitmapFree(cores_maps[i]); - VIR_FREE(cores_maps); - virBitmapFree(sockets_map); - virBitmapFree(node_cpus_map); - - return ret; -} - -/* Check whether the host subcore configuration is valid. - * - * A valid configuration is one where no secondary thread is online; - * the primary thread in a subcore is always the first one */ -static bool -virHostCPUHasValidSubcoreConfiguration(int threads_per_subcore) -{ - virBitmapPtr online_cpus = NULL; - int cpu = -1; - bool ret = false; - - /* No point in checking if subcores are not in use */ - if (threads_per_subcore <= 0) - goto cleanup; - - if (!(online_cpus = virHostCPUGetOnlineBitmap())) - goto cleanup; - - while ((cpu = virBitmapNextSetBit(online_cpus, cpu)) >= 0) { - - /* A single online secondary thread is enough to - * make the configuration invalid */ - if (cpu % threads_per_subcore != 0) - goto cleanup; - } - - ret = true; - - cleanup: - virBitmapFree(online_cpus); - - return ret; -} - -int -virHostCPUGetInfoPopulateLinux(FILE *cpuinfo, - virArch arch, - unsigned int *cpus, - unsigned int *mhz, - unsigned int *nodes, - unsigned int *sockets, - unsigned int *cores, - unsigned int *threads) -{ - virBitmapPtr present_cpus_map = NULL; - virBitmapPtr online_cpus_map = NULL; - char line[1024]; - DIR *nodedir = NULL; - struct dirent *nodedirent = NULL; - int nodecpus, nodecores, nodesockets, nodethreads, offline = 0; - int threads_per_subcore = 0; - unsigned int node; - int ret = -1; - char *sysfs_nodedir = NULL; - char *sysfs_cpudir = NULL; - int direrr; - - *mhz = 0; - *cpus = *nodes = *sockets = *cores = *threads = 0; - - /* Start with parsing CPU clock speed from /proc/cpuinfo */ - while (fgets(line, sizeof(line), cpuinfo) != NULL) { - if (ARCH_IS_X86(arch)) { - char *buf = line; - if (STRPREFIX(buf, "cpu MHz")) { - char *p; - unsigned int ui; - - buf += 7; - while (*buf && c_isspace(*buf)) - buf++; - - if (*buf != ':' || !buf[1]) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("parsing cpu MHz from cpuinfo")); - goto cleanup; - } - - if (virStrToLong_ui(buf+1, &p, 10, &ui) == 0 && - /* Accept trailing fractional part. */ - (*p == '\0' || *p == '.' || c_isspace(*p))) - *mhz = ui; - } - } else if (ARCH_IS_PPC(arch)) { - char *buf = line; - if (STRPREFIX(buf, "clock")) { - char *p; - unsigned int ui; - - buf += 5; - while (*buf && c_isspace(*buf)) - buf++; - - if (*buf != ':' || !buf[1]) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("parsing cpu MHz from cpuinfo")); - goto cleanup; - } - - if (virStrToLong_ui(buf+1, &p, 10, &ui) == 0 && - /* Accept trailing fractional part. */ - (*p == '\0' || *p == '.' || c_isspace(*p))) - *mhz = ui; - /* No other interesting infos are available in /proc/cpuinfo. - * However, there is a line identifying processor's version, - * identification and machine, but we don't want it to be caught - * and parsed in next iteration, because it is not in expected - * format and thus lead to error. */ - } - } else if (ARCH_IS_ARM(arch)) { - char *buf = line; - if (STRPREFIX(buf, "BogoMIPS")) { - char *p; - unsigned int ui; - - buf += 8; - while (*buf && c_isspace(*buf)) - buf++; - - if (*buf != ':' || !buf[1]) { - virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("parsing cpu MHz from cpuinfo")); - goto cleanup; - } - - if (virStrToLong_ui(buf+1, &p, 10, &ui) == 0 - /* Accept trailing fractional part. */ - && (*p == '\0' || *p == '.' || c_isspace(*p))) - *mhz = ui; - } - } else if (ARCH_IS_S390(arch)) { - /* s390x has no realistic value for CPU speed, - * assign a value of zero to signify this */ - *mhz = 0; - } else { - VIR_WARN("Parser for /proc/cpuinfo needs to be adapted for your architecture"); - break; - } - } - - /* Get information about what CPUs are present in the host and what - * CPUs are online, so that we don't have to so for each node */ - present_cpus_map = virHostCPUGetPresentBitmap(); - if (!present_cpus_map) - goto cleanup; - online_cpus_map = virHostCPUGetOnlineBitmap(); - if (!online_cpus_map) - goto cleanup; - - /* OK, we've parsed clock speed out of /proc/cpuinfo. Get the - * core, node, socket, thread and topology information from /sys - */ - if (virAsprintf(&sysfs_nodedir, "%s/node", sysfs_system_path) < 0) - goto cleanup; - - if (!(nodedir = opendir(sysfs_nodedir))) { - /* the host isn't probably running a NUMA architecture */ - goto fallback; - } - - /* PPC-KVM needs the secondary threads of a core to be offline on the - * host. The kvm scheduler brings the secondary threads online in the - * guest context. Moreover, P8 processor has split-core capability - * where, there can be 1,2 or 4 subcores per core. The primaries of the - * subcores alone will be online on the host for a subcore in the - * host. Even though the actual threads per core for P8 processor is 8, - * depending on the subcores_per_core = 1, 2 or 4, the threads per - * subcore will vary accordingly to 8, 4 and 2 repectively. - * So, On host threads_per_core what is arrived at from sysfs in the - * current logic is actually the subcores_per_core. Threads per subcore - * can only be obtained from the kvm device. For example, on P8 wih 1 - * core having 8 threads, sub_cores_percore=4, the threads 0,2,4 & 6 - * will be online. The sysfs reflects this and in the current logic - * variable 'threads' will be 4 which is nothing but subcores_per_core. - * If the user tampers the cpu online/offline states using chcpu or other - * means, then it is an unsupported configuration for kvm. - * The code below tries to keep in mind - * - when the libvirtd is run inside a KVM guest or Phyp based guest. - * - Or on the kvm host where user manually tampers the cpu states to - * offline/online randomly. - * On hosts other than POWER this will be 0, in which case a simpler - * thread-counting logic will be used */ - if ((threads_per_subcore = virHostCPUGetThreadsPerSubcore(arch)) < 0) - goto cleanup; - - /* If the subcore configuration is not valid, just pretend subcores - * are not in use and count threads one by one */ - if (!virHostCPUHasValidSubcoreConfiguration(threads_per_subcore)) - threads_per_subcore = 0; - - while ((direrr = virDirRead(nodedir, &nodedirent, sysfs_nodedir)) > 0) { - if (sscanf(nodedirent->d_name, "node%u", &node) != 1) - continue; - - (*nodes)++; - - if (virAsprintf(&sysfs_cpudir, "%s/node/%s", - sysfs_system_path, nodedirent->d_name) < 0) - goto cleanup; - - if ((nodecpus = virHostCPUParseNode(sysfs_cpudir, arch, - present_cpus_map, - online_cpus_map, - threads_per_subcore, - &nodesockets, &nodecores, - &nodethreads, &offline)) < 0) - goto cleanup; - - VIR_FREE(sysfs_cpudir); - - *cpus += nodecpus; - - if (nodesockets > *sockets) - *sockets = nodesockets; - - if (nodecores > *cores) - *cores = nodecores; - - if (nodethreads > *threads) - *threads = nodethreads; - } - - if (direrr < 0) - goto cleanup; - - if (*cpus && *nodes) - goto done; - - fallback: - VIR_FREE(sysfs_cpudir); - - if (virAsprintf(&sysfs_cpudir, "%s/cpu", sysfs_system_path) < 0) - goto cleanup; - - if ((nodecpus = virHostCPUParseNode(sysfs_cpudir, arch, - present_cpus_map, - online_cpus_map, - threads_per_subcore, - &nodesockets, &nodecores, - &nodethreads, &offline)) < 0) - goto cleanup; - - *nodes = 1; - *cpus = nodecpus; - *sockets = nodesockets; - *cores = nodecores; - *threads = nodethreads; - - done: - /* There should always be at least one cpu, socket, node, and thread. */ - if (*cpus == 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("no CPUs found")); - goto cleanup; - } - - if (*sockets == 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("no sockets found")); - goto cleanup; - } - - if (*threads == 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("no threads found")); - goto cleanup; - } - - /* Now check if the topology makes sense. There are machines that don't - * expose their real number of nodes or for example the AMD Bulldozer - * architecture that exposes their Clustered integer core modules as both - * threads and cores. This approach throws off our detection. Unfortunately - * the nodeinfo structure isn't designed to carry the full topology so - * we're going to lie about the detected topology to notify the user - * to check the host capabilities for the actual topology. */ - if ((*nodes * - *sockets * - *cores * - *threads) != (*cpus + offline)) { - *nodes = 1; - *sockets = 1; - *cores = *cpus + offline; - *threads = 1; - } - - ret = 0; - - cleanup: - /* don't shadow a more serious error */ - if (nodedir && closedir(nodedir) < 0 && ret >= 0) { - virReportSystemError(errno, _("problem closing %s"), sysfs_nodedir); - ret = -1; - } - - virBitmapFree(present_cpus_map); - virBitmapFree(online_cpus_map); - VIR_FREE(sysfs_nodedir); - VIR_FREE(sysfs_cpudir); - return ret; -} - -static int -virHostCPUStatsAssign(virNodeCPUStatsPtr param, - const char *name, - unsigned long long value) -{ - if (virStrcpyStatic(param->field, name) == NULL) { - virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("kernel cpu time field is too long" - " for the destination")); - return -1; - } - param->value = value; - return 0; -} - -# define TICK_TO_NSEC (1000ull * 1000ull * 1000ull / sysconf(_SC_CLK_TCK)) - -int -virHostCPUGetStatsLinux(FILE *procstat, - int cpuNum, - virNodeCPUStatsPtr params, - int *nparams) -{ - int ret = -1; - char line[1024]; - unsigned long long usr, ni, sys, idle, iowait; - unsigned long long irq, softirq, steal, guest, guest_nice; - char cpu_header[4 + INT_BUFSIZE_BOUND(cpuNum)]; - - if ((*nparams) == 0) { - /* Current number of cpu stats supported by linux */ - *nparams = LINUX_NB_CPU_STATS; - ret = 0; - goto cleanup; - } - - if ((*nparams) != LINUX_NB_CPU_STATS) { - virReportInvalidArg(*nparams, - _("nparams in %s must be equal to %d"), - __FUNCTION__, LINUX_NB_CPU_STATS); - goto cleanup; - } - - if (cpuNum == VIR_NODE_CPU_STATS_ALL_CPUS) { - strcpy(cpu_header, "cpu "); - } else { - snprintf(cpu_header, sizeof(cpu_header), "cpu%d ", cpuNum); - } - - while (fgets(line, sizeof(line), procstat) != NULL) { - char *buf = line; - - if (STRPREFIX(buf, cpu_header)) { /* aka logical CPU time */ - if (sscanf(buf, - "%*s %llu %llu %llu %llu %llu" // user ~ iowait - "%llu %llu %llu %llu %llu", // irq ~ guest_nice - &usr, &ni, &sys, &idle, &iowait, - &irq, &softirq, &steal, &guest, &guest_nice) < 4) { - continue; - } - - if (virHostCPUStatsAssign(¶ms[0], VIR_NODE_CPU_STATS_KERNEL, - (sys + irq + softirq) * TICK_TO_NSEC) < 0) - goto cleanup; - - if (virHostCPUStatsAssign(¶ms[1], VIR_NODE_CPU_STATS_USER, - (usr + ni) * TICK_TO_NSEC) < 0) - goto cleanup; - - if (virHostCPUStatsAssign(¶ms[2], VIR_NODE_CPU_STATS_IDLE, - idle * TICK_TO_NSEC) < 0) - goto cleanup; - - if (virHostCPUStatsAssign(¶ms[3], VIR_NODE_CPU_STATS_IOWAIT, - iowait * TICK_TO_NSEC) < 0) - goto cleanup; - - ret = 0; - goto cleanup; - } - } - - virReportInvalidArg(cpuNum, - _("Invalid cpuNum in %s"), - __FUNCTION__); - - cleanup: - return ret; -} static int virHostMemGetStatsLinux(FILE *meminfo, @@ -1065,84 +305,6 @@ virHostMemGetStatsLinux(FILE *meminfo, return ret; } -static char * -virHostCPUGetGlobalPathLinux(const char *file) -{ - char *path = NULL; - - if (virAsprintf(&path, "%s/cpu/%s", sysfs_system_path, file) < 0) - return NULL; - - return path; -} - -static char * -virHostCPUGetPresentPathLinux(void) -{ - return virHostCPUGetGlobalPathLinux("present"); -} - -static char * -virHostCPUGetOnlinePathLinux(void) -{ - return virHostCPUGetGlobalPathLinux("online"); -} - -/* Determine the number of CPUs (maximum CPU id + 1) from a file containing - * a list of CPU ids, like the Linux sysfs cpu/present file */ -static int -virHostCPUParseCountLinux(const char *path) -{ - char *str = NULL; - char *tmp; - int ret = -1; - - if (virFileReadAll(path, 5 * VIR_DOMAIN_CPUMASK_LEN, &str) < 0) - goto cleanup; - - tmp = str; - do { - if (virStrToLong_i(tmp, &tmp, 10, &ret) < 0 || - !strchr(",-\n", *tmp)) { - virReportError(VIR_ERR_NO_SUPPORT, - _("failed to parse %s"), path); - ret = -1; - goto cleanup; - } - } while (*tmp++ != '\n'); - ret++; - - cleanup: - VIR_FREE(str); - return ret; -} - -/* - * Linux maintains cpu bit map under cpu/online. For example, if - * cpuid=5's flag is not set and max cpu is 7, the map file shows - * 0-4,6-7. This function parses it and returns cpumap. - */ -static virBitmapPtr -virHostCPUParseMapLinux(int max_cpuid, const char *path) -{ - virBitmapPtr map = NULL; - char *str = NULL; - - if (virFileReadAll(path, 5 * VIR_DOMAIN_CPUMASK_LEN, &str) < 0) - goto error; - - if (virBitmapParse(str, 0, &map, max_cpuid) < 0) - goto error; - - VIR_FREE(str); - return map; - - error: - VIR_FREE(str); - virBitmapFree(map); - return NULL; -} - static virBitmapPtr virNodeGetSiblingsListLinux(const char *dir, int cpu_id) @@ -1169,73 +331,6 @@ virNodeGetSiblingsListLinux(const char *dir, int cpu_id) #endif -static int -virHostCPUGetInfo(virArch hostarch, - unsigned int *cpus, - unsigned int *mhz, - unsigned int *nodes, - unsigned int *sockets, - unsigned int *cores, - unsigned int *threads) -{ -#ifdef __linux__ - int ret = -1; - FILE *cpuinfo = fopen(CPUINFO_PATH, "r"); - - if (!cpuinfo) { - virReportSystemError(errno, - _("cannot open %s"), CPUINFO_PATH); - return -1; - } - - ret = virHostCPUGetInfoPopulateLinux(cpuinfo, hostarch, - cpus, mhz, nodes, - sockets, cores, threads); - if (ret < 0) - goto cleanup; - - cleanup: - VIR_FORCE_FCLOSE(cpuinfo); - return ret; -#elif defined(__FreeBSD__) || defined(__APPLE__) - unsigned long cpu_freq; - size_t cpu_freq_len = sizeof(cpu_freq); - - *cpus = virHostCPUGetCountAppleFreeBSD(); - if (*cpus == -1) - return -1; - - *nodes = 1; - *sockets = 1; - *cores = *cpus; - *threads = 1; - -# ifdef __FreeBSD__ - if (sysctlbyname("dev.cpu.0.freq", &cpu_freq, &cpu_freq_len, NULL, 0) < 0) { - virReportSystemError(errno, "%s", _("cannot obtain CPU freq")); - return -1; - } - - *mhz = cpu_freq; -# else - if (sysctlbyname("hw.cpufrequency", &cpu_freq, &cpu_freq_len, NULL, 0) < 0) { - virReportSystemError(errno, "%s", _("cannot obtain CPU freq")); - return -1; - } - - *mhz = cpu_freq / 1000000; -# endif - - return 0; -#else - /* XXX Solaris will need an impl later if they port QEMU driver */ - virReportError(VIR_ERR_NO_SUPPORT, "%s", - _("node info not implemented on this platform")); - return -1; -#endif -} - - int nodeGetInfo(virNodeInfoPtr nodeinfo) { @@ -1260,36 +355,6 @@ nodeGetInfo(virNodeInfoPtr nodeinfo) return 0; } -int -virHostCPUGetStats(int cpuNum ATTRIBUTE_UNUSED, - virNodeCPUStatsPtr params ATTRIBUTE_UNUSED, - int *nparams ATTRIBUTE_UNUSED, - unsigned int flags) -{ - virCheckFlags(0, -1); - -#ifdef __linux__ - { - int ret; - FILE *procstat = fopen(PROCSTAT_PATH, "r"); - if (!procstat) { - virReportSystemError(errno, - _("cannot open %s"), PROCSTAT_PATH); - return -1; - } - ret = virHostCPUGetStatsLinux(procstat, cpuNum, params, nparams); - VIR_FORCE_FCLOSE(procstat); - - return ret; - } -#elif defined(__FreeBSD__) - return virHostCPUGetStatsFreeBSD(cpuNum, params, nparams); -#else - virReportError(VIR_ERR_NO_SUPPORT, "%s", - _("node CPU stats not implemented on this platform")); - return -1; -#endif -} int virHostMemGetStats(int cellNum ATTRIBUTE_UNUSED, @@ -1348,146 +413,6 @@ virHostMemGetStats(int cellNum ATTRIBUTE_UNUSED, #endif } -int -virHostCPUGetCount(void) -{ -#if defined(__linux__) - /* To support older kernels that lack cpu/present, such as 2.6.18 - * in RHEL5, we fall back to count cpu/cpuNN entries; this assumes - * that such kernels also lack hotplug, and therefore cpu/cpuNN - * will be consecutive. - */ - char *present_path = NULL; - char *cpupath = NULL; - int ncpu = -1; - - if (!(present_path = virHostCPUGetPresentPathLinux())) - return -1; - - if (virFileExists(present_path)) { - ncpu = virHostCPUParseCountLinux(present_path); - goto cleanup; - } - - if (virAsprintf(&cpupath, "%s/cpu/cpu0", sysfs_system_path) < 0) - goto cleanup; - if (virFileExists(cpupath)) { - ncpu = 0; - do { - ncpu++; - VIR_FREE(cpupath); - if (virAsprintf(&cpupath, "%s/cpu/cpu%d", - sysfs_system_path, ncpu) < 0) { - ncpu = -1; - goto cleanup; - } - } while (virFileExists(cpupath)); - } else { - /* no cpu/cpu0: we give up */ - virReportError(VIR_ERR_NO_SUPPORT, "%s", - _("host cpu counting not supported on this node")); - } - - cleanup: - VIR_FREE(present_path); - VIR_FREE(cpupath); - return ncpu; -#elif defined(__FreeBSD__) || defined(__APPLE__) - return virHostCPUGetCountAppleFreeBSD(); -#else - virReportError(VIR_ERR_NO_SUPPORT, "%s", - _("host cpu counting not implemented on this platform")); - return -1; -#endif -} - -virBitmapPtr -virHostCPUGetPresentBitmap(void) -{ -#ifdef __linux__ - virBitmapPtr present_cpus = NULL; - char *present_path = NULL; - int npresent_cpus; - - if ((npresent_cpus = virHostCPUGetCount()) < 0) - goto cleanup; - - if (!(present_path = virHostCPUGetPresentPathLinux())) - goto cleanup; - - /* If the cpu/present file is available, parse it and exit */ - if (virFileExists(present_path)) { - present_cpus = virHostCPUParseMapLinux(npresent_cpus, present_path); - goto cleanup; - } - - /* If the file is not available, we can assume that the kernel is - * too old to support non-consecutive CPU ids and just mark all - * possible CPUs as present */ - if (!(present_cpus = virBitmapNew(npresent_cpus))) - goto cleanup; - - virBitmapSetAll(present_cpus); - - cleanup: - VIR_FREE(present_path); - - return present_cpus; -#endif - virReportError(VIR_ERR_NO_SUPPORT, "%s", - _("node present CPU map not implemented on this platform")); - return NULL; -} - -virBitmapPtr -virHostCPUGetOnlineBitmap(void) -{ -#ifdef __linux__ - char *online_path = NULL; - char *cpudir = NULL; - virBitmapPtr cpumap; - int present; - - present = virHostCPUGetCount(); - if (present < 0) - return NULL; - - if (!(online_path = virHostCPUGetOnlinePathLinux())) - return NULL; - if (virFileExists(online_path)) { - cpumap = virHostCPUParseMapLinux(present, online_path); - } else { - size_t i; - - cpumap = virBitmapNew(present); - if (!cpumap) - goto cleanup; - - if (virAsprintf(&cpudir, "%s/cpu", sysfs_system_path) < 0) - goto cleanup; - - for (i = 0; i < present; i++) { - int online = virNodeGetCpuValue(cpudir, i, "online", 1); - if (online < 0) { - virBitmapFree(cpumap); - cpumap = NULL; - goto cleanup; - } - if (online) - ignore_value(virBitmapSetBit(cpumap, i)); - } - } - - cleanup: - VIR_FREE(online_path); - VIR_FREE(cpudir); - return cpumap; -#else - virReportError(VIR_ERR_NO_SUPPORT, "%s", - _("node online CPU map not implemented on this platform")); - return NULL; -#endif -} #ifdef __linux__ static int @@ -1791,36 +716,6 @@ virHostMemGetParameters(virTypedParameterPtr params ATTRIBUTE_UNUSED, #endif } -int -virHostCPUGetMap(unsigned char **cpumap, - unsigned int *online, - unsigned int flags) -{ - virBitmapPtr cpus = NULL; - int ret = -1; - int dummy; - - virCheckFlags(0, -1); - - if (!cpumap && !online) - return virHostCPUGetCount(); - - if (!(cpus = virHostCPUGetOnlineBitmap())) - goto cleanup; - - if (cpumap && virBitmapToData(cpus, cpumap, &dummy) < 0) - goto cleanup; - if (online) - *online = virBitmapCountBits(cpus); - - ret = virBitmapSize(cpus); - - cleanup: - if (ret < 0 && cpumap) - VIR_FREE(*cpumap); - virBitmapFree(cpus); - return ret; -} static int nodeCapsInitNUMAFake(const char *cpupath ATTRIBUTE_UNUSED, @@ -2329,67 +1224,3 @@ virHostMemAllocPages(unsigned int npages, cleanup: return ret; } - -#if HAVE_LINUX_KVM_H && defined(KVM_CAP_PPC_SMT) - -/* Get the number of threads per subcore. - * - * This will be 2, 4 or 8 on POWER hosts, depending on the current - * micro-threading configuration, and 0 everywhere else. - * - * Returns the number of threads per subcore if subcores are in use, zero - * if subcores are not in use, and a negative value on error */ -int -virHostCPUGetThreadsPerSubcore(virArch arch) -{ - int threads_per_subcore = 0; - const char *kvmpath = "/dev/kvm"; - int kvmfd; - - if (ARCH_IS_PPC64(arch)) { - - /* It's okay if /dev/kvm doesn't exist, because - * a. we might be running in a guest - * b. the kvm module might not be installed or enabled - * In either case, falling back to the subcore-unaware thread - * counting logic is the right thing to do */ - if (!virFileExists(kvmpath)) - goto out; - - if ((kvmfd = open(kvmpath, O_RDONLY)) < 0) { - /* This can happen when running as a regular user if - * permissions are tight enough, in which case erroring out - * is better than silently falling back and reporting - * different nodeinfo depending on the user */ - virReportSystemError(errno, - _("Failed to open '%s'"), - kvmpath); - threads_per_subcore = -1; - goto out; - } - - /* For Phyp and KVM based guests the ioctl for KVM_CAP_PPC_SMT - * returns zero and both primary and secondary threads will be - * online */ - threads_per_subcore = ioctl(kvmfd, - KVM_CHECK_EXTENSION, - KVM_CAP_PPC_SMT); - - VIR_FORCE_CLOSE(kvmfd); - } - - out: - return threads_per_subcore; -} - -#else - -/* Fallback for nodeGetThreadsPerSubcore() used when KVM headers - * are not available on the system */ -int -virHostCPUGetThreadsPerSubcore(virArch arch ATTRIBUTE_UNUSED) -{ - return 0; -} - -#endif /* HAVE_LINUX_KVM_H && defined(KVM_CAP_PPC_SMT) */ diff --git a/src/nodeinfo.h b/src/nodeinfo.h index 08b9f9d..918fcb5 100644 --- a/src/nodeinfo.h +++ b/src/nodeinfo.h @@ -29,10 +29,6 @@ int nodeGetInfo(virNodeInfoPtr nodeinfo); int nodeCapsInitNUMA(virCapsPtr caps); -int virHostCPUGetStats(int cpuNum, - virNodeCPUStatsPtr params, - int *nparams, - unsigned int flags); int virHostMemGetStats(int cellNum, virNodeMemoryStatsPtr params, int *nparams, @@ -43,11 +39,6 @@ int virHostMemGetCellsFree(unsigned long long *freeMems, int virHostMemGetInfo(unsigned long long *mem, unsigned long long *freeMem); -virBitmapPtr virHostCPUGetPresentBitmap(void); -virBitmapPtr virHostCPUGetOnlineBitmap(void); -int virHostCPUGetCount(void); -int virHostCPUGetThreadsPerSubcore(virArch arch); - int virHostMemGetParameters(virTypedParameterPtr params, int *nparams, unsigned int flags); @@ -56,10 +47,6 @@ int virHostMemSetParameters(virTypedParameterPtr params, int nparams, unsigned int flags); -int virHostCPUGetMap(unsigned char **cpumap, - unsigned int *online, - unsigned int flags); - int virHostMemGetFreePages(unsigned int npages, unsigned int *pages, int startCell, diff --git a/src/openvz/openvz_driver.c b/src/openvz/openvz_driver.c index ef11745..910ccfe 100644 --- a/src/openvz/openvz_driver.c +++ b/src/openvz/openvz_driver.c @@ -49,6 +49,7 @@ #include "virbuffer.h" #include "openvz_conf.h" #include "nodeinfo.h" +#include "virhostcpu.h" #include "viralloc.h" #include "virfile.h" #include "virtypedparam.h" diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 38aab15..ec04fb7 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -63,6 +63,7 @@ #include "datatypes.h" #include "virbuffer.h" #include "nodeinfo.h" +#include "virhostcpu.h" #include "virstats.h" #include "capabilities.h" #include "viralloc.h" diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 7d01e38..5ae38bf 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -54,7 +54,7 @@ #include "virhook.h" #include "virfile.h" #include "virpidfile.h" -#include "nodeinfo.h" +#include "virhostcpu.h" #include "domain_audit.h" #include "domain_nwfilter.h" #include "locking/domain_lock.h" diff --git a/src/uml/uml_driver.c b/src/uml/uml_driver.c index 19ba901..4048b11 100644 --- a/src/uml/uml_driver.c +++ b/src/uml/uml_driver.c @@ -46,6 +46,7 @@ #include "uml_conf.h" #include "virbuffer.h" #include "nodeinfo.h" +#include "virhostcpu.h" #include "virstats.h" #include "capabilities.h" #include "viralloc.h" diff --git a/src/util/vircgroup.c b/src/util/vircgroup.c index 05097f8..0124dbe 100644 --- a/src/util/vircgroup.c +++ b/src/util/vircgroup.c @@ -52,8 +52,7 @@ #include "virstring.h" #include "virsystemd.h" #include "virtypedparam.h" - -#include "nodeinfo.h" +#include "virhostcpu.h" VIR_LOG_INIT("util.cgroup"); diff --git a/src/util/virhostcpu.c b/src/util/virhostcpu.c new file mode 100644 index 0000000..72ea943 --- /dev/null +++ b/src/util/virhostcpu.c @@ -0,0 +1,1299 @@ +/* + * virhostcpu.c: helper APIs for host CPU info + * + * Copyright (C) 2006-2016 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + * + * Author: Daniel P. Berrange <berrange@xxxxxxxxxx> + */ + +#include <config.h> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdint.h> +#include <errno.h> +#include <dirent.h> +#include <sys/utsname.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#if HAVE_LINUX_KVM_H +# include <linux/kvm.h> +#endif + +#if defined(__FreeBSD__) || defined(__APPLE__) +# include <sys/time.h> +# include <sys/types.h> +# include <sys/sysctl.h> +# include <sys/resource.h> +#endif + +#include "c-ctype.h" +#include "viralloc.h" +#include "virhostcpupriv.h" +#include "physmem.h" +#include "virerror.h" +#include "count-one-bits.h" +#include "intprops.h" +#include "virarch.h" +#include "virfile.h" +#include "virtypedparam.h" +#include "virstring.h" +#include "virnuma.h" +#include "virlog.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +VIR_LOG_INIT("util.hostcpu"); + + +#if defined(__FreeBSD__) || defined(__APPLE__) +static int +virHostCPUGetCountAppleFreeBSD(void) +{ + int ncpu_mib[2] = { CTL_HW, HW_NCPU }; + unsigned long ncpu; + size_t ncpu_len = sizeof(ncpu); + + if (sysctl(ncpu_mib, 2, &ncpu, &ncpu_len, NULL, 0) == -1) { + virReportSystemError(errno, "%s", _("Cannot obtain CPU count")); + return -1; + } + + return ncpu; +} +#endif /* defined(__FreeBSD__) || defined(__APPLE__) */ + +#ifdef __FreeBSD__ +# define BSD_CPU_STATS_ALL 4 +# define BSD_MEMORY_STATS_ALL 4 + +# define TICK_TO_NSEC (1000ull * 1000ull * 1000ull / (stathz ? stathz : hz)) + +static int +virHostCPUGetStatsFreebsd(int cpuNum, + virNodeCPUStatsPtr params, + int *nparams) +{ + const char *sysctl_name; + long *cpu_times; + struct clockinfo clkinfo; + size_t i, j, cpu_times_size, clkinfo_size; + int cpu_times_num, offset, hz, stathz, ret = -1; + struct field_cpu_map { + const char *field; + int idx[CPUSTATES]; + } cpu_map[] = { + {VIR_NODE_CPU_STATS_KERNEL, {CP_SYS}}, + {VIR_NODE_CPU_STATS_USER, {CP_USER, CP_NICE}}, + {VIR_NODE_CPU_STATS_IDLE, {CP_IDLE}}, + {VIR_NODE_CPU_STATS_INTR, {CP_INTR}}, + {NULL, {0}} + }; + + if ((*nparams) == 0) { + *nparams = BSD_CPU_STATS_ALL; + return 0; + } + + if ((*nparams) != BSD_CPU_STATS_ALL) { + virReportInvalidArg(*nparams, + _("nparams in %s must be equal to %d"), + __FUNCTION__, BSD_CPU_STATS_ALL); + return -1; + } + + clkinfo_size = sizeof(clkinfo); + if (sysctlbyname("kern.clockrate", &clkinfo, &clkinfo_size, NULL, 0) < 0) { + virReportSystemError(errno, + _("sysctl failed for '%s'"), + "kern.clockrate"); + return -1; + } + + stathz = clkinfo.stathz; + hz = clkinfo.hz; + + if (cpuNum == VIR_NODE_CPU_STATS_ALL_CPUS) { + sysctl_name = "kern.cp_time"; + cpu_times_num = 1; + offset = 0; + } else { + sysctl_name = "kern.cp_times"; + cpu_times_num = virHostCPUGetCountAppleFreeBSD(); + + if (cpuNum >= cpu_times_num) { + virReportInvalidArg(cpuNum, + _("Invalid cpuNum in %s"), + __FUNCTION__); + return -1; + } + + offset = cpu_times_num * CPUSTATES; + } + + cpu_times_size = sizeof(long) * cpu_times_num * CPUSTATES; + + if (VIR_ALLOC_N(cpu_times, cpu_times_num * CPUSTATES) < 0) + goto cleanup; + + if (sysctlbyname(sysctl_name, cpu_times, &cpu_times_size, NULL, 0) < 0) { + virReportSystemError(errno, + _("sysctl failed for '%s'"), + sysctl_name); + goto cleanup; + } + + for (i = 0; cpu_map[i].field != NULL; i++) { + virNodeCPUStatsPtr param = ¶ms[i]; + + if (virStrcpyStatic(param->field, cpu_map[i].field) == NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Field '%s' too long for destination"), + cpu_map[i].field); + goto cleanup; + } + + param->value = 0; + for (j = 0; j < ARRAY_CARDINALITY(cpu_map[i].idx); j++) + param->value += cpu_times[offset + cpu_map[i].idx[j]] * TICK_TO_NSEC; + } + + ret = 0; + + cleanup: + VIR_FREE(cpu_times); + + return ret; +} + +#endif /* __FreeBSD__ */ + +#ifdef __linux__ +# define SYSFS_SYSTEM_PATH "/sys/devices/system" +# define CPUINFO_PATH "/proc/cpuinfo" +# define PROCSTAT_PATH "/proc/stat" +# define SYSFS_THREAD_SIBLINGS_LIST_LENGTH_MAX 8192 + +# define LINUX_NB_CPU_STATS 4 + +static const char *sysfs_system_path = SYSFS_SYSTEM_PATH; + +void virHostCPUSetSysFSSystemPathLinux(const char *path) +{ + if (path) + sysfs_system_path = path; + else + sysfs_system_path = SYSFS_SYSTEM_PATH; +} + +/* Return the positive decimal contents of the given + * DIR/cpu%u/FILE, or -1 on error. If DEFAULT_VALUE is non-negative + * and the file could not be found, return that instead of an error; + * this is useful for machines that cannot hot-unplug cpu0, or where + * hot-unplugging is disabled, or where the kernel is too old + * to support NUMA cells, etc. */ +static int +virHostCPUGetValue(const char *dir, unsigned int cpu, const char *file, + int default_value) +{ + char *path; + FILE *pathfp; + int value = -1; + char value_str[INT_BUFSIZE_BOUND(value)]; + char *tmp; + + if (virAsprintf(&path, "%s/cpu%u/%s", dir, cpu, file) < 0) + return -1; + + pathfp = fopen(path, "r"); + if (pathfp == NULL) { + if (default_value >= 0 && errno == ENOENT) + value = default_value; + else + virReportSystemError(errno, _("cannot open %s"), path); + goto cleanup; + } + + if (fgets(value_str, sizeof(value_str), pathfp) == NULL) { + virReportSystemError(errno, _("cannot read from %s"), path); + goto cleanup; + } + if (virStrToLong_i(value_str, &tmp, 10, &value) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("could not convert '%s' to an integer"), + value_str); + goto cleanup; + } + + cleanup: + VIR_FORCE_FCLOSE(pathfp); + VIR_FREE(path); + + return value; +} + +static unsigned long +virHostCPUCountThreadSiblings(const char *dir, unsigned int cpu) +{ + unsigned long ret = 0; + char *path; + char *str = NULL; + size_t i; + + if (virAsprintf(&path, "%s/cpu%u/topology/thread_siblings", + dir, cpu) < 0) + return 0; + + if (!virFileExists(path)) { + /* If file doesn't exist, then pretend our only + * sibling is ourself */ + ret = 1; + goto cleanup; + } + + if (virFileReadAll(path, SYSFS_THREAD_SIBLINGS_LIST_LENGTH_MAX, &str) < 0) + goto cleanup; + + for (i = 0; str[i] != '\0'; i++) { + if (c_isxdigit(str[i])) + ret += count_one_bits(virHexToBin(str[i])); + } + + cleanup: + VIR_FREE(str); + VIR_FREE(path); + return ret; +} + +static int +virHostCPUParseSocket(const char *dir, + virArch arch, + unsigned int cpu) +{ + int ret = virHostCPUGetValue(dir, cpu, "topology/physical_package_id", 0); + + if (ARCH_IS_ARM(arch) || ARCH_IS_PPC(arch) || ARCH_IS_S390(arch)) { + /* arm, ppc and s390(x) has -1 */ + if (ret < 0) + ret = 0; + } + + return ret; +} + +/* parses a node entry, returning number of processors in the node and + * filling arguments */ +static int +ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3) +ATTRIBUTE_NONNULL(4) ATTRIBUTE_NONNULL(6) +ATTRIBUTE_NONNULL(7) ATTRIBUTE_NONNULL(8) +ATTRIBUTE_NONNULL(9) +virHostCPUParseNode(const char *node, + virArch arch, + virBitmapPtr present_cpus_map, + virBitmapPtr online_cpus_map, + int threads_per_subcore, + int *sockets, + int *cores, + int *threads, + int *offline) +{ + /* Biggest value we can expect to be used as either socket id + * or core id. Bitmaps will need to be sized accordingly */ + const int ID_MAX = 4095; + int ret = -1; + int processors = 0; + DIR *cpudir = NULL; + struct dirent *cpudirent = NULL; + virBitmapPtr node_cpus_map = NULL; + virBitmapPtr sockets_map = NULL; + virBitmapPtr *cores_maps = NULL; + int npresent_cpus = virBitmapSize(present_cpus_map); + int sock_max = 0; + int sock; + int core; + size_t i; + int siblings; + unsigned int cpu; + int direrr; + + *threads = 0; + *cores = 0; + *sockets = 0; + + if (!(cpudir = opendir(node))) { + virReportSystemError(errno, _("cannot opendir %s"), node); + goto cleanup; + } + + /* Keep track of the CPUs that belong to the current node */ + if (!(node_cpus_map = virBitmapNew(npresent_cpus))) + goto cleanup; + + /* enumerate sockets in the node */ + if (!(sockets_map = virBitmapNew(ID_MAX + 1))) + goto cleanup; + + while ((direrr = virDirRead(cpudir, &cpudirent, node)) > 0) { + if (sscanf(cpudirent->d_name, "cpu%u", &cpu) != 1) + continue; + + if (!virBitmapIsBitSet(present_cpus_map, cpu)) + continue; + + /* Mark this CPU as part of the current node */ + if (virBitmapSetBit(node_cpus_map, cpu) < 0) + goto cleanup; + + if (!virBitmapIsBitSet(online_cpus_map, cpu)) + continue; + + /* Parse socket */ + if ((sock = virHostCPUParseSocket(node, arch, cpu)) < 0) + goto cleanup; + if (sock > ID_MAX) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Socket %d can't be handled (max socket is %d)"), + sock, ID_MAX); + goto cleanup; + } + + if (virBitmapSetBit(sockets_map, sock) < 0) + goto cleanup; + + if (sock > sock_max) + sock_max = sock; + } + + if (direrr < 0) + goto cleanup; + + sock_max++; + + /* allocate cores maps for each socket */ + if (VIR_ALLOC_N(cores_maps, sock_max) < 0) + goto cleanup; + + for (i = 0; i < sock_max; i++) + if (!(cores_maps[i] = virBitmapNew(ID_MAX + 1))) + goto cleanup; + + /* Iterate over all CPUs in the node, in ascending order */ + for (cpu = 0; cpu < npresent_cpus; cpu++) { + + /* Skip CPUs that are not part of the current node */ + if (!virBitmapIsBitSet(node_cpus_map, cpu)) + continue; + + if (!virBitmapIsBitSet(online_cpus_map, cpu)) { + if (threads_per_subcore > 0 && + cpu % threads_per_subcore != 0 && + virBitmapIsBitSet(online_cpus_map, + cpu - (cpu % threads_per_subcore))) { + /* Secondary offline threads are counted as online when + * subcores are in use and the corresponding primary + * thread is online */ + processors++; + } else { + /* But they are counted as offline otherwise */ + (*offline)++; + } + continue; + } + + processors++; + + /* Parse socket */ + if ((sock = virHostCPUParseSocket(node, arch, cpu)) < 0) + goto cleanup; + if (!virBitmapIsBitSet(sockets_map, sock)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("CPU socket topology has changed")); + goto cleanup; + } + + /* Parse core */ + if (ARCH_IS_S390(arch)) { + /* logical cpu is equivalent to a core on s390 */ + core = cpu; + } else { + if ((core = virHostCPUGetValue(node, cpu, + "topology/core_id", 0)) < 0) + goto cleanup; + } + if (core > ID_MAX) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Core %d can't be handled (max core is %d)"), + core, ID_MAX); + goto cleanup; + } + + if (virBitmapSetBit(cores_maps[sock], core) < 0) + goto cleanup; + + if (!(siblings = virHostCPUCountThreadSiblings(node, cpu))) + goto cleanup; + + if (siblings > *threads) + *threads = siblings; + } + + /* finalize the returned data */ + *sockets = virBitmapCountBits(sockets_map); + + for (i = 0; i < sock_max; i++) { + if (!virBitmapIsBitSet(sockets_map, i)) + continue; + + core = virBitmapCountBits(cores_maps[i]); + if (core > *cores) + *cores = core; + } + + if (threads_per_subcore > 0) { + /* The thread count ignores offline threads, which means that only + * only primary threads have been considered so far. If subcores + * are in use, we need to also account for secondary threads */ + *threads *= threads_per_subcore; + } + ret = processors; + + cleanup: + /* don't shadow a more serious error */ + if (cpudir && closedir(cpudir) < 0 && ret >= 0) { + virReportSystemError(errno, _("problem closing %s"), node); + ret = -1; + } + if (cores_maps) + for (i = 0; i < sock_max; i++) + virBitmapFree(cores_maps[i]); + VIR_FREE(cores_maps); + virBitmapFree(sockets_map); + virBitmapFree(node_cpus_map); + + return ret; +} + +/* Check whether the host subcore configuration is valid. + * + * A valid configuration is one where no secondary thread is online; + * the primary thread in a subcore is always the first one */ +static bool +virHostCPUHasValidSubcoreConfiguration(int threads_per_subcore) +{ + virBitmapPtr online_cpus = NULL; + int cpu = -1; + bool ret = false; + + /* No point in checking if subcores are not in use */ + if (threads_per_subcore <= 0) + goto cleanup; + + if (!(online_cpus = virHostCPUGetOnlineBitmap())) + goto cleanup; + + while ((cpu = virBitmapNextSetBit(online_cpus, cpu)) >= 0) { + + /* A single online secondary thread is enough to + * make the configuration invalid */ + if (cpu % threads_per_subcore != 0) + goto cleanup; + } + + ret = true; + + cleanup: + virBitmapFree(online_cpus); + + return ret; +} + +int +virHostCPUGetInfoPopulateLinux(FILE *cpuinfo, + virArch arch, + unsigned int *cpus, + unsigned int *mhz, + unsigned int *nodes, + unsigned int *sockets, + unsigned int *cores, + unsigned int *threads) +{ + virBitmapPtr present_cpus_map = NULL; + virBitmapPtr online_cpus_map = NULL; + char line[1024]; + DIR *nodedir = NULL; + struct dirent *nodedirent = NULL; + int nodecpus, nodecores, nodesockets, nodethreads, offline = 0; + int threads_per_subcore = 0; + unsigned int node; + int ret = -1; + char *sysfs_nodedir = NULL; + char *sysfs_cpudir = NULL; + int direrr; + + *mhz = 0; + *cpus = *nodes = *sockets = *cores = *threads = 0; + + /* Start with parsing CPU clock speed from /proc/cpuinfo */ + while (fgets(line, sizeof(line), cpuinfo) != NULL) { + if (ARCH_IS_X86(arch)) { + char *buf = line; + if (STRPREFIX(buf, "cpu MHz")) { + char *p; + unsigned int ui; + + buf += 7; + while (*buf && c_isspace(*buf)) + buf++; + + if (*buf != ':' || !buf[1]) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("parsing cpu MHz from cpuinfo")); + goto cleanup; + } + + if (virStrToLong_ui(buf+1, &p, 10, &ui) == 0 && + /* Accept trailing fractional part. */ + (*p == '\0' || *p == '.' || c_isspace(*p))) + *mhz = ui; + } + } else if (ARCH_IS_PPC(arch)) { + char *buf = line; + if (STRPREFIX(buf, "clock")) { + char *p; + unsigned int ui; + + buf += 5; + while (*buf && c_isspace(*buf)) + buf++; + + if (*buf != ':' || !buf[1]) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("parsing cpu MHz from cpuinfo")); + goto cleanup; + } + + if (virStrToLong_ui(buf+1, &p, 10, &ui) == 0 && + /* Accept trailing fractional part. */ + (*p == '\0' || *p == '.' || c_isspace(*p))) + *mhz = ui; + /* No other interesting infos are available in /proc/cpuinfo. + * However, there is a line identifying processor's version, + * identification and machine, but we don't want it to be caught + * and parsed in next iteration, because it is not in expected + * format and thus lead to error. */ + } + } else if (ARCH_IS_ARM(arch)) { + char *buf = line; + if (STRPREFIX(buf, "BogoMIPS")) { + char *p; + unsigned int ui; + + buf += 8; + while (*buf && c_isspace(*buf)) + buf++; + + if (*buf != ':' || !buf[1]) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("parsing cpu MHz from cpuinfo")); + goto cleanup; + } + + if (virStrToLong_ui(buf+1, &p, 10, &ui) == 0 + /* Accept trailing fractional part. */ + && (*p == '\0' || *p == '.' || c_isspace(*p))) + *mhz = ui; + } + } else if (ARCH_IS_S390(arch)) { + /* s390x has no realistic value for CPU speed, + * assign a value of zero to signify this */ + *mhz = 0; + } else { + VIR_WARN("Parser for /proc/cpuinfo needs to be adapted for your architecture"); + break; + } + } + + /* Get information about what CPUs are present in the host and what + * CPUs are online, so that we don't have to so for each node */ + present_cpus_map = virHostCPUGetPresentBitmap(); + if (!present_cpus_map) + goto cleanup; + online_cpus_map = virHostCPUGetOnlineBitmap(); + if (!online_cpus_map) + goto cleanup; + + /* OK, we've parsed clock speed out of /proc/cpuinfo. Get the + * core, node, socket, thread and topology information from /sys + */ + if (virAsprintf(&sysfs_nodedir, "%s/node", sysfs_system_path) < 0) + goto cleanup; + + if (!(nodedir = opendir(sysfs_nodedir))) { + /* the host isn't probably running a NUMA architecture */ + goto fallback; + } + + /* PPC-KVM needs the secondary threads of a core to be offline on the + * host. The kvm scheduler brings the secondary threads online in the + * guest context. Moreover, P8 processor has split-core capability + * where, there can be 1,2 or 4 subcores per core. The primaries of the + * subcores alone will be online on the host for a subcore in the + * host. Even though the actual threads per core for P8 processor is 8, + * depending on the subcores_per_core = 1, 2 or 4, the threads per + * subcore will vary accordingly to 8, 4 and 2 repectively. + * So, On host threads_per_core what is arrived at from sysfs in the + * current logic is actually the subcores_per_core. Threads per subcore + * can only be obtained from the kvm device. For example, on P8 wih 1 + * core having 8 threads, sub_cores_percore=4, the threads 0,2,4 & 6 + * will be online. The sysfs reflects this and in the current logic + * variable 'threads' will be 4 which is nothing but subcores_per_core. + * If the user tampers the cpu online/offline states using chcpu or other + * means, then it is an unsupported configuration for kvm. + * The code below tries to keep in mind + * - when the libvirtd is run inside a KVM guest or Phyp based guest. + * - Or on the kvm host where user manually tampers the cpu states to + * offline/online randomly. + * On hosts other than POWER this will be 0, in which case a simpler + * thread-counting logic will be used */ + if ((threads_per_subcore = virHostCPUGetThreadsPerSubcore(arch)) < 0) + goto cleanup; + + /* If the subcore configuration is not valid, just pretend subcores + * are not in use and count threads one by one */ + if (!virHostCPUHasValidSubcoreConfiguration(threads_per_subcore)) + threads_per_subcore = 0; + + while ((direrr = virDirRead(nodedir, &nodedirent, sysfs_nodedir)) > 0) { + if (sscanf(nodedirent->d_name, "node%u", &node) != 1) + continue; + + (*nodes)++; + + if (virAsprintf(&sysfs_cpudir, "%s/node/%s", + sysfs_system_path, nodedirent->d_name) < 0) + goto cleanup; + + if ((nodecpus = virHostCPUParseNode(sysfs_cpudir, arch, + present_cpus_map, + online_cpus_map, + threads_per_subcore, + &nodesockets, &nodecores, + &nodethreads, &offline)) < 0) + goto cleanup; + + VIR_FREE(sysfs_cpudir); + + *cpus += nodecpus; + + if (nodesockets > *sockets) + *sockets = nodesockets; + + if (nodecores > *cores) + *cores = nodecores; + + if (nodethreads > *threads) + *threads = nodethreads; + } + + if (direrr < 0) + goto cleanup; + + if (*cpus && *nodes) + goto done; + + fallback: + VIR_FREE(sysfs_cpudir); + + if (virAsprintf(&sysfs_cpudir, "%s/cpu", sysfs_system_path) < 0) + goto cleanup; + + if ((nodecpus = virHostCPUParseNode(sysfs_cpudir, arch, + present_cpus_map, + online_cpus_map, + threads_per_subcore, + &nodesockets, &nodecores, + &nodethreads, &offline)) < 0) + goto cleanup; + + *nodes = 1; + *cpus = nodecpus; + *sockets = nodesockets; + *cores = nodecores; + *threads = nodethreads; + + done: + /* There should always be at least one cpu, socket, node, and thread. */ + if (*cpus == 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("no CPUs found")); + goto cleanup; + } + + if (*sockets == 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("no sockets found")); + goto cleanup; + } + + if (*threads == 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("no threads found")); + goto cleanup; + } + + /* Now check if the topology makes sense. There are machines that don't + * expose their real number of nodes or for example the AMD Bulldozer + * architecture that exposes their Clustered integer core modules as both + * threads and cores. This approach throws off our detection. Unfortunately + * the nodeinfo structure isn't designed to carry the full topology so + * we're going to lie about the detected topology to notify the user + * to check the host capabilities for the actual topology. */ + if ((*nodes * + *sockets * + *cores * + *threads) != (*cpus + offline)) { + *nodes = 1; + *sockets = 1; + *cores = *cpus + offline; + *threads = 1; + } + + ret = 0; + + cleanup: + /* don't shadow a more serious error */ + if (nodedir && closedir(nodedir) < 0 && ret >= 0) { + virReportSystemError(errno, _("problem closing %s"), sysfs_nodedir); + ret = -1; + } + + virBitmapFree(present_cpus_map); + virBitmapFree(online_cpus_map); + VIR_FREE(sysfs_nodedir); + VIR_FREE(sysfs_cpudir); + return ret; +} + +static int +virHostCPUStatsAssign(virNodeCPUStatsPtr param, + const char *name, + unsigned long long value) +{ + if (virStrcpyStatic(param->field, name) == NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("kernel cpu time field is too long" + " for the destination")); + return -1; + } + param->value = value; + return 0; +} + +# define TICK_TO_NSEC (1000ull * 1000ull * 1000ull / sysconf(_SC_CLK_TCK)) + +int +virHostCPUGetStatsLinux(FILE *procstat, + int cpuNum, + virNodeCPUStatsPtr params, + int *nparams) +{ + int ret = -1; + char line[1024]; + unsigned long long usr, ni, sys, idle, iowait; + unsigned long long irq, softirq, steal, guest, guest_nice; + char cpu_header[4 + INT_BUFSIZE_BOUND(cpuNum)]; + + if ((*nparams) == 0) { + /* Current number of cpu stats supported by linux */ + *nparams = LINUX_NB_CPU_STATS; + ret = 0; + goto cleanup; + } + + if ((*nparams) != LINUX_NB_CPU_STATS) { + virReportInvalidArg(*nparams, + _("nparams in %s must be equal to %d"), + __FUNCTION__, LINUX_NB_CPU_STATS); + goto cleanup; + } + + if (cpuNum == VIR_NODE_CPU_STATS_ALL_CPUS) { + strcpy(cpu_header, "cpu "); + } else { + snprintf(cpu_header, sizeof(cpu_header), "cpu%d ", cpuNum); + } + + while (fgets(line, sizeof(line), procstat) != NULL) { + char *buf = line; + + if (STRPREFIX(buf, cpu_header)) { /* aka logical CPU time */ + if (sscanf(buf, + "%*s %llu %llu %llu %llu %llu" // user ~ iowait + "%llu %llu %llu %llu %llu", // irq ~ guest_nice + &usr, &ni, &sys, &idle, &iowait, + &irq, &softirq, &steal, &guest, &guest_nice) < 4) { + continue; + } + + if (virHostCPUStatsAssign(¶ms[0], VIR_NODE_CPU_STATS_KERNEL, + (sys + irq + softirq) * TICK_TO_NSEC) < 0) + goto cleanup; + + if (virHostCPUStatsAssign(¶ms[1], VIR_NODE_CPU_STATS_USER, + (usr + ni) * TICK_TO_NSEC) < 0) + goto cleanup; + + if (virHostCPUStatsAssign(¶ms[2], VIR_NODE_CPU_STATS_IDLE, + idle * TICK_TO_NSEC) < 0) + goto cleanup; + + if (virHostCPUStatsAssign(¶ms[3], VIR_NODE_CPU_STATS_IOWAIT, + iowait * TICK_TO_NSEC) < 0) + goto cleanup; + + ret = 0; + goto cleanup; + } + } + + virReportInvalidArg(cpuNum, + _("Invalid cpuNum in %s"), + __FUNCTION__); + + cleanup: + return ret; +} + + +static char * +virHostCPUGetGlobalPathLinux(const char *file) +{ + char *path = NULL; + + if (virAsprintf(&path, "%s/cpu/%s", sysfs_system_path, file) < 0) + return NULL; + + return path; +} + +static char * +virHostCPUGetPresentPathLinux(void) +{ + return virHostCPUGetGlobalPathLinux("present"); +} + +static char * +virHostCPUGetOnlinePathLinux(void) +{ + return virHostCPUGetGlobalPathLinux("online"); +} + +/* Determine the number of CPUs (maximum CPU id + 1) from a file containing + * a list of CPU ids, like the Linux sysfs cpu/present file */ +static int +virHostCPUParseCountLinux(const char *path) +{ + char *str = NULL; + char *tmp; + int ret = -1; + + if (virFileReadAll(path, 5 * VIR_HOST_CPU_MASK_LEN, &str) < 0) + goto cleanup; + + tmp = str; + do { + if (virStrToLong_i(tmp, &tmp, 10, &ret) < 0 || + !strchr(",-\n", *tmp)) { + virReportError(VIR_ERR_NO_SUPPORT, + _("failed to parse %s"), path); + ret = -1; + goto cleanup; + } + } while (*tmp++ != '\n'); + ret++; + + cleanup: + VIR_FREE(str); + return ret; +} + +/* + * Linux maintains cpu bit map under cpu/online. For example, if + * cpuid=5's flag is not set and max cpu is 7, the map file shows + * 0-4,6-7. This function parses it and returns cpumap. + */ +static virBitmapPtr +virHostCPUParseMapLinux(int max_cpuid, const char *path) +{ + virBitmapPtr map = NULL; + char *str = NULL; + + if (virFileReadAll(path, 5 * VIR_HOST_CPU_MASK_LEN, &str) < 0) + goto error; + + if (virBitmapParse(str, 0, &map, max_cpuid) < 0) + goto error; + + VIR_FREE(str); + return map; + + error: + VIR_FREE(str); + virBitmapFree(map); + return NULL; +} +#endif + + +int +virHostCPUGetInfo(virArch hostarch, + unsigned int *cpus, + unsigned int *mhz, + unsigned int *nodes, + unsigned int *sockets, + unsigned int *cores, + unsigned int *threads) +{ +#ifdef __linux__ + int ret = -1; + FILE *cpuinfo = fopen(CPUINFO_PATH, "r"); + + if (!cpuinfo) { + virReportSystemError(errno, + _("cannot open %s"), CPUINFO_PATH); + return -1; + } + + ret = virHostCPUGetInfoPopulateLinux(cpuinfo, hostarch, + cpus, mhz, nodes, + sockets, cores, threads); + if (ret < 0) + goto cleanup; + + cleanup: + VIR_FORCE_FCLOSE(cpuinfo); + return ret; +#elif defined(__FreeBSD__) || defined(__APPLE__) + unsigned long cpu_freq; + size_t cpu_freq_len = sizeof(cpu_freq); + + *cpus = virHostCPUGetCountAppleFreeBSD(); + if (*cpus == -1) + return -1; + + *nodes = 1; + *sockets = 1; + *cores = *cpus; + *threads = 1; + +# ifdef __FreeBSD__ + if (sysctlbyname("dev.cpu.0.freq", &cpu_freq, &cpu_freq_len, NULL, 0) < 0) { + virReportSystemError(errno, "%s", _("cannot obtain CPU freq")); + return -1; + } + + *mhz = cpu_freq; +# else + if (sysctlbyname("hw.cpufrequency", &cpu_freq, &cpu_freq_len, NULL, 0) < 0) { + virReportSystemError(errno, "%s", _("cannot obtain CPU freq")); + return -1; + } + + *mhz = cpu_freq / 1000000; +# endif + + return 0; +#else + /* XXX Solaris will need an impl later if they port QEMU driver */ + virReportError(VIR_ERR_NO_SUPPORT, "%s", + _("node info not implemented on this platform")); + return -1; +#endif +} + + +int +virHostCPUGetStats(int cpuNum ATTRIBUTE_UNUSED, + virNodeCPUStatsPtr params ATTRIBUTE_UNUSED, + int *nparams ATTRIBUTE_UNUSED, + unsigned int flags) +{ + virCheckFlags(0, -1); + +#ifdef __linux__ + { + int ret; + FILE *procstat = fopen(PROCSTAT_PATH, "r"); + if (!procstat) { + virReportSystemError(errno, + _("cannot open %s"), PROCSTAT_PATH); + return -1; + } + ret = virHostCPUGetStatsLinux(procstat, cpuNum, params, nparams); + VIR_FORCE_FCLOSE(procstat); + + return ret; + } +#elif defined(__FreeBSD__) + return virHostCPUGetStatsFreeBSD(cpuNum, params, nparams); +#else + virReportError(VIR_ERR_NO_SUPPORT, "%s", + _("node CPU stats not implemented on this platform")); + return -1; +#endif +} + + +int +virHostCPUGetCount(void) +{ +#if defined(__linux__) + /* To support older kernels that lack cpu/present, such as 2.6.18 + * in RHEL5, we fall back to count cpu/cpuNN entries; this assumes + * that such kernels also lack hotplug, and therefore cpu/cpuNN + * will be consecutive. + */ + char *present_path = NULL; + char *cpupath = NULL; + int ncpu = -1; + + if (!(present_path = virHostCPUGetPresentPathLinux())) + return -1; + + if (virFileExists(present_path)) { + ncpu = virHostCPUParseCountLinux(present_path); + goto cleanup; + } + + if (virAsprintf(&cpupath, "%s/cpu/cpu0", sysfs_system_path) < 0) + goto cleanup; + if (virFileExists(cpupath)) { + ncpu = 0; + do { + ncpu++; + VIR_FREE(cpupath); + if (virAsprintf(&cpupath, "%s/cpu/cpu%d", + sysfs_system_path, ncpu) < 0) { + ncpu = -1; + goto cleanup; + } + } while (virFileExists(cpupath)); + } else { + /* no cpu/cpu0: we give up */ + virReportError(VIR_ERR_NO_SUPPORT, "%s", + _("host cpu counting not supported on this node")); + } + + cleanup: + VIR_FREE(present_path); + VIR_FREE(cpupath); + return ncpu; +#elif defined(__FreeBSD__) || defined(__APPLE__) + return virHostCPUGetCountAppleFreeBSD(); +#else + virReportError(VIR_ERR_NO_SUPPORT, "%s", + _("host cpu counting not implemented on this platform")); + return -1; +#endif +} + +virBitmapPtr +virHostCPUGetPresentBitmap(void) +{ +#ifdef __linux__ + virBitmapPtr present_cpus = NULL; + char *present_path = NULL; + int npresent_cpus; + + if ((npresent_cpus = virHostCPUGetCount()) < 0) + goto cleanup; + + if (!(present_path = virHostCPUGetPresentPathLinux())) + goto cleanup; + + /* If the cpu/present file is available, parse it and exit */ + if (virFileExists(present_path)) { + present_cpus = virHostCPUParseMapLinux(npresent_cpus, present_path); + goto cleanup; + } + + /* If the file is not available, we can assume that the kernel is + * too old to support non-consecutive CPU ids and just mark all + * possible CPUs as present */ + if (!(present_cpus = virBitmapNew(npresent_cpus))) + goto cleanup; + + virBitmapSetAll(present_cpus); + + cleanup: + VIR_FREE(present_path); + + return present_cpus; +#endif + virReportError(VIR_ERR_NO_SUPPORT, "%s", + _("node present CPU map not implemented on this platform")); + return NULL; +} + +virBitmapPtr +virHostCPUGetOnlineBitmap(void) +{ +#ifdef __linux__ + char *online_path = NULL; + char *cpudir = NULL; + virBitmapPtr cpumap; + int present; + + present = virHostCPUGetCount(); + if (present < 0) + return NULL; + + if (!(online_path = virHostCPUGetOnlinePathLinux())) + return NULL; + if (virFileExists(online_path)) { + cpumap = virHostCPUParseMapLinux(present, online_path); + } else { + size_t i; + + cpumap = virBitmapNew(present); + if (!cpumap) + goto cleanup; + + if (virAsprintf(&cpudir, "%s/cpu", sysfs_system_path) < 0) + goto cleanup; + + for (i = 0; i < present; i++) { + int online = virHostCPUGetValue(cpudir, i, "online", 1); + if (online < 0) { + virBitmapFree(cpumap); + cpumap = NULL; + goto cleanup; + } + if (online) + ignore_value(virBitmapSetBit(cpumap, i)); + } + } + + cleanup: + VIR_FREE(online_path); + VIR_FREE(cpudir); + return cpumap; +#else + virReportError(VIR_ERR_NO_SUPPORT, "%s", + _("node online CPU map not implemented on this platform")); + return NULL; +#endif +} + + +int +virHostCPUGetMap(unsigned char **cpumap, + unsigned int *online, + unsigned int flags) +{ + virBitmapPtr cpus = NULL; + int ret = -1; + int dummy; + + virCheckFlags(0, -1); + + if (!cpumap && !online) + return virHostCPUGetCount(); + + if (!(cpus = virHostCPUGetOnlineBitmap())) + goto cleanup; + + if (cpumap && virBitmapToData(cpus, cpumap, &dummy) < 0) + goto cleanup; + if (online) + *online = virBitmapCountBits(cpus); + + ret = virBitmapSize(cpus); + + cleanup: + if (ret < 0 && cpumap) + VIR_FREE(*cpumap); + virBitmapFree(cpus); + return ret; +} + + +#if HAVE_LINUX_KVM_H && defined(KVM_CAP_PPC_SMT) + +/* Get the number of threads per subcore. + * + * This will be 2, 4 or 8 on POWER hosts, depending on the current + * micro-threading configuration, and 0 everywhere else. + * + * Returns the number of threads per subcore if subcores are in use, zero + * if subcores are not in use, and a negative value on error */ +int +virHostCPUGetThreadsPerSubcore(virArch arch) +{ + int threads_per_subcore = 0; + const char *kvmpath = "/dev/kvm"; + int kvmfd; + + if (ARCH_IS_PPC64(arch)) { + + /* It's okay if /dev/kvm doesn't exist, because + * a. we might be running in a guest + * b. the kvm module might not be installed or enabled + * In either case, falling back to the subcore-unaware thread + * counting logic is the right thing to do */ + if (!virFileExists(kvmpath)) + goto out; + + if ((kvmfd = open(kvmpath, O_RDONLY)) < 0) { + /* This can happen when running as a regular user if + * permissions are tight enough, in which case erroring out + * is better than silently falling back and reporting + * different nodeinfo depending on the user */ + virReportSystemError(errno, + _("Failed to open '%s'"), + kvmpath); + threads_per_subcore = -1; + goto out; + } + + /* For Phyp and KVM based guests the ioctl for KVM_CAP_PPC_SMT + * returns zero and both primary and secondary threads will be + * online */ + threads_per_subcore = ioctl(kvmfd, + KVM_CHECK_EXTENSION, + KVM_CAP_PPC_SMT); + + VIR_FORCE_CLOSE(kvmfd); + } + + out: + return threads_per_subcore; +} + +#else + +/* Fallback for nodeGetThreadsPerSubcore() used when KVM headers + * are not available on the system */ +int +virHostCPUGetThreadsPerSubcore(virArch arch ATTRIBUTE_UNUSED) +{ + return 0; +} + +#endif /* HAVE_LINUX_KVM_H && defined(KVM_CAP_PPC_SMT) */ diff --git a/src/util/virhostcpu.h b/src/util/virhostcpu.h new file mode 100644 index 0000000..e5ffc70 --- /dev/null +++ b/src/util/virhostcpu.h @@ -0,0 +1,54 @@ +/* + * virhostcpu.h: helper APIs for host CPU info + * + * Copyright (C) 2006-2016 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + * + * Author: Daniel P. Berrange <berrange@xxxxxxxxxx> + */ + +#ifndef __VIR_HOSTCPU_H__ +# define __VIR_HOSTCPU_H__ + +# include "internal.h" +# include "virarch.h" +# include "virbitmap.h" + +# define VIR_HOST_CPU_MASK_LEN 1024 + +int virHostCPUGetStats(int cpuNum, + virNodeCPUStatsPtr params, + int *nparams, + unsigned int flags); + +virBitmapPtr virHostCPUGetPresentBitmap(void); +virBitmapPtr virHostCPUGetOnlineBitmap(void); +int virHostCPUGetCount(void); +int virHostCPUGetThreadsPerSubcore(virArch arch); + +int virHostCPUGetMap(unsigned char **cpumap, + unsigned int *online, + unsigned int flags); +int virHostCPUGetInfo(virArch hostarch, + unsigned int *cpus, + unsigned int *mhz, + unsigned int *nodes, + unsigned int *sockets, + unsigned int *cores, + unsigned int *threads); + +#endif /* __VIR_HOSTCPU_H__*/ diff --git a/src/nodeinfopriv.h b/src/util/virhostcpupriv.h similarity index 86% rename from src/nodeinfopriv.h rename to src/util/virhostcpupriv.h index 0fd4391..de30983 100644 --- a/src/nodeinfopriv.h +++ b/src/util/virhostcpupriv.h @@ -1,7 +1,7 @@ /* - * nodeinfopriv.h: internal APIs for testing nodeinfo code + * virhostcpupriv.h: helper APIs for host CPU info * - * Copyright (C) 2014 Red Hat, Inc. + * Copyright (C) 2014-2016 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -19,10 +19,10 @@ * */ -#ifndef __NODEINFO_PRIV_H__ -# define __NODEINFO_PRIV_H__ +#ifndef __VIR_HOSTCPU_PRIV_H__ +# define __VIR_HOSTCPU_PRIV_H__ -# include "nodeinfo.h" +# include "virhostcpu.h" # ifdef __linux__ void virHostCPUSetSysFSSystemPathLinux(const char *path); @@ -42,4 +42,4 @@ int virHostCPUGetStatsLinux(FILE *procstat, int *nparams); # endif -#endif /* __NODEINFO_PRIV_H__ */ +#endif /* __VIR_HOSTCPU_PRIV_H__ */ diff --git a/tests/Makefile.am b/tests/Makefile.am index db4f88b..16677cb 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -107,7 +107,7 @@ EXTRA_DIST = \ networkxml2xmlupdateout \ nodedevschemadata \ nodedevschematest \ - nodeinfodata \ + virhostcpudata \ nssdata \ nwfilterschematest \ nwfilterxml2firewalldata \ @@ -160,7 +160,7 @@ EXTRA_DIST = \ test_helpers = commandhelper ssh test_conf test_programs = virshtest sockettest \ - nodeinfotest virbuftest \ + virhostcputest virbuftest \ commandtest seclabeltest \ virhashtest \ viratomictest \ @@ -426,7 +426,7 @@ test_libraries = libshunload.la \ vircgroupmock.la \ virpcimock.la \ virnetdevmock.la \ - nodeinfomock.la \ + virhostcpumock.la \ nssmock.la \ $(NULL) if WITH_QEMU @@ -875,9 +875,9 @@ test_conf_SOURCES = \ test_conf.c test_conf_LDADD = $(LDADDS) -nodeinfotest_SOURCES = \ - nodeinfotest.c testutils.h testutils.c -nodeinfotest_LDADD = $(LDADDS) +virhostcputest_SOURCES = \ + virhostcputest.c testutils.h testutils.c +virhostcputest_LDADD = $(LDADDS) commandtest_SOURCES = \ commandtest.c testutils.h testutils.c @@ -1068,11 +1068,11 @@ virpcimock_la_CFLAGS = $(AM_CFLAGS) virpcimock_la_LDFLAGS = $(MOCKLIBS_LDFLAGS) virpcimock_la_LIBADD = $(MOCKLIBS_LIBS) -nodeinfomock_la_SOURCES = \ - nodeinfomock.c -nodeinfomock_la_CFLAGS = $(AM_CFLAGS) -nodeinfomock_la_LDFLAGS = $(MOCKLIBS_LDFLAGS) -nodeinfomock_la_LIBADD = $(MOCKLIBS_LIBS) +virhostcpumock_la_SOURCES = \ + virhostcpumock.c +virhostcpumock_la_CFLAGS = $(AM_CFLAGS) +virhostcpumock_la_LDFLAGS = $(MOCKLIBS_LDFLAGS) +virhostcpumock_la_LIBADD = $(MOCKLIBS_LIBS) nsstest_SOURCES = \ nsstest.c testutils.h testutils.c diff --git a/tests/vircgrouptest.c b/tests/vircgrouptest.c index 148721d..566ffa8 100644 --- a/tests/vircgrouptest.c +++ b/tests/vircgrouptest.c @@ -34,7 +34,7 @@ # include "virfile.h" # include "virbuffer.h" # include "testutilslxc.h" -# include "nodeinfo.h" +# include "virhostcpu.h" # define VIR_FROM_THIS VIR_FROM_NONE diff --git a/tests/nodeinfodata/linux-aarch64-f21-mustang.cpuinfo b/tests/virhostcpudata/linux-aarch64-f21-mustang.cpuinfo similarity index 100% rename from tests/nodeinfodata/linux-aarch64-f21-mustang.cpuinfo rename to tests/virhostcpudata/linux-aarch64-f21-mustang.cpuinfo diff --git a/tests/nodeinfodata/linux-aarch64-f21-mustang.expected b/tests/virhostcpudata/linux-aarch64-f21-mustang.expected similarity index 100% rename from tests/nodeinfodata/linux-aarch64-f21-mustang.expected rename to tests/virhostcpudata/linux-aarch64-f21-mustang.expected diff --git a/tests/nodeinfodata/linux-aarch64-rhelsa-3.19.0-mustang.cpuinfo b/tests/virhostcpudata/linux-aarch64-rhelsa-3.19.0-mustang.cpuinfo similarity index 100% rename from tests/nodeinfodata/linux-aarch64-rhelsa-3.19.0-mustang.cpuinfo rename to tests/virhostcpudata/linux-aarch64-rhelsa-3.19.0-mustang.cpuinfo [...snipped countless more renames...] diff --git a/tests/nodeinfomock.c b/tests/virhostcpumock.c similarity index 95% rename from tests/nodeinfomock.c rename to tests/virhostcpumock.c index ac12b5e..00d10f1 100644 --- a/tests/nodeinfomock.c +++ b/tests/virhostcpumock.c @@ -19,8 +19,7 @@ #include <config.h> -#include "internal.h" -#include "nodeinfo.h" +#include "virhostcpu.h" int virHostCPUGetThreadsPerSubcore(virArch arch) diff --git a/tests/nodeinfotest.c b/tests/virhostcputest.c similarity index 89% rename from tests/nodeinfotest.c rename to tests/virhostcputest.c index 50b1c82..0643876 100644 --- a/tests/nodeinfotest.c +++ b/tests/virhostcputest.c @@ -7,7 +7,7 @@ #include "testutils.h" #include "internal.h" -#include "nodeinfopriv.h" +#include "virhostcpupriv.h" #include "virfile.h" #include "virstring.h" @@ -155,26 +155,26 @@ linuxCPUStatsCompareFiles(const char *cpustatfile, } -struct linuxTestNodeInfoData { +struct linuxTestHostCPUData { const char *testName; virArch arch; }; static int -linuxTestNodeInfo(const void *opaque) +linuxTestHostCPU(const void *opaque) { int result = -1; char *cpuinfo = NULL; char *sysfs_prefix = NULL; char *output = NULL; - struct linuxTestNodeInfoData *data = (struct linuxTestNodeInfoData *) opaque; + struct linuxTestHostCPUData *data = (struct linuxTestHostCPUData *) opaque; const char *archStr = virArchToString(data->arch); - if (virAsprintf(&sysfs_prefix, "%s/nodeinfodata/linux-%s", + if (virAsprintf(&sysfs_prefix, "%s/virhostcpudata/linux-%s", abs_srcdir, data->testName) < 0 || - virAsprintf(&cpuinfo, "%s/nodeinfodata/linux-%s-%s.cpuinfo", + virAsprintf(&cpuinfo, "%s/virhostcpudata/linux-%s-%s.cpuinfo", abs_srcdir, archStr, data->testName) < 0 || - virAsprintf(&output, "%s/nodeinfodata/linux-%s-%s.expected", + virAsprintf(&output, "%s/virhostcpudata/linux-%s-%s.expected", abs_srcdir, archStr, data->testName) < 0) { goto cleanup; } @@ -204,9 +204,9 @@ linuxTestNodeCPUStats(const void *data) char *cpustatfile = NULL; char *outfile = NULL; - if (virAsprintf(&cpustatfile, "%s/nodeinfodata/linux-cpustat-%s.stat", + if (virAsprintf(&cpustatfile, "%s/virhostcpudata/linux-cpustat-%s.stat", abs_srcdir, testData->name) < 0 || - virAsprintf(&outfile, "%s/nodeinfodata/linux-cpustat-%s.out", + virAsprintf(&outfile, "%s/virhostcpudata/linux-cpustat-%s.out", abs_srcdir, testData->name) < 0) goto fail; @@ -225,7 +225,7 @@ mymain(void) { int ret = 0; size_t i; - const struct linuxTestNodeInfoData nodeData[] = { + const struct linuxTestHostCPUData nodeData[] = { {"test1", VIR_ARCH_X86_64}, {"test1", VIR_ARCH_PPC}, {"test2", VIR_ARCH_X86_64}, @@ -251,7 +251,7 @@ mymain(void) return EXIT_FAILURE; for (i = 0; i < ARRAY_CARDINALITY(nodeData); i++) - if (virtTestRun(nodeData[i].testName, linuxTestNodeInfo, &nodeData[i]) != 0) + if (virtTestRun(nodeData[i].testName, linuxTestHostCPU, &nodeData[i]) != 0) ret = -1; # define DO_TEST_CPU_STATS(name, ncpus) \ @@ -266,6 +266,6 @@ mymain(void) return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } -VIRT_TEST_MAIN_PRELOAD(mymain, abs_builddir "/.libs/nodeinfomock.so") +VIRT_TEST_MAIN_PRELOAD(mymain, abs_builddir "/.libs/virhostcpumock.so") #endif /* __linux__ */ -- 2.5.5 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list