diff options
-rw-r--r-- | psutil/__init__.py | 30 | ||||
-rw-r--r-- | psutil/_psbsd.py | 7 | ||||
-rw-r--r-- | psutil/_pslinux.py | 24 | ||||
-rw-r--r-- | psutil/_psosx.py | 12 | ||||
-rw-r--r-- | psutil/_psutil_bsd.c | 8 | ||||
-rw-r--r-- | psutil/_psutil_common.c | 17 | ||||
-rw-r--r-- | psutil/_psutil_common.h | 1 | ||||
-rw-r--r-- | psutil/_psutil_linux.c | 29 | ||||
-rw-r--r-- | psutil/_psutil_osx.c | 8 | ||||
-rw-r--r-- | psutil/_psutil_windows.c | 3 | ||||
-rw-r--r-- | psutil/_pswindows.py | 17 | ||||
-rw-r--r-- | psutil/arch/freebsd/cpu.c | 29 | ||||
-rw-r--r-- | psutil/arch/freebsd/cpu.h | 1 | ||||
-rw-r--r-- | psutil/arch/netbsd/specific.c | 29 | ||||
-rw-r--r-- | psutil/arch/netbsd/specific.h | 20 | ||||
-rw-r--r-- | psutil/arch/openbsd/cpu.c | 28 | ||||
-rw-r--r-- | psutil/arch/openbsd/cpu.h | 6 | ||||
-rw-r--r-- | psutil/arch/osx/cpu.c | 229 | ||||
-rw-r--r-- | psutil/arch/osx/cpu.h | 12 | ||||
-rw-r--r-- | psutil/arch/windows/cpu.c | 557 | ||||
-rw-r--r-- | psutil/arch/windows/cpu.h | 4 | ||||
-rw-r--r-- | psutil/tests/__init__.py | 3 | ||||
-rwxr-xr-x | psutil/tests/test_memleaks.py | 5 | ||||
-rwxr-xr-x | psutil/tests/test_osx.py | 55 | ||||
-rwxr-xr-x | psutil/tests/test_system.py | 9 | ||||
-rwxr-xr-x | psutil/tests/test_windows.py | 10 | ||||
-rwxr-xr-x | setup.py | 2 |
27 files changed, 1106 insertions, 49 deletions
diff --git a/psutil/__init__.py b/psutil/__init__.py index 6deebb26..9b781d2b 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -184,7 +184,7 @@ __all__ = [ "pid_exists", "pids", "process_iter", "wait_procs", # proc "virtual_memory", "swap_memory", # memory "cpu_times", "cpu_percent", "cpu_times_percent", "cpu_count", # cpu - "cpu_stats", # "cpu_freq", "getloadavg" + "cpu_stats", # "cpu_freq", "getloadavg", "cpu_info", "net_io_counters", "net_connections", "net_if_addrs", # network "net_if_stats", "disk_io_counters", "disk_partitions", "disk_usage", # disk @@ -1899,6 +1899,34 @@ if hasattr(_psplatform, "cpu_freq"): __all__.append("cpu_freq") +if hasattr(_psplatform, "cpu_info"): + + def cpu_info(): + """Return CPU varius types of information about the CPU as a dict. + Usually used in conjunction with cpu_count(). + Dictionary keys availability: + + * "model": Linux, macOS, Windows, FreeBSD, NetBSD, OpenBSD + * "vendor": Linux, macOS, Windows, OpenBSD + * "arch": Linux, macOS, Windows + * "byteorder": all + * "l1d_cache": Linux, macOS + * "l1i_cache": Linux, macOS + * "l1_cache": Windows + * "l2_cache": Linux, macOS, Windows + * "l3_cache": Linux, macOS, Windows + * "flags": Linux, macOS, Windows + """ + ret = _psplatform.cpu_info() + if 'arch' not in ret and POSIX: + ret['arch'] = os.uname()[4] + if 'byteorder' not in ret: + ret['byteorder'] = sys.byteorder + return ret + + __all__.append("cpu_info") + + if hasattr(os, "getloadavg") or hasattr(_psplatform, "getloadavg"): # Perform this hasattr check once on import time to either use the # platform based code or proxy straight from the os module. diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py index c4200cce..ead05381 100644 --- a/psutil/_psbsd.py +++ b/psutil/_psbsd.py @@ -280,6 +280,13 @@ else: return ret +def cpu_info(): + d = dict(model=cext.cpu_model()) + if OPENBSD: + d["vendor"] = cext.cpu_vendor() + return d + + def cpu_stats(): """Return various CPU stats as a named tuple.""" if FREEBSD: diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py index 5f149a03..c4ec0c94 100644 --- a/psutil/_pslinux.py +++ b/psutil/_pslinux.py @@ -680,6 +680,29 @@ def cpu_count_cores(): return result or None # mimic os.cpu_count() +def cpu_info(): + def lookup_in_lines(lines, text): + for line in lines: + if line.startswith(text): + return line.split('\t:', 1)[1].strip() + return None + + with open_text('%s/cpuinfo' % get_procfs_path()) as f: + lines = f.readlines() + + caches = cext.cpu_caches() + return dict( + # strings + model=lookup_in_lines(lines, "model name"), + vendor=lookup_in_lines(lines, 'vendor_id'), + flags=lookup_in_lines(lines, 'flags'), + l1i_cache=caches[0] if caches[0] != -1 else None, + l1d_cache=caches[1] if caches[1] != -1 else None, + l2_cache=caches[2] if caches[2] != -1 else None, + l3_cache=caches[3] if caches[3] != -1 else None, + ) + + def cpu_stats(): """Return various CPU stats as a named tuple.""" with open_binary('%s/stat' % get_procfs_path()) as f: @@ -714,6 +737,7 @@ def _cpu_get_cpuinfo_freq(): if os.path.exists("/sys/devices/system/cpu/cpufreq/policy0") or \ os.path.exists("/sys/devices/system/cpu/cpu0/cpufreq"): + def cpu_freq(): """Return frequency metrics for all CPUs. Contrarily to other OSes, Linux updates these values in diff --git a/psutil/_psosx.py b/psutil/_psosx.py index ac8ecc53..b54c2101 100644 --- a/psutil/_psosx.py +++ b/psutil/_psosx.py @@ -170,6 +170,18 @@ def cpu_stats(): ctx_switches, interrupts, soft_interrupts, syscalls) +def cpu_info(): + return dict( + model=cext.cpu_model(), + vendor=cext.cpu_vendor(), + l1i_cache=cext.cpu_l1i_cache(), + l1d_cache=cext.cpu_l1d_cache(), + l2_cache=cext.cpu_l2_cache(), + l3_cache=cext.cpu_l3_cache(), + flags=cext.cpu_flags(), + ) + + def cpu_freq(): """Return CPU frequency. On macOS per-cpu frequency is not supported. diff --git a/psutil/_psutil_bsd.c b/psutil/_psutil_bsd.c index 13170838..a1aec439 100644 --- a/psutil/_psutil_bsd.c +++ b/psutil/_psutil_bsd.c @@ -1112,12 +1112,20 @@ static PyMethodDef mod_methods[] = { {"sensors_battery", psutil_sensors_battery, METH_VARARGS}, {"sensors_cpu_temperature", psutil_sensors_cpu_temperature, METH_VARARGS}, #endif +#if defined(PSUTIL_NETBSD) + {"cpu_model", psutil_cpu_model, METH_VARARGS, ""}, +#endif +#if defined(PSUTIL_OPENBSD) + {"cpu_vendor", psutil_cpu_vendor, METH_VARARGS, ""}, + {"cpu_model", psutil_cpu_model, METH_VARARGS, ""}, +#endif // --- others {"set_debug", psutil_set_debug, METH_VARARGS}, {NULL, NULL, 0, NULL} }; + #if PY_MAJOR_VERSION >= 3 #define INITERR return NULL diff --git a/psutil/_psutil_common.c b/psutil/_psutil_common.c index 9679da67..31a62008 100644 --- a/psutil/_psutil_common.c +++ b/psutil/_psutil_common.c @@ -151,6 +151,23 @@ psutil_set_debug(PyObject *self, PyObject *args) { } +/* + * Add a new python object to an existing dict, DECREFing that object and + * setting it to NULL both in case of success or failure. + */ +int +psutil_add_to_dict(PyObject *py_dict, char *keyname, PyObject *py_obj) { + if (!py_obj) + return 1; + if (PyDict_SetItemString(py_dict, keyname, py_obj)) { + Py_CLEAR(py_obj); + return 1; + } + Py_CLEAR(py_obj); + return 0; +} + + // ============================================================================ // Utility functions (BSD) // ============================================================================ diff --git a/psutil/_psutil_common.h b/psutil/_psutil_common.h index 591f5521..6c3d97e4 100644 --- a/psutil/_psutil_common.h +++ b/psutil/_psutil_common.h @@ -99,6 +99,7 @@ PyObject* PyErr_SetFromOSErrnoWithSyscall(const char *syscall); // --- Global utils // ==================================================================== +int psutil_add_to_dict(PyObject *py_dict, char *keyname, PyObject *py_obj); PyObject* psutil_set_debug(PyObject *self, PyObject *args); int psutil_setup(void); diff --git a/psutil/_psutil_linux.c b/psutil/_psutil_linux.c index 70cf5d1b..743fc786 100644 --- a/psutil/_psutil_linux.c +++ b/psutil/_psutil_linux.c @@ -469,6 +469,32 @@ error: /* + * Return L1/2/3 CPU cache sizes. + */ +static PyObject* +psutil_cpu_caches(PyObject* self, PyObject* args) { + long l1i = -1; + long l1d = -1; + long l2 = -1; + long l3 = -1; + +#ifdef _SC_LEVEL1_ICACHE_SIZE + l1i = sysconf(_SC_LEVEL1_ICACHE_SIZE); +#endif +#ifdef _SC_LEVEL1_DCACHE_SIZE + l1d = sysconf(_SC_LEVEL1_DCACHE_SIZE); +#endif +#ifdef _SC_LEVEL2_CACHE_SIZE + l2 = sysconf(_SC_LEVEL2_CACHE_SIZE); +#endif +#ifdef _SC_LEVEL3_CACHE_SIZE + l3 = sysconf(_SC_LEVEL3_CACHE_SIZE); +#endif + return Py_BuildValue("llll", l1i, l1d, l2, l3); +} + + +/* * Module init. */ @@ -484,9 +510,10 @@ static PyMethodDef mod_methods[] = { {"proc_cpu_affinity_set", psutil_proc_cpu_affinity_set, METH_VARARGS}, #endif // --- system related functions + {"cpu_caches", psutil_cpu_caches, METH_VARARGS}, {"disk_partitions", psutil_disk_partitions, METH_VARARGS}, - {"users", psutil_users, METH_VARARGS}, {"net_if_duplex_speed", psutil_net_if_duplex_speed, METH_VARARGS}, + {"users", psutil_users, METH_VARARGS}, // --- linux specific {"linux_sysinfo", psutil_linux_sysinfo, METH_VARARGS}, diff --git a/psutil/_psutil_osx.c b/psutil/_psutil_osx.c index 2470c3eb..5e74cadd 100644 --- a/psutil/_psutil_osx.c +++ b/psutil/_psutil_osx.c @@ -24,7 +24,6 @@ #include <mach/mach.h> #include <mach/mach_vm.h> #include <mach/shared_region.h> - #include <mach-o/loader.h> #include <CoreFoundation/CoreFoundation.h> @@ -1662,9 +1661,16 @@ static PyMethodDef mod_methods[] = { {"boot_time", psutil_boot_time, METH_VARARGS}, {"cpu_count_cores", psutil_cpu_count_cores, METH_VARARGS}, {"cpu_count_logical", psutil_cpu_count_logical, METH_VARARGS}, + {"cpu_flags", psutil_cpu_flags, METH_VARARGS}, {"cpu_freq", psutil_cpu_freq, METH_VARARGS}, + {"cpu_l1d_cache", psutil_cpu_l1d_cache, METH_VARARGS}, + {"cpu_l1i_cache", psutil_cpu_l1i_cache, METH_VARARGS}, + {"cpu_l2_cache", psutil_cpu_l2_cache, METH_VARARGS}, + {"cpu_l3_cache", psutil_cpu_l3_cache, METH_VARARGS}, + {"cpu_model", psutil_cpu_model, METH_VARARGS}, {"cpu_stats", psutil_cpu_stats, METH_VARARGS}, {"cpu_times", psutil_cpu_times, METH_VARARGS}, + {"cpu_vendor", psutil_cpu_vendor, METH_VARARGS}, {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS}, {"disk_partitions", psutil_disk_partitions, METH_VARARGS}, {"net_io_counters", psutil_net_io_counters, METH_VARARGS}, diff --git a/psutil/_psutil_windows.c b/psutil/_psutil_windows.c index 83da3a26..5ce2ee02 100644 --- a/psutil/_psutil_windows.c +++ b/psutil/_psutil_windows.c @@ -1587,6 +1587,7 @@ PsutilMethods[] = { {"cpu_count_cores", psutil_cpu_count_cores, METH_VARARGS}, {"cpu_count_logical", psutil_cpu_count_logical, METH_VARARGS}, {"cpu_freq", psutil_cpu_freq, METH_VARARGS}, + {"cpu_info", psutil_cpu_info, METH_VARARGS}, {"cpu_stats", psutil_cpu_stats, METH_VARARGS}, {"cpu_times", psutil_cpu_times, METH_VARARGS}, {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS}, @@ -1616,6 +1617,7 @@ PsutilMethods[] = { {"winservice_stop", psutil_winservice_stop, METH_VARARGS}, // --- windows API bindings + {"GetLogicalProcessorInformationEx", psutil_GetLogicalProcessorInformationEx, METH_VARARGS}, {"QueryDosDevice", psutil_QueryDosDevice, METH_VARARGS}, // --- others @@ -1625,6 +1627,7 @@ PsutilMethods[] = { }; + struct module_state { PyObject *error; }; diff --git a/psutil/_pswindows.py b/psutil/_pswindows.py index 9966b1b4..55dc7173 100644 --- a/psutil/_pswindows.py +++ b/psutil/_pswindows.py @@ -315,7 +315,7 @@ def cpu_count_logical(): def cpu_count_cores(): """Return the number of CPU cores in the system.""" - return cext.cpu_count_cores() + return cext.GetLogicalProcessorInformationEx()['cores'] or None def cpu_stats(): @@ -335,6 +335,21 @@ def cpu_freq(): return [_common.scpufreq(float(curr), min_, float(max_))] +def cpu_info(): + """Return CPU hardware-related information.""" + ret = cext.cpu_info() + infoex = cext.GetLogicalProcessorInformationEx() + ret['l1_cache'] = infoex['l1_cache'] + ret['l2_cache'] = infoex['l2_cache'] + ret['l3_cache'] = infoex['l3_cache'] + # https://superuser.com/a/1441469 + # "AMD64", "X86", "IA64", "ARM64", "EM64T" + ret['arch'] = \ + os.environ.get('PROCESSOR_ARCHITEW6432', '') or \ + os.environ.get('PROCESSOR_ARCHITECTURE', '') or None + return ret + + _loadavg_inititialized = False diff --git a/psutil/arch/freebsd/cpu.c b/psutil/arch/freebsd/cpu.c index f31e9bb0..8ab6beac 100644 --- a/psutil/arch/freebsd/cpu.c +++ b/psutil/arch/freebsd/cpu.c @@ -128,3 +128,32 @@ error: PyErr_SetFromErrno(PyExc_OSError); return NULL; } + + +PyObject * +psutil_cpu_model(PyObject *self, PyObject *args) { + void *buf = NULL; + size_t size = 0; + PyObject *py_str; + + if (sysctlbyname("hw.model", NULL, &size, NULL, 0)) + goto error; + + buf = malloc(size); + if (!buf) { + PyErr_NoMemory(); + return NULL; + } + + if (sysctlbyname("hw.model", buf, &size, NULL, 0)) + goto error; + + py_str = Py_BuildValue("s", buf); + free(buf); + return py_str; + +error: + if (buf != NULL) + free(buf); + Py_RETURN_NONE; +} diff --git a/psutil/arch/freebsd/cpu.h b/psutil/arch/freebsd/cpu.h index 8decd773..9676c523 100644 --- a/psutil/arch/freebsd/cpu.h +++ b/psutil/arch/freebsd/cpu.h @@ -9,3 +9,4 @@ PyObject* psutil_cpu_freq(PyObject* self, PyObject* args); PyObject* psutil_cpu_stats(PyObject* self, PyObject* args); PyObject* psutil_cpu_topology(PyObject* self, PyObject* args); +PyObject* psutil_cpu_model(PyObject* self, PyObject* args); diff --git a/psutil/arch/netbsd/specific.c b/psutil/arch/netbsd/specific.c index 4e286e5e..e9f8a915 100644 --- a/psutil/arch/netbsd/specific.c +++ b/psutil/arch/netbsd/specific.c @@ -686,3 +686,32 @@ psutil_cpu_stats(PyObject *self, PyObject *args) { uv.forks // forks ); } + + +PyObject * +psutil_cpu_model(PyObject *self, PyObject *args) { + void *buf = NULL; + size_t size = 0; + PyObject *py_str; + + if (sysctlbyname("machdep.cpu_brand", NULL, &size, NULL, 0)) + goto error; + + buf = malloc(size); + if (!buf) { + PyErr_NoMemory(); + return NULL; + } + + if (sysctlbyname("machdep.cpu_brand", buf, &size, NULL, 0)) + goto error; + + py_str = Py_BuildValue("s", buf); + free(buf); + return py_str; + +error: + if (buf != NULL) + free(buf); + Py_RETURN_NONE; +} diff --git a/psutil/arch/netbsd/specific.h b/psutil/arch/netbsd/specific.h index 391ed164..1892756b 100644 --- a/psutil/arch/netbsd/specific.h +++ b/psutil/arch/netbsd/specific.h @@ -14,16 +14,16 @@ struct kinfo_file * kinfo_getfile(pid_t pid, int* cnt); int psutil_get_proc_list(kinfo_proc **procList, size_t *procCount); char *psutil_get_cmd_args(pid_t pid, size_t *argsize); -// +PyObject *psutil_cpu_model(PyObject *self, PyObject *args); +PyObject *psutil_cpu_stats(PyObject* self, PyObject* args); +PyObject *psutil_disk_io_counters(PyObject* self, PyObject* args); PyObject *psutil_get_cmdline(pid_t pid); -PyObject *psutil_proc_threads(PyObject *self, PyObject *args); -PyObject *psutil_virtual_mem(PyObject *self, PyObject *args); -PyObject *psutil_swap_mem(PyObject *self, PyObject *args); -PyObject *psutil_proc_num_fds(PyObject *self, PyObject *args); -PyObject *psutil_proc_connections(PyObject *self, PyObject *args); PyObject *psutil_per_cpu_times(PyObject *self, PyObject *args); -PyObject* psutil_disk_io_counters(PyObject* self, PyObject* args); -PyObject* psutil_proc_exe(PyObject* self, PyObject* args); -PyObject* psutil_proc_num_threads(PyObject* self, PyObject* args); -PyObject* psutil_cpu_stats(PyObject* self, PyObject* args); +PyObject *psutil_proc_connections(PyObject *self, PyObject *args); PyObject *psutil_proc_cwd(PyObject *self, PyObject *args); +PyObject *psutil_proc_exe(PyObject* self, PyObject* args); +PyObject *psutil_proc_num_fds(PyObject *self, PyObject *args); +PyObject *psutil_proc_num_threads(PyObject* self, PyObject* args); +PyObject *psutil_proc_threads(PyObject *self, PyObject *args); +PyObject *psutil_swap_mem(PyObject *self, PyObject *args); +PyObject *psutil_virtual_mem(PyObject *self, PyObject *args); diff --git a/psutil/arch/openbsd/cpu.c b/psutil/arch/openbsd/cpu.c index 0691fd1f..3724843e 100644 --- a/psutil/arch/openbsd/cpu.c +++ b/psutil/arch/openbsd/cpu.c @@ -107,3 +107,31 @@ psutil_cpu_freq(PyObject *self, PyObject *args) { return Py_BuildValue("i", freq); } + + +PyObject * +psutil_cpu_vendor(PyObject *self, PyObject *args) { + char vendor[128]; + size_t size = sizeof(vendor); + int mib[2] = {CTL_HW, HW_VENDOR}; + + if (sysctl(mib, 2, vendor, &size, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + return Py_BuildValue("s", vendor); +} + + +PyObject * +psutil_cpu_model(PyObject *self, PyObject *args) { + char product[128]; + size_t size = sizeof(product); + int mib[2] = {CTL_HW, HW_PRODUCT}; + + if (sysctl(mib, 2, product, &size, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + return Py_BuildValue("s", product); +} diff --git a/psutil/arch/openbsd/cpu.h b/psutil/arch/openbsd/cpu.h index 07bf95fd..5a6d3a65 100644 --- a/psutil/arch/openbsd/cpu.h +++ b/psutil/arch/openbsd/cpu.h @@ -7,6 +7,8 @@ #include <Python.h> -PyObject *psutil_cpu_freq(PyObject* self, PyObject* args); -PyObject *psutil_cpu_stats(PyObject* self, PyObject* args); +PyObject *psutil_cpu_freq(PyObject *self, PyObject* args); +PyObject *psutil_cpu_model(PyObject *self, PyObject* args); +PyObject *psutil_cpu_stats(PyObject *self, PyObject* args); +PyObject *psutil_cpu_vendor(PyObject *self, PyObject* args); PyObject *psutil_per_cpu_times(PyObject *self, PyObject *args); diff --git a/psutil/arch/osx/cpu.c b/psutil/arch/osx/cpu.c index 37141a2d..cdd943e1 100644 --- a/psutil/arch/osx/cpu.c +++ b/psutil/arch/osx/cpu.c @@ -7,6 +7,15 @@ /* System-wide CPU related functions. +References: +- https://opensource.apple.com/source/xnu/xnu-1456.1.26/bsd/sys/sysctl.h.auto.html +- sysctl C types: https://ss64.com/osx/sysctl.html +- https://apple.stackexchange.com/questions/238777 +- it looks like CPU "sockets" on macOS are called "packages" +- it looks like macOS does not support NUMA nodes: + https://apple.stackexchange.com/questions/36465/do-mac-pros-use-numa +- $ sysctl -a | grep machdep.cpu + Original code was refactored and moved from psutil/_psutil_osx.c in 2020 right before a4c0a0eb0d2a872ab7a45e47fcf37ef1fde5b012. For reference, here's the git history with original implementations: @@ -18,8 +27,11 @@ For reference, here's the git history with original implementations: - CPU frequency: 6ba1ac4ebfcd8c95fca324b15606ab0ec1412d39 */ + #include <Python.h> #include <sys/sysctl.h> +#include <ctype.h> +#include <sys/sysctl.h> #include <sys/vmmeter.h> #include <mach/mach_error.h> @@ -30,7 +42,6 @@ For reference, here's the git history with original implementations: #include "../../_psutil_posix.h" - PyObject * psutil_cpu_count_logical(PyObject *self, PyObject *args) { int num; @@ -56,6 +67,20 @@ psutil_cpu_count_cores(PyObject *self, PyObject *args) { PyObject * +psutil_cpu_sockets() { + // It looks like on macOS "sockets" are called "packages". + // Hopefully it's the same thing. + int value; + size_t size = sizeof(value); + + if (sysctlbyname("hw.packages", &value, &size, NULL, 2)) + Py_RETURN_NONE; + else + return Py_BuildValue("i", value); +} + + +PyObject * psutil_cpu_times(PyObject *self, PyObject *args) { mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT; kern_return_t error; @@ -138,3 +163,205 @@ psutil_cpu_freq(PyObject *self, PyObject *args) { min / 1000 / 1000, max / 1000 / 1000); } + + +PyObject * +psutil_cpu_model() { + size_t len; + char *buffer; + PyObject *py_str = NULL; + + if (sysctlbyname("machdep.cpu.brand_string", NULL, &len, NULL, 0) != 0) + Py_RETURN_NONE; + + buffer = malloc(len); + if (sysctlbyname("machdep.cpu.brand_string", buffer, &len, NULL, 0) != 0) { + free(buffer); + Py_RETURN_NONE; + } + + py_str = Py_BuildValue("s", buffer); + free(buffer); + return py_str; +} + + +PyObject * +psutil_cpu_vendor() { + size_t len; + char *buffer; + PyObject *py_str = NULL; + + if (sysctlbyname("machdep.cpu.vendor", NULL, &len, NULL, 0) != 0) + Py_RETURN_NONE; + + buffer = malloc(len); + if (sysctlbyname("machdep.cpu.vendor", buffer, &len, NULL, 0) != 0) { + free(buffer); + Py_RETURN_NONE; + } + + py_str = Py_BuildValue("s", buffer); + free(buffer); + return py_str; +} + + +PyObject * +psutil_cpu_flags() { + size_t len1; + size_t len2; + char *buf1 = NULL; + char *buf2 = NULL; + char *buf3 = NULL; + int i; + PyObject *py_str = NULL; + + // There are standard and extended features (both strings). First, + // get the size for both. + if (sysctlbyname("machdep.cpu.features", NULL, &len1, NULL, 0) != 0) + Py_RETURN_NONE; + if (sysctlbyname("machdep.cpu.extfeatures", NULL, &len2, NULL, 0) != 0) + Py_RETURN_NONE; + + // Now we get the real values; we need 2 mallocs. + + // ...standard + buf1 = malloc(len1); + if (buf1 == NULL) { + PyErr_NoMemory(); + goto error; + } + if (sysctlbyname("machdep.cpu.features", buf1, &len1, NULL, 0) != 0) + goto error; + + // ...extended + buf2 = malloc(len2); + if (buf2 == NULL) { + PyErr_NoMemory(); + goto error; + } + if (sysctlbyname("machdep.cpu.extfeatures", buf2, &len2, NULL, 0) != 0) + goto error; + + // Lower case both strings (mimic Linux lscpu). + for (i = 0; buf1[i]; i++) + buf1[i] = tolower(buf1[i]); + for (i = 0; buf2[i]; i++) + buf2[i] = tolower(buf2[i]); + + // Make space for both in a new buffer and join them (+1 is for the + // null terminator). + buf3 = malloc(len1 + len2 + 1); + if (buf3 == NULL) { + PyErr_NoMemory(); + goto error; + } + sprintf(buf3, "%s %s", buf1, buf2); + + // Return. + py_str = Py_BuildValue("s", buf3); + free(buf1); + free(buf2); + free(buf3); + return py_str; + +error: + if (buf1 != NULL) + free(buf1); + if (buf2 != NULL) + free(buf2); + if (buf3 != NULL) + free(buf3); + if (PyErr_Occurred()) // malloc failed + return NULL; + Py_RETURN_NONE; +} + + +// also available as sysctlbyname("hw.l1icachesize") but it returns 1 +PyObject * +psutil_cpu_l1i_cache() { + int value; + size_t len = sizeof(value); + int mib[2] = { CTL_HW, HW_L1ICACHESIZE }; + + if (sysctl(mib, 2, &value, &len, NULL, 0) < 0) + Py_RETURN_NONE; + else + return Py_BuildValue("i", value); +} + + +// also available as sysctlbyname("hw.l1dcachesize") but it returns 1 +PyObject * +psutil_cpu_l1d_cache() { + int value; + size_t len = sizeof(value); + int mib[2] = { CTL_HW, HW_L1DCACHESIZE }; + + if (sysctl(mib, 2, &value, &len, NULL, 0) < 0) + Py_RETURN_NONE; + else + return Py_BuildValue("i", value); +} + + +// also available as sysctlbyname("hw.l2cachesize") but it returns 1 +PyObject * +psutil_cpu_l2_cache() { + int value; + size_t len = sizeof(value); + int mib[2] = { CTL_HW, HW_L2CACHESIZE }; + + if (sysctl(mib, 2, &value, &len, NULL, 0) < 0) + Py_RETURN_NONE; + else + return Py_BuildValue("i", value); +} + + +// also available as sysctlbyname("hw.l3cachesize") but it returns 1 +PyObject * +psutil_cpu_l3_cache() { + int value; + size_t len = sizeof(value); + int mib[2] = { CTL_HW, HW_L3CACHESIZE }; + + if (sysctl(mib, 2, &value, &len, NULL, 0) < 0) + Py_RETURN_NONE; + else + return Py_BuildValue("i", value); +} + + +/* +PyObject * +psutil_cpu_num_cores_per_socket() { + int value; + size_t size = sizeof(value); + + if (sysctlbyname("machdep.cpu.cores_per_package", + &value, &size, NULL, 0) != 0) { + Py_RETURN_NONE; + } + else + return Py_BuildValue("i", value); +} + + +// "threads_per_core" is how it's being called by lscpu on Linux. +// Here it's "thread_count". Hopefully it's the same thing. +PyObject * +psutil_cpu_threads_per_core() { + int value; + size_t size = sizeof(value); + + if (sysctlbyname("machdep.cpu.thread_count", + &value, &size, NULL, 0) != 0) { + Py_RETURN_NONE; + } + else + return Py_BuildValue("i", value); +} +*/ diff --git a/psutil/arch/osx/cpu.h b/psutil/arch/osx/cpu.h index aac0f809..d6c91557 100644 --- a/psutil/arch/osx/cpu.h +++ b/psutil/arch/osx/cpu.h @@ -6,8 +6,16 @@ #include <Python.h> -PyObject *psutil_cpu_count_logical(PyObject *self, PyObject *args); PyObject *psutil_cpu_count_cores(PyObject *self, PyObject *args); -PyObject *psutil_cpu_times(PyObject *self, PyObject *args); +PyObject *psutil_cpu_count_logical(PyObject *self, PyObject *args); +PyObject *psutil_cpu_count_sockets(PyObject *self, PyObject *args); +PyObject *psutil_cpu_flags(PyObject *self, PyObject *args); PyObject *psutil_cpu_freq(PyObject *self, PyObject *args); +PyObject *psutil_cpu_l1d_cache(PyObject *self, PyObject *args); +PyObject *psutil_cpu_l1i_cache(PyObject *self, PyObject *args); +PyObject *psutil_cpu_l2_cache(PyObject *self, PyObject *args); +PyObject *psutil_cpu_l3_cache(PyObject *self, PyObject *args); +PyObject *psutil_cpu_model(PyObject *self, PyObject *args); PyObject *psutil_cpu_stats(PyObject *self, PyObject *args); +PyObject *psutil_cpu_times(PyObject *self, PyObject *args); +PyObject *psutil_cpu_vendor(PyObject *self, PyObject *args); diff --git a/psutil/arch/windows/cpu.c b/psutil/arch/windows/cpu.c index 20c01a0d..a6ab68d2 100644 --- a/psutil/arch/windows/cpu.c +++ b/psutil/arch/windows/cpu.c @@ -7,6 +7,9 @@ #include <Python.h> #include <windows.h> #include <PowrProf.h> +#include <stdio.h> +#include <string.h> +#include <intrin.h> #include "../../_psutil_common.h" @@ -177,17 +180,29 @@ psutil_cpu_count_logical(PyObject *self, PyObject *args) { /* - * Return the number of CPU cores (non hyper-threading). + * Re-adapted from: + * https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ + * nf-sysinfoapi-getlogicalprocessorinformation?redirectedfrom=MSDN */ PyObject * -psutil_cpu_count_cores(PyObject *self, PyObject *args) { +psutil_GetLogicalProcessorInformationEx(PyObject *self, PyObject *args) { DWORD rc; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX buffer = NULL; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX ptr = NULL; DWORD length = 0; DWORD offset = 0; - DWORD ncpus = 0; - DWORD prev_processor_info_size = 0; + DWORD coresCount = 0; + DWORD socketsCount = 0; + DWORD numaNodesCount = 0; + DWORD L1CacheCount = 0; + DWORD L2CacheCount = 0; + DWORD L3CacheCount = 0; + DWORD prevProcInfoSize = 0; + PCACHE_RELATIONSHIP Cache; + PyObject *py_retdict = PyDict_New(); + + if (py_retdict == NULL) + return NULL; // GetLogicalProcessorInformationEx() is available from Windows 7 // onward. Differently from GetLogicalProcessorInformation() @@ -196,12 +211,11 @@ psutil_cpu_count_cores(PyObject *self, PyObject *args) { // https://bugs.python.org/issue33166 if (GetLogicalProcessorInformationEx == NULL) { psutil_debug("Win < 7; cpu_count_cores() forced to None"); - Py_RETURN_NONE; + return py_retdict; } while (1) { - rc = GetLogicalProcessorInformationEx( - RelationAll, buffer, &length); + rc = GetLogicalProcessorInformationEx(RelationAll, buffer, &length); if (rc == FALSE) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { if (buffer) { @@ -215,9 +229,9 @@ psutil_cpu_count_cores(PyObject *self, PyObject *args) { } } else { - psutil_debug("GetLogicalProcessorInformationEx() returned %u", - GetLastError()); - goto return_none; + PyErr_SetFromOSErrnoWithSyscall( + "GetLogicalProcessorInformationEx"); + return NULL; } } else { @@ -230,31 +244,61 @@ psutil_cpu_count_cores(PyObject *self, PyObject *args) { // Advance ptr by the size of the previous // SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX struct. ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*) \ - (((char*)ptr) + prev_processor_info_size); + (((char*)ptr) + prevProcInfoSize); if (ptr->Relationship == RelationProcessorCore) { - ncpus += 1; + coresCount += 1; + } + else if (ptr->Relationship == RelationProcessorCore) { + numaNodesCount += 1; + } + else if (ptr->Relationship == RelationProcessorPackage) { + socketsCount += 1; + } + else if (ptr->Relationship == RelationCache) { + // Cache data is in ptr->Cache, one CACHE_DESCRIPTOR structure for each cache. + Cache = &ptr->Cache; + if (Cache->Level == 1) + L1CacheCount = Cache->CacheSize; + else if (Cache->Level == 2) + L2CacheCount = Cache->CacheSize; + else if (Cache->Level == 3) + L3CacheCount = Cache->CacheSize; } // When offset == length, we've reached the last processor // info struct in the buffer. offset += ptr->Size; - prev_processor_info_size = ptr->Size; + prevProcInfoSize = ptr->Size; } free(buffer); - if (ncpus != 0) { - return Py_BuildValue("I", ncpus); + + if (psutil_add_to_dict(py_retdict, "cores", + Py_BuildValue("I", coresCount)) == 1) { + return NULL; } - else { - psutil_debug("GetLogicalProcessorInformationEx() count was 0"); - Py_RETURN_NONE; // mimick os.cpu_count() + if (psutil_add_to_dict(py_retdict, "sockets", + Py_BuildValue("I", socketsCount)) == 1) { + return NULL; } - -return_none: - if (buffer != NULL) - free(buffer); - Py_RETURN_NONE; + if (psutil_add_to_dict(py_retdict, "numa", + Py_BuildValue("I", numaNodesCount)) == 1) { + return NULL; + } + if (psutil_add_to_dict(py_retdict, "l1_cache", + Py_BuildValue("I", L1CacheCount)) == 1) { + return NULL; + } + if (psutil_add_to_dict(py_retdict, "l2_cache", + Py_BuildValue("I", L2CacheCount)) == 1) { + return NULL; + } + if (psutil_add_to_dict(py_retdict, "l3_cache", + Py_BuildValue("I", L3CacheCount)) == 1) { + return NULL; + } + return py_retdict; } @@ -412,3 +456,470 @@ error: LocalFree(pBuffer); return NULL; } + + +// CPU info for x86, x64 processors. +// Re-adapted from: https://docs.microsoft.com/en-us/previous-versions/ +// visualstudio/visual-studio-2008/hskdteyh(v=vs.90)?redirectedfrom=MSDN +// List of CPU flags: +// https://project.altservice.com/documents/14 + +typedef enum { + false = 0, + true = 1, +} bool; + +static char* szFeatures[] = { + "fpu", // "x87 FPU On Chip", + "vme", // "Virtual-8086 Mode Enhancement", + "de", // "Debugging Extensions", + "psa", // "Page Size Extensions", + "tsc", // "Time Stamp Counter", + "msr", // "RDMSR and WRMSR Support", + "pae", // "Physical Address Extensions", + "mce", // "Machine Check Exception", + "cx8", // "CMPXCHG8B Instruction", + "apic", // "APIC On Chip", + "unknown1", // "Unknown1", + "sep", // "SYSENTER and SYSEXIT", + "mtrr", // "Memory Type Range Registers", + "pge", // "PTE Global Bit", + "mca", // "Machine Check Architecture", + "cmov", // "Conditional Move/Compare Instruction", + "pat", // "Page Attribute Table", + "pse36", // "36-bit Page Size Extension", + "pn", // "Processor Serial Number", + "clflush", // "CFLUSH Extension", + "unknown2", // "Unknown2", + "dts", // "Debug Store", + "tmclockctrl", // "Thermal Monitor and Clock Ctrl", ??? + "mmx", // "MMX Technology", + "fxsr", // "FXSAVE/FXRSTOR", + "sse", // "SSE Extensions", + "sse2", // "SSE2 Extensions", + "ss", // "Self Snoop", + "mthread", // "Multithreading Technology", + "tm", // "Thermal Monitor", + "unknown4", // "Unknown4", + "pbe", // "Pending Break Enable" +}; + + +static void +stradd(char *base, int len, char *tail) { + if (strlen(base) != 0) + strcat_s(base, len, " "); + strcat_s(base, len, tail); +} + + +PyObject * +psutil_cpu_info(PyObject *self, PyObject *args) { + char CPUString[0x20]; + char CPUBrandString[0x40]; + int CPUInfo[4] = {-1}; + int nSteppingID = 0; + int nModel = 0; + int nFamily = 0; + int nProcessorType = 0; + int nExtendedmodel = 0; + int nExtendedfamily = 0; + int nBrandIndex = 0; + int nCLFLUSHcachelinesize = 0; + int nLogicalProcessors = 0; + int nAPICPhysicalID = 0; + int nFeatureInfo = 0; + int nCacheLineSize = 0; + int nL2Associativity = 0; + int nCacheSizeK = 0; + int nPhysicalAddress = 0; + int nVirtualAddress = 0; + int nRet = 0; + + int nCores = 0; + int nCacheType = 0; + int nCacheLevel = 0; + int nMaxThread = 0; + int nSysLineSize = 0; + int nPhysicalLinePartitions = 0; + int nWaysAssociativity = 0; + int nNumberSets = 0; + + unsigned nIds, nExIds, i; + + bool bSSE3Instructions = false; + bool bMONITOR_MWAIT = false; + bool bCPLQualifiedDebugStore = false; + bool bVirtualMachineExtensions = false; + bool bEnhancedIntelSpeedStepTechnology = false; + bool bThermalMonitor2 = false; + bool bSupplementalSSE3 = false; + bool bL1ContextID = false; + bool bCMPXCHG16B = false; + bool bxTPRUpdateControl = false; + bool bPerfDebugCapabilityMSR = false; + bool bSSE41Extensions = false; + bool bSSE42Extensions = false; + bool bPOPCNT = false; + + bool bMultithreading = false; + + bool bLAHF_SAHFAvailable = false; + bool bCmpLegacy = false; + bool bSVM = false; + bool bExtApicSpace = false; + bool bAltMovCr8 = false; + bool bLZCNT = false; + bool bSSE4A = false; + bool bMisalignedSSE = false; + bool bPREFETCH = false; + bool bSKINITandDEV = false; + bool bSYSCALL_SYSRETAvailable = false; + bool bExecuteDisableBitAvailable = false; + bool bMMXExtensions = false; + bool bFFXSR = false; + bool b1GBSupport = false; + bool bRDTSCP = false; + bool b64Available = false; + bool b3DNowExt = false; + bool b3DNow = false; + bool bNestedPaging = false; + bool bLBRVisualization = false; + bool bFP128 = false; + bool bMOVOptimization = false; + + bool bSelfInit = false; + bool bFullyAssociative = false; + char flags[900] = ""; + + PyObject *py_retdict = PyDict_New(); + + if (py_retdict == NULL) + return NULL; + + // __cpuid with an InfoType argument of 0 returns the number of + // valid Ids in CPUInfo[0] and the CPU identification string in + // the other three array elements. The CPU identification string is + // not in linear order. The code below arranges the information + // in a human readable form. + __cpuid(CPUInfo, 0); + nIds = CPUInfo[0]; + memset(CPUString, 0, sizeof(CPUString)); + *((int*)CPUString) = CPUInfo[1]; + *((int*)(CPUString+4)) = CPUInfo[3]; + *((int*)(CPUString+8)) = CPUInfo[2]; + + // Get the information associated with each valid Id + for (i=0; i <= nIds; ++i) { + __cpuid(CPUInfo, i); + + // Interpret CPU feature information. + if (i == 1) { + nSteppingID = CPUInfo[0] & 0xf; + nModel = (CPUInfo[0] >> 4) & 0xf; + nFamily = (CPUInfo[0] >> 8) & 0xf; + nProcessorType = (CPUInfo[0] >> 12) & 0x3; + nExtendedmodel = (CPUInfo[0] >> 16) & 0xf; + nExtendedfamily = (CPUInfo[0] >> 20) & 0xff; + nBrandIndex = CPUInfo[1] & 0xff; + nCLFLUSHcachelinesize = ((CPUInfo[1] >> 8) & 0xff) * 8; + nLogicalProcessors = ((CPUInfo[1] >> 16) & 0xff); + nAPICPhysicalID = (CPUInfo[1] >> 24) & 0xff; + bSSE3Instructions = (CPUInfo[2] & 0x1) || false; + bMONITOR_MWAIT = (CPUInfo[2] & 0x8) || false; + bCPLQualifiedDebugStore = (CPUInfo[2] & 0x10) || false; + bVirtualMachineExtensions = (CPUInfo[2] & 0x20) || false; + bEnhancedIntelSpeedStepTechnology = (CPUInfo[2] & 0x80) || false; + bThermalMonitor2 = (CPUInfo[2] & 0x100) || false; + bSupplementalSSE3 = (CPUInfo[2] & 0x200) || false; + bL1ContextID = (CPUInfo[2] & 0x300) || false; + bCMPXCHG16B= (CPUInfo[2] & 0x2000) || false; + bxTPRUpdateControl = (CPUInfo[2] & 0x4000) || false; + bPerfDebugCapabilityMSR = (CPUInfo[2] & 0x8000) || false; + bSSE41Extensions = (CPUInfo[2] & 0x80000) || false; + bSSE42Extensions = (CPUInfo[2] & 0x100000) || false; + bPOPCNT= (CPUInfo[2] & 0x800000) || false; + nFeatureInfo = CPUInfo[3]; + bMultithreading = (nFeatureInfo & (1 << 28)) || false; + } + } + + // Calling __cpuid with 0x80000000 as the InfoType argument + // gets the number of valid extended IDs. + __cpuid(CPUInfo, 0x80000000); + nExIds = CPUInfo[0]; + memset(CPUBrandString, 0, sizeof(CPUBrandString)); + + // Get the information associated with each extended ID. + for (i=0x80000000; i<=nExIds; ++i) { + __cpuid(CPUInfo, i); + if (i == 0x80000001) { + bLAHF_SAHFAvailable = (CPUInfo[2] & 0x1) || false; + bCmpLegacy = (CPUInfo[2] & 0x2) || false; + bSVM = (CPUInfo[2] & 0x4) || false; + bExtApicSpace = (CPUInfo[2] & 0x8) || false; + bAltMovCr8 = (CPUInfo[2] & 0x10) || false; + bLZCNT = (CPUInfo[2] & 0x20) || false; + bSSE4A = (CPUInfo[2] & 0x40) || false; + bMisalignedSSE = (CPUInfo[2] & 0x80) || false; + bPREFETCH = (CPUInfo[2] & 0x100) || false; + bSKINITandDEV = (CPUInfo[2] & 0x1000) || false; + bSYSCALL_SYSRETAvailable = (CPUInfo[3] & 0x800) || false; + bExecuteDisableBitAvailable = (CPUInfo[3] & 0x10000) || false; + bMMXExtensions = (CPUInfo[3] & 0x40000) || false; + bFFXSR = (CPUInfo[3] & 0x200000) || false; + b1GBSupport = (CPUInfo[3] & 0x400000) || false; + bRDTSCP = (CPUInfo[3] & 0x8000000) || false; + b64Available = (CPUInfo[3] & 0x20000000) || false; + b3DNowExt = (CPUInfo[3] & 0x40000000) || false; + b3DNow = (CPUInfo[3] & 0x80000000) || false; + } + + // Interpret CPU brand string and cache information. + if (i == 0x80000002) + memcpy(CPUBrandString, CPUInfo, sizeof(CPUInfo)); + else if (i == 0x80000003) + memcpy(CPUBrandString + 16, CPUInfo, sizeof(CPUInfo)); + else if (i == 0x80000004) + memcpy(CPUBrandString + 32, CPUInfo, sizeof(CPUInfo)); + else if (i == 0x80000006) { + nCacheLineSize = CPUInfo[2] & 0xff; + nL2Associativity = (CPUInfo[2] >> 12) & 0xf; + nCacheSizeK = (CPUInfo[2] >> 16) & 0xffff; + } + else if (i == 0x80000008) { + nPhysicalAddress = CPUInfo[0] & 0xff; + nVirtualAddress = (CPUInfo[0] >> 8) & 0xff; + } + else if (i == 0x8000000A) { + bNestedPaging = (CPUInfo[3] & 0x1) || false; + bLBRVisualization = (CPUInfo[3] & 0x2) || false; + } + else if (i == 0x8000001A) { + bFP128 = (CPUInfo[0] & 0x1) || false; + bMOVOptimization = (CPUInfo[0] & 0x2) || false; + } + } + + if (psutil_add_to_dict(py_retdict, "vendor", + Py_BuildValue("s", CPUString)) == 1) + goto error; + + if (nIds >= 1) { + /* + if (nSteppingID) + printf_s("Stepping ID = %d\n", nSteppingID); + if (nModel) + printf_s("Model = %d\n", nModel); + if (nFamily) + printf_s("Family = %d\n", nFamily); + if (nProcessorType) + printf_s("Processor Type = %d\n", nProcessorType); + if (nExtendedmodel) + printf_s("Extended model = %d\n", nExtendedmodel); + if (nExtendedfamily) + printf_s("Extended family = %d\n", nExtendedfamily); + if (nBrandIndex) + printf_s("Brand Index = %d\n", nBrandIndex); + if (nCLFLUSHcachelinesize) + printf_s("CLFLUSH cache line size = %d\n", + nCLFLUSHcachelinesize); + if (bMultithreading && (nLogicalProcessors > 0)) + printf_s("Logical Processor Count = %d\n", nLogicalProcessors); + if (nAPICPhysicalID) + printf_s("APIC Physical ID = %d\n", nAPICPhysicalID); + */ + + if (nFeatureInfo || bSSE3Instructions || + bMONITOR_MWAIT || bCPLQualifiedDebugStore || + bVirtualMachineExtensions || bEnhancedIntelSpeedStepTechnology || + bThermalMonitor2 || bSupplementalSSE3 || bL1ContextID || + bCMPXCHG16B || bxTPRUpdateControl || bPerfDebugCapabilityMSR || + bSSE41Extensions || bSSE42Extensions || bPOPCNT || + bLAHF_SAHFAvailable || bCmpLegacy || bSVM || + bExtApicSpace || bAltMovCr8 || + bLZCNT || bSSE4A || bMisalignedSSE || + bPREFETCH || bSKINITandDEV || bSYSCALL_SYSRETAvailable || + bExecuteDisableBitAvailable || bMMXExtensions || bFFXSR || + b1GBSupport || bRDTSCP || b64Available || b3DNowExt || b3DNow || + bNestedPaging || bLBRVisualization || bFP128 || bMOVOptimization) + { + + if (bSSE3Instructions) // SSE3 + stradd(flags, _countof(flags), "sse3"); + if (bMONITOR_MWAIT) // MONITOR/MWAIT + stradd(flags, _countof(flags), "monitor"); + if (bCPLQualifiedDebugStore) // CPL Qualified Debug Store + stradd(flags, _countof(flags), "ds_cpl"); + if (bVirtualMachineExtensions) // Virtual Machine Extensions??? + stradd(flags, _countof(flags), "vmext"); + if (bEnhancedIntelSpeedStepTechnology) // Enhanced Intel SpeedStep Technology + stradd(flags, _countof(flags), "est"); + if (bThermalMonitor2) // Thermal Monitor 2 + stradd(flags, _countof(flags), "tm2"); + if (bSupplementalSSE3) // Supplemental Streaming SIMD Extensions 3? + stradd(flags, _countof(flags), "supplsse3"); + if (bL1ContextID) // L1 Context ID ??? + stradd(flags, _countof(flags), "l1ctxid"); + if (bCMPXCHG16B) // CMPXCHG16B Instruction + stradd(flags, _countof(flags), "cx16"); + if (bxTPRUpdateControl) // xTPR Update Control + stradd(flags, _countof(flags), "xtpr"); + if (bPerfDebugCapabilityMSR) // Perf\\Debug Capability MSR + stradd(flags, _countof(flags), "perfdebugmsr"); + if (bSSE41Extensions) // SSE4.1 Extensions + stradd(flags, _countof(flags), "sse4_1"); + if (bSSE42Extensions) // SSE4.2 Extensions + stradd(flags, _countof(flags), "sse4_2"); + if (bPOPCNT) // PPOPCNT Instruction + stradd(flags, _countof(flags), "popcnt"); + + i = 0; + nIds = 1; + while (i < (sizeof(szFeatures) / sizeof(const char*))) { + if (nFeatureInfo & nIds) { + stradd(flags, _countof(flags), szFeatures[i]); + } + nIds <<= 1; + ++i; + } + + if (bLAHF_SAHFAvailable) // LAHF/SAHF in 64-bit mode + stradd(flags, _countof(flags), "lhaf_lm"); + if (bCmpLegacy) // Core multi-processing legacy mode + stradd(flags, _countof(flags), "cmplegacy"); + if (bSVM) // Secure Virtual Machine + stradd(flags, _countof(flags), "svm"); + if (bExtApicSpace) // Extended APIC Register Space + stradd(flags, _countof(flags), "x2apic"); + if (bAltMovCr8) // AltMovCr8 ??? + stradd(flags, _countof(flags), "altmovcr8"); + if (bLZCNT) // LZCNT instruction + stradd(flags, _countof(flags), "lzcnt"); + if (bSSE4A) // SSE4A + stradd(flags, _countof(flags), "sse4a)"); + if (bMisalignedSSE) // Misaligned SSE mode + stradd(flags, _countof(flags), "misalignsse"); + if (bPREFETCH) // PREFETCH and PREFETCHW Instructions + stradd(flags, _countof(flags), "3dnowprefetch"); + if (bSKINITandDEV) // SKINIT and DEV support + stradd(flags, _countof(flags), "skinit"); + if (bSYSCALL_SYSRETAvailable) // SYSCALL/SYSRET in 64-bit mode + stradd(flags, _countof(flags), "syscall"); + if (bExecuteDisableBitAvailable) // Execute Disable Bit + stradd(flags, _countof(flags), "nx"); + if (bMMXExtensions) // Extensions to MMX Instructions + stradd(flags, _countof(flags), "mmxext"); + if (bFFXSR) // FFXSR + stradd(flags, _countof(flags), "ffxsr"); + if (b1GBSupport) // 1GB page support + stradd(flags, _countof(flags), "pdpe1gb"); + if (bRDTSCP) // RDTSCP instruction + stradd(flags, _countof(flags), "rdtscp"); + if (b64Available) // 64 bit Technology + stradd(flags, _countof(flags), "lm"); + if (b3DNowExt) // 3Dnow Ext + stradd(flags, _countof(flags), "3dnowext"); + if (b3DNow) // 3Dnow! instructions + stradd(flags, _countof(flags), "3dnow"); + if (bNestedPaging) // Nested Paging + stradd(flags, _countof(flags), "npt"); + if (bLBRVisualization) // LBR Visualization + stradd(flags, _countof(flags), "lbrv"); + if (bFP128) // FP128 optimization??? + stradd(flags, _countof(flags), "fp128"); + if (bMOVOptimization) // MOVU Optimization??? + stradd(flags, _countof(flags), "movu"); + } + } + + if (nExIds >= 0x80000004) { + if (psutil_add_to_dict(py_retdict, "model", + Py_BuildValue("s", CPUBrandString)) == 1) + goto error; + + } + /* + if (nExIds >= 0x80000006) { + printf_s("Cache Line Size = %d\n", nCacheLineSize); + printf_s("L2 Associativity = %d\n", nL2Associativity); + printf_s("Cache Size = %dK\n", nCacheSizeK); + } + + while (1) { + __cpuidex(CPUInfo, 0x4, i); + if (!(CPUInfo[0] & 0xf0)) + break; + + if (i == 0) { + nCores = CPUInfo[0] >> 26; + // printf_s("\n\nNumber of Cores = %d\n", nCores + 1); + } + + nCacheType = (CPUInfo[0] & 0x1f); + nCacheLevel = (CPUInfo[0] & 0xe0) >> 5; + bSelfInit = (CPUInfo[0] & 0x100) >> 8; + bFullyAssociative = (CPUInfo[0] & 0x200) >> 9; + nMaxThread = (CPUInfo[0] & 0x03ffc000) >> 14; + nSysLineSize = (CPUInfo[1] & 0x0fff); + nPhysicalLinePartitions = (CPUInfo[1] & 0x03ff000) >> 12; + nWaysAssociativity = (CPUInfo[1]) >> 22; + nNumberSets = CPUInfo[2]; + + printf_s("\n"); + + printf_s("ECX Index %d\n", i); + switch (nCacheType) { + case 0: + printf_s(" Type: Null\n"); + break; + case 1: + printf_s(" Type: Data Cache\n"); + break; + case 2: + printf_s(" Type: Instruction Cache\n"); + break; + case 3: + printf_s(" Type: Unified Cache\n"); + break; + default: + printf_s(" Type: Unknown\n"); + } + + printf_s(" Level = %d\n", nCacheLevel + 1); + if (bSelfInit) { + printf_s(" Self Initializing\n"); + } + else { + printf_s(" Not Self Initializing\n"); + } + if (bFullyAssociative) { + printf_s(" Is Fully Associatve\n"); + } + else { + printf_s(" Is Not Fully Associatve\n"); + } + printf_s(" Max Threads = %d\n", + nMaxThread+1); + printf_s(" System Line Size = %d\n", + nSysLineSize+1); + printf_s(" Physical Line Partions = %d\n", + nPhysicalLinePartitions+1); + printf_s(" Ways of Associativity = %d\n", + nWaysAssociativity+1); + printf_s(" Number of Sets = %d\n", + nNumberSets+1); + i = i + 1; + } + */ + + if (psutil_add_to_dict(py_retdict, "flags", + Py_BuildValue("s", flags)) == 1) + goto error; + + return py_retdict; + +error: + Py_DECREF(py_retdict); + return NULL; +} diff --git a/psutil/arch/windows/cpu.h b/psutil/arch/windows/cpu.h index 1ef3ff1f..029f057b 100644 --- a/psutil/arch/windows/cpu.h +++ b/psutil/arch/windows/cpu.h @@ -7,8 +7,10 @@ #include <Python.h> PyObject *psutil_cpu_count_logical(PyObject *self, PyObject *args); -PyObject *psutil_cpu_count_cores(PyObject *self, PyObject *args); +PyObject *psutil_GetLogicalProcessorInformationEx( + PyObject *self, PyObject *args); PyObject *psutil_cpu_freq(PyObject *self, PyObject *args); PyObject *psutil_cpu_stats(PyObject *self, PyObject *args); PyObject *psutil_cpu_times(PyObject *self, PyObject *args); PyObject *psutil_per_cpu_times(PyObject *self, PyObject *args); +PyObject *psutil_cpu_info(PyObject *self, PyObject *args); diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index 21bb3e61..5d23bc74 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -86,7 +86,7 @@ __all__ = [ 'CI_TESTING', 'VALID_PROC_STATUSES', 'TOLERANCE_DISK_USAGE', 'IS_64BIT', "HAS_CPU_AFFINITY", "HAS_CPU_FREQ", "HAS_ENVIRON", "HAS_PROC_IO_COUNTERS", "HAS_IONICE", "HAS_MEMORY_MAPS", "HAS_PROC_CPU_NUM", "HAS_RLIMIT", - "HAS_SENSORS_BATTERY", "HAS_BATTERY", "HAS_SENSORS_FANS", + "HAS_SENSORS_BATTERY", "HAS_BATTERY", "HAS_SENSORS_FANS", "HAS_CPU_FREQ", "HAS_SENSORS_TEMPERATURES", "HAS_MEMORY_FULL_INFO", # subprocesses 'pyrun', 'terminate', 'reap_children', 'spawn_testproc', 'spawn_zombie', @@ -176,6 +176,7 @@ HERE = os.path.realpath(os.path.dirname(__file__)) HAS_CONNECTIONS_UNIX = POSIX and not SUNOS HAS_CPU_AFFINITY = hasattr(psutil.Process, "cpu_affinity") HAS_CPU_FREQ = hasattr(psutil, "cpu_freq") +HAS_CPU_INFO = hasattr(psutil, "cpu_info") HAS_GETLOADAVG = hasattr(psutil, "getloadavg") HAS_ENVIRON = hasattr(psutil.Process, "environ") HAS_IONICE = hasattr(psutil.Process, "ionice") diff --git a/psutil/tests/test_memleaks.py b/psutil/tests/test_memleaks.py index d5baffa5..40f3d7ef 100755 --- a/psutil/tests/test_memleaks.py +++ b/psutil/tests/test_memleaks.py @@ -32,6 +32,7 @@ from psutil._compat import ProcessLookupError from psutil._compat import super from psutil.tests import HAS_CPU_AFFINITY from psutil.tests import HAS_CPU_FREQ +from psutil.tests import HAS_CPU_INFO from psutil.tests import HAS_ENVIRON from psutil.tests import HAS_IONICE from psutil.tests import HAS_MEMORY_MAPS @@ -368,6 +369,10 @@ class TestModuleFunctionsLeaks(TestMemoryLeak): def test_cpu_freq(self): self.execute(psutil.cpu_freq) + @unittest.skipIf(not HAS_CPU_INFO, "not supported") + def test_cpu_info(self): + self.execute(psutil.cpu_info) + @unittest.skipIf(not WINDOWS, "WINDOWS only") def test_getloadavg(self): psutil.getloadavg() diff --git a/psutil/tests/test_osx.py b/psutil/tests/test_osx.py index 4f4b1c29..b5e9300c 100755 --- a/psutil/tests/test_osx.py +++ b/psutil/tests/test_osx.py @@ -137,7 +137,7 @@ class TestSystemAPIs(PsutilTestCase): # --- cpu def test_cpu_count_logical(self): - num = sysctl("sysctl hw.logicalcpu") + num = sysctl("hw.logicalcpu") self.assertEqual(num, psutil.cpu_count(logical=True)) def test_cpu_count_cores(self): @@ -147,11 +147,11 @@ class TestSystemAPIs(PsutilTestCase): def test_cpu_freq(self): freq = psutil.cpu_freq() self.assertEqual( - freq.current * 1000 * 1000, sysctl("sysctl hw.cpufrequency")) + freq.current * 1000 * 1000, sysctl("hw.cpufrequency")) self.assertEqual( - freq.min * 1000 * 1000, sysctl("sysctl hw.cpufrequency_min")) + freq.min * 1000 * 1000, sysctl("hw.cpufrequency_min")) self.assertEqual( - freq.max * 1000 * 1000, sysctl("sysctl hw.cpufrequency_max")) + freq.max * 1000 * 1000, sysctl("hw.cpufrequency_max")) # --- virtual mem @@ -233,6 +233,53 @@ class TestSystemAPIs(PsutilTestCase): self.assertEqual(psutil_result.percent, int(percent)) +@unittest.skipIf(not MACOS, "MACOS only") +class TestCpuInfo(PsutilTestCase): + + def test_model(self): + value = psutil.cpu_info()['model'] + self.assertEqual(value, sysctl("machdep.cpu.brand_string")) + + def test_vendor(self): + value = psutil.cpu_info()['vendor'] + self.assertEqual(value, sysctl("machdep.cpu.vendor")) + + def test_features(self): + value = psutil.cpu_info()['features'] + sctl = "%s %s" % ( + sysctl("machdep.cpu.features").lower(), + sysctl("machdep.cpu.extfeatures").lower()) + self.assertEqual(value, sctl) + + def test_cores_per_socket(self): + value = psutil.cpu_info()['cores_per_socket'] + self.assertEqual(value, sysctl("machdep.cpu.cores_per_package")) + + def test_threads_per_core(self): + value = psutil.cpu_info()['threads_per_core'] + self.assertEqual(value, sysctl("machdep.cpu.thread_count")) + + def test_sockets(self): + value = psutil.cpu_info()['sockets'] + self.assertEqual(value, sysctl("hw.packages")) + + def test_l1i_cache(self): + value = psutil.cpu_info()['l1i_cache'] + self.assertEqual(value, sysctl("hw.l1icachesize")) + + def test_l1d_cache(self): + value = psutil.cpu_info()['l1d_cache'] + self.assertEqual(value, sysctl("hw.l1dcachesize")) + + def test_l2_cache(self): + value = psutil.cpu_info()['l2_cache'] + self.assertEqual(value, sysctl("hw.l2cachesize")) + + def test_l3_cache(self): + value = psutil.cpu_info()['l3_cache'] + self.assertEqual(value, sysctl("hw.l3cachesize")) + + if __name__ == '__main__': from psutil.tests.runner import run_from_name run_from_name(__file__) diff --git a/psutil/tests/test_system.py b/psutil/tests/test_system.py index ed328d01..4478fe75 100755 --- a/psutil/tests/test_system.py +++ b/psutil/tests/test_system.py @@ -10,6 +10,7 @@ import contextlib import datetime import errno import os +import platform import pprint import shutil import signal @@ -37,6 +38,7 @@ from psutil.tests import GITHUB_ACTIONS from psutil.tests import GLOBAL_TIMEOUT from psutil.tests import HAS_BATTERY from psutil.tests import HAS_CPU_FREQ +from psutil.tests import HAS_CPU_INFO from psutil.tests import HAS_GETLOADAVG from psutil.tests import HAS_NET_IO_COUNTERS from psutil.tests import HAS_SENSORS_BATTERY @@ -532,6 +534,13 @@ class TestCpuAPIs(PsutilTestCase): if LINUX: self.assertEqual(len(ls), psutil.cpu_count()) + @unittest.skipIf(not HAS_CPU_INFO, "not suported") + def test_cpu_info(self): + info = psutil.cpu_info() + with self.subTest(info): + self.assertEqual(info['arch'], platform.machine()) + self.assertEqual(info['byteorder'], sys.byteorder) + @unittest.skipIf(not HAS_GETLOADAVG, "not supported") def test_getloadavg(self): loadavg = psutil.getloadavg() diff --git a/psutil/tests/test_windows.py b/psutil/tests/test_windows.py index 55b6bc7b..b39f5d31 100755 --- a/psutil/tests/test_windows.py +++ b/psutil/tests/test_windows.py @@ -104,6 +104,16 @@ class TestCpuAPIs(WindowsTestCase): self.assertEqual(proc.CurrentClockSpeed, psutil.cpu_freq().current) self.assertEqual(proc.MaxClockSpeed, psutil.cpu_freq().max) + def test_cpu_info_model(self): + w = wmi.WMI() + proc = w.Win32_Processor()[0] + self.assertEqual(psutil.cpu_info()['model'], proc.Name) + + def test_cpu_info_vendor(self): + w = wmi.WMI() + proc = w.Win32_Processor()[0] + self.assertEqual(psutil.cpu_info()['vendor'], proc.Manufacturer) + class TestSystemAPIs(WindowsTestCase): @@ -192,8 +192,8 @@ elif MACOS: 'psutil._psutil_osx', sources=sources + [ 'psutil/_psutil_osx.c', - 'psutil/arch/osx/process_info.c', 'psutil/arch/osx/cpu.c', + 'psutil/arch/osx/process_info.c', ], define_macros=macros, extra_link_args=[ |