Skip to content

Potential incorrect values in getUsableMemory() and getFreeSwapMemory() on Windows #208

Description

@marcosfrm

The Windows implementation of getMemoryInfo() appears to have a calculation error.

uint64_t WinSystemInfo::getMemoryInfo(memory_info_t type) const {
MEMORYSTATUSEX info;
info.dwLength = sizeof(MEMORYSTATUSEX);
GlobalMemoryStatusEx(&info);
switch (type) {
case MEM_INFO_TOTAL: return (uint64_t)info.ullTotalPhys;
case MEM_INFO_FREE: return (uint64_t)info.ullAvailPhys;
case MEM_INFO_SWAP: return (uint64_t)info.ullAvailPageFile;
case MEM_INFO_USABLE:
return (uint64_t)(info.ullAvailPhys + info.ullAvailPageFile);
}
return 0;
}

In getUsableMemory(), the code sums ullAvailPhys + ullAvailPageFile. However, according to the Windows API documentation, ullAvailPageFile already includes both free RAM and available pagefile space ("The maximum amount of memory the current process can commit ...": https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex). This causes the free RAM to be counted twice.

Similarly, getFreeSwapMemory() returns ullAvailPageFile directly, which includes free physical RAM instead of showing only the isolated swap space.

Suggested fix:

  • MEM_INFO_USABLE: Should just return info.ullAvailPageFile. Note that this represents the unallocated system commit limit (free RAM + free swap), which differs fundamentally from Linux's MemAvailable. It is impossible to calculate a true MemAvailable equivalent using only the MEMORYSTATUSEX structure; achieving that requires switching to PERFORMANCE_INFORMATION and calculating it via perfInfo.PhysicalAvailable * perfInfo.PageSize (which includes the system cache/standby list: https://learn.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-performance_information), as done in Python's psutil:
    unsigned long long totalPhys, availPhys, totalSys, availSys, pageSize;
    PERFORMANCE_INFORMATION perfInfo;

    if (!GetPerformanceInfo(&perfInfo, sizeof(PERFORMANCE_INFORMATION))) {
        psutil_oserror();
        return NULL;
    }
    // values are size_t, widen (if needed) to long long
    pageSize = perfInfo.PageSize;
    totalPhys = perfInfo.PhysicalTotal * pageSize;
    availPhys = perfInfo.PhysicalAvailable * pageSize;
    totalSys = perfInfo.CommitLimit * pageSize;
    availSys = totalSys - perfInfo.CommitTotal * pageSize;

https://github.com/giampaolo/psutil/blob/v7.2.2/psutil/arch/windows/mem.c#L23-L39

  • MEM_INFO_SWAP should return info.ullAvailPageFile - info.ullAvailPhys to isolate the pagefile, which is roughly equivalent to Linux's SwapFree in /proc/meminfo.

Here is how Zabbix does it (it separates total swap and available swap):

		ms_ex.dwLength = sizeof(MEMORYSTATUSEX);

		(*zbx_get_GlobalMemoryStatusEx())(&ms_ex);

		real_swap_total = ms_ex.ullTotalPageFile > ms_ex.ullTotalPhys ?
				ms_ex.ullTotalPageFile - ms_ex.ullTotalPhys : 0;
		real_swap_avail = ms_ex.ullAvailPageFile > ms_ex.ullAvailPhys ?
				ms_ex.ullAvailPageFile - ms_ex.ullAvailPhys : 0;

https://github.com/zabbix/zabbix/blob/7.4.11/src/libs/zbxsysinfo/win32/swap.c#L130-L137

Note: this issue and its proposed fix were generated with help from Gemini and require manual review.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions