Skip to content

Commit

Permalink
Get Windows percent swap usage from performance counters (#2160)
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Widdis <[email protected]>
  • Loading branch information
dbwiddis committed Apr 13, 2023
1 parent 67d988a commit 0dde184
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 6 deletions.
2 changes: 1 addition & 1 deletion CREDITS
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
=====
Expand Down
1 change: 1 addition & 0 deletions psutil/_psutil_windows.c
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
16 changes: 11 additions & 5 deletions psutil/_pswindows.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down
51 changes: 51 additions & 0 deletions psutil/arch/windows/mem.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <Python.h>
#include <windows.h>
#include <Psapi.h>
#include <pdh.h>

#include "../../_psutil_common.h"

Expand Down Expand Up @@ -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);
}
1 change: 1 addition & 0 deletions psutil/arch/windows/mem.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
21 changes: 21 additions & 0 deletions psutil/tests/test_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 0dde184

Please sign in to comment.