From 0dde184075f81a9b6e22caa7255396d21ae63769 Mon Sep 17 00:00:00 2001 From: Daniel Widdis Date: Thu, 13 Apr 2023 01:11:27 -0700 Subject: [PATCH] Get Windows percent swap usage from performance counters (#2160) Signed-off-by: Daniel Widdis --- CREDITS | 2 +- HISTORY.rst | 2 ++ psutil/_psutil_windows.c | 1 + psutil/_pswindows.py | 16 +++++++---- psutil/arch/windows/mem.c | 51 ++++++++++++++++++++++++++++++++++++ psutil/arch/windows/mem.h | 1 + psutil/tests/test_windows.py | 21 +++++++++++++++ 7 files changed, 88 insertions(+), 6 deletions(-) diff --git a/CREDITS b/CREDITS index f414636087..c845a8c661 100644 --- a/CREDITS +++ b/CREDITS @@ -804,7 +804,7 @@ I: 2150 N: Daniel Widdis W: https://github.com/dbwiddis -I: 2077 +I: 2077, 2160 N: Amir Rossert W: https://github.com/arossert diff --git a/HISTORY.rst b/HISTORY.rst index e82605d8ac..8d22111a73 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -47,6 +47,8 @@ ``SPEED_UNKNOWN`` definition. (patch by Amir Rossert) - 2010_, [macOS]: on MacOS, arm64 ``IFM_1000_TX`` and ``IFM_1000_T`` are the same value, causing a build failure. (patch by Lawrence D'Anna) +- 2160_, [Windows]: Get Windows percent swap usage from performance counters. + (patch by Daniel Widdis) 5.9.3 ===== diff --git a/psutil/_psutil_windows.c b/psutil/_psutil_windows.c index 2ed937ee95..11176de738 100644 --- a/psutil/_psutil_windows.c +++ b/psutil/_psutil_windows.c @@ -1560,6 +1560,7 @@ PsutilMethods[] = { {"disk_usage", psutil_disk_usage, METH_VARARGS}, {"getloadavg", (PyCFunction)psutil_get_loadavg, METH_VARARGS}, {"getpagesize", psutil_getpagesize, METH_VARARGS}, + {"swap_percent", psutil_swap_percent, METH_VARARGS}, {"init_loadavg_counter", (PyCFunction)psutil_init_loadavg_counter, METH_VARARGS}, {"net_connections", psutil_net_connections, METH_VARARGS}, {"net_if_addrs", psutil_net_if_addrs, METH_VARARGS}, diff --git a/psutil/_pswindows.py b/psutil/_pswindows.py index 3802f3edb0..4081aa17fb 100644 --- a/psutil/_pswindows.py +++ b/psutil/_pswindows.py @@ -244,16 +244,22 @@ def swap_memory(): mem = cext.virtual_mem() total_phys = mem[0] - free_phys = mem[1] total_system = mem[2] - free_system = mem[3] # system memory (commit total/limit) is the sum of physical and swap # thus physical memory values need to be substracted to get swap values total = total_system - total_phys - free = min(total, free_system - free_phys) - used = total - free - percent = usage_percent(used, total, round_=1) + # commit total is incremented immediately (decrementing free_system) + # while the corresponding free physical value is not decremented until + # pages are accessed, so we can't use free system memory for swap. + # instead, we calculate page file usage based on performance counter + if (total > 0): + percentswap = cext.swap_percent() + used = int(0.01 * percentswap * total) + else: + used = 0 + free = total - used + percent = round(percentswap, 1) return _common.sswap(total, used, free, percent, 0, 0) diff --git a/psutil/arch/windows/mem.c b/psutil/arch/windows/mem.c index 18b535e6af..24dc15ad0e 100644 --- a/psutil/arch/windows/mem.c +++ b/psutil/arch/windows/mem.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "../../_psutil_common.h" @@ -41,3 +42,53 @@ psutil_virtual_mem(PyObject *self, PyObject *args) { totalSys, availSys); } + + +// Return a float representing the percent usage of all paging files on +// the system. +PyObject * +psutil_swap_percent(PyObject *self, PyObject *args) { + WCHAR *szCounterPath = L"\\Paging File(_Total)\\% Usage"; + PDH_STATUS s; + HQUERY hQuery; + HCOUNTER hCounter; + PDH_FMT_COUNTERVALUE counterValue; + double percentUsage; + + if ((PdhOpenQueryW(NULL, 0, &hQuery)) != ERROR_SUCCESS) { + PyErr_Format(PyExc_RuntimeError, "PdhOpenQueryW failed"); + return NULL; + } + + s = PdhAddEnglishCounterW(hQuery, szCounterPath, 0, &hCounter); + if (s != ERROR_SUCCESS) { + PdhCloseQuery(hQuery); + PyErr_Format( + PyExc_RuntimeError, + "PdhAddEnglishCounterW failed. Performance counters may be disabled." + ); + return NULL; + } + + s = PdhCollectQueryData(hQuery); + if (s != ERROR_SUCCESS) { + // If swap disabled this will fail. + psutil_debug("PdhCollectQueryData failed; assume swap percent is 0"); + percentUsage = 0; + } + else { + s = PdhGetFormattedCounterValue( + (PDH_HCOUNTER)hCounter, PDH_FMT_DOUBLE, 0, &counterValue); + if (s != ERROR_SUCCESS) { + PdhCloseQuery(hQuery); + PyErr_Format( + PyExc_RuntimeError, "PdhGetFormattedCounterValue failed"); + return NULL; + } + percentUsage = counterValue.doubleValue; + } + + PdhRemoveCounter(hCounter); + PdhCloseQuery(hQuery); + return Py_BuildValue("d", percentUsage); +} diff --git a/psutil/arch/windows/mem.h b/psutil/arch/windows/mem.h index a10781dfce..48d3dadee6 100644 --- a/psutil/arch/windows/mem.h +++ b/psutil/arch/windows/mem.h @@ -8,3 +8,4 @@ PyObject *psutil_getpagesize(PyObject *self, PyObject *args); PyObject *psutil_virtual_mem(PyObject *self, PyObject *args); +PyObject *psutil_swap_percent(PyObject *self, PyObject *args); diff --git a/psutil/tests/test_windows.py b/psutil/tests/test_windows.py index 9b163a185f..a9f6893364 100755 --- a/psutil/tests/test_windows.py +++ b/psutil/tests/test_windows.py @@ -162,6 +162,27 @@ def test_free_phymem(self): int(w.AvailableBytes), psutil.virtual_memory().free, delta=TOLERANCE_SYS_MEM) + def test_total_swapmem(self): + w = wmi.WMI().Win32_PerfRawData_PerfOS_Memory()[0] + self.assertEqual(int(w.CommitLimit) - psutil.virtual_memory().total, + psutil.swap_memory().total) + if (psutil.swap_memory().total == 0): + self.assertEqual(0, psutil.swap_memory().free) + self.assertEqual(0, psutil.swap_memory().used) + + def test_percent_swapmem(self): + if (psutil.swap_memory().total > 0): + w = wmi.WMI().Win32_PerfRawData_PerfOS_PagingFile( + Name="_Total")[0] + # calculate swap usage to percent + percentSwap = int(w.PercentUsage) * 100 / int(w.PercentUsage_Base) + # exact percent may change but should be reasonable + # assert within +/- 5% and between 0 and 100% + self.assertGreaterEqual(psutil.swap_memory().percent, 0) + self.assertAlmostEqual(psutil.swap_memory().percent, percentSwap, + delta=5) + self.assertLessEqual(psutil.swap_memory().percent, 100) + # @unittest.skipIf(wmi is None, "wmi module is not installed") # def test__UPTIME(self): # # _UPTIME constant is not public but it is used internally