/* ** Copyright (C) 2001-2025 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** This program 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 Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ #include "zbxsysinfo.h" #include "../sysinfo.h" #include "win32_cpu.h" #include "../common/stats.h" #include "perfstat/perfstat.h" /* shortcut to avoid extra verbosity */ typedef PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX PSYS_LPI_EX; /* pointer to GetLogicalProcessorInformationEx(), it's not guaranteed to be available */ typedef BOOL (WINAPI *GETLPIEX)(LOGICAL_PROCESSOR_RELATIONSHIP, PSYS_LPI_EX, PDWORD); static GETLPIEX get_lpiex; /****************************************************************************** * * * Purpose: finds number of active logical CPUs * * * * Return value: number of CPUs or 0 on failure * * * ******************************************************************************/ int get_cpu_num_win32(void) { /* pointer to GetActiveProcessorCount() */ typedef DWORD (WINAPI *GETACTIVEPC)(WORD); ZBX_THREAD_LOCAL static GETACTIVEPC get_act; SYSTEM_INFO sysInfo; PSYS_LPI_EX buffer = NULL; int cpu_count = 0; /* The rationale for checking dynamically if specific functions are implemented */ /* in kernel32.lib is because these may not be available in certain Windows versions. */ /* E.g. GetActiveProcessorCount() is available from Windows 7 onward (and not in Windows Vista or XP) */ /* We can't resolve this using conditional compilation unless we release multiple agents */ /* targeting different sets of Windows APIs. */ /* First, let's try GetLogicalProcessorInformationEx() method. It's the most reliable way */ /* because it counts logical CPUs (aka threads) regardless of whether the application is */ /* 32 or 64-bit. GetActiveProcessorCount() may return incorrect value (e.g. 64 CPUs for systems */ /* with 128 CPUs) if executed under WoW64. */ if (NULL == get_lpiex) { get_lpiex = (GETLPIEX)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "GetLogicalProcessorInformationEx"); } if (NULL != get_lpiex) { DWORD buffer_length = 0; /* first run with empty arguments to figure the buffer length */ if (get_lpiex(RelationProcessorCore, NULL, &buffer_length) || ERROR_INSUFFICIENT_BUFFER != GetLastError()) { goto fallback; } buffer = (PSYS_LPI_EX)zbx_malloc(buffer, (size_t)buffer_length); if (get_lpiex(RelationProcessorCore, buffer, &buffer_length)) { PSYS_LPI_EX ptr; for (unsigned int i = 0; i < buffer_length; i += (unsigned int)ptr->Size) { ptr = (PSYS_LPI_EX)((PBYTE)buffer + i); for (WORD group = 0; group < ptr->Processor.GroupCount; group++) { for (KAFFINITY mask = ptr->Processor.GroupMask[group].Mask; mask != 0; mask >>= 1) { cpu_count += mask & 1; } } } goto finish; } } fallback: if (NULL == get_act) get_act = (GETACTIVEPC)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "GetActiveProcessorCount"); if (NULL != get_act) { /* cpu_count set to 0 if GetActiveProcessorCount() fails */ cpu_count = (int)get_act(ALL_PROCESSOR_GROUPS); goto finish; } zabbix_log(LOG_LEVEL_DEBUG, "GetActiveProcessorCount() not supported, fall back to GetNativeSystemInfo()"); GetNativeSystemInfo(&sysInfo); cpu_count = (int)sysInfo.dwNumberOfProcessors; finish: zbx_free(buffer); zabbix_log(LOG_LEVEL_DEBUG, "logical CPU count %d", cpu_count); return cpu_count; } /****************************************************************************** * * * Purpose: returns number of active processor groups * * * * Return value: number of groups, 1 if groups are not supported * * * ******************************************************************************/ int get_cpu_group_num_win32(void) { /* pointer type for the GetActiveProcessorGroupCount() */ typedef WORD (WINAPI *GETACTIVEPGC)(); ZBX_THREAD_LOCAL static GETACTIVEPGC get_act; /* Check if GetActiveProcessorGroupCount() is available. See comments in get_cpu_num_win32() for details. */ if (NULL == get_act) { get_act = (GETACTIVEPGC)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "GetActiveProcessorGroupCount"); } if (NULL != get_act) { int groups = (int)get_act(); if (0 >= groups) zabbix_log(LOG_LEVEL_WARNING, "GetActiveProcessorGroupCount() failed"); else return groups; } else { zabbix_log(LOG_LEVEL_DEBUG, "GetActiveProcessorGroupCount() not supported, assuming 1"); } return 1; } /****************************************************************************** * * * Purpose: returns number of NUMA nodes * * * * Return value: number of NUMA nodes, 1 if NUMA not supported * * * ******************************************************************************/ int get_numa_node_num_win32(void) { int numa_node_count = 1; if (NULL == get_lpiex) { get_lpiex = (GETLPIEX)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "GetLogicalProcessorInformationEx"); } if (NULL != get_lpiex) { DWORD buffer_length = 0; PSYS_LPI_EX buffer = NULL; /* first run with empty arguments to figure the buffer length */ if (get_lpiex(RelationNumaNode, NULL, &buffer_length) || ERROR_INSUFFICIENT_BUFFER != GetLastError()) goto finish; buffer = (PSYS_LPI_EX)zbx_malloc(buffer, (size_t)buffer_length); if (get_lpiex(RelationNumaNode, buffer, &buffer_length)) { numa_node_count = 0; for (unsigned int i = 0; i < buffer_length; numa_node_count++) { PSYS_LPI_EX ptr = (PSYS_LPI_EX)((PBYTE)buffer + i); i += (unsigned)ptr->Size; } } zbx_free(buffer); } finish: zabbix_log(LOG_LEVEL_DEBUG, "NUMA node count %d", numa_node_count); return numa_node_count; } int system_cpu_num(AGENT_REQUEST *request, AGENT_RESULT *result) { char *tmp; int cpu_num; if (1 < request->nparam) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters.")); return SYSINFO_RET_FAIL; } /* only "online" (default) for parameter "type" is supported */ if (NULL != (tmp = get_rparam(request, 0)) && '\0' != *tmp && 0 != strcmp(tmp, "online")) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter.")); return SYSINFO_RET_FAIL; } if (0 >= (cpu_num = get_cpu_num_win32())) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Error getting number of CPUs.")); return SYSINFO_RET_FAIL; } SET_UI64_RESULT(result, cpu_num); return SYSINFO_RET_OK; } int system_cpu_util(AGENT_REQUEST *request, AGENT_RESULT *result) { char *tmp, *error = NULL; int cpu_num, interval; double value; if (0 == cpu_collector_started()) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Collector is not started.")); return SYSINFO_RET_FAIL; } if (3 < request->nparam) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters.")); return SYSINFO_RET_FAIL; } if (NULL == (tmp = get_rparam(request, 0)) || '\0' == *tmp || 0 == strcmp(tmp, "all")) cpu_num = ZBX_CPUNUM_ALL; else if (SUCCEED != zbx_is_uint_range(tmp, &cpu_num, 0, (get_collector())->cpus.count - 1)) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter.")); return SYSINFO_RET_FAIL; } /* only "system" (default) for parameter "type" is supported */ if (NULL != (tmp = get_rparam(request, 1)) && '\0' != *tmp && 0 != strcmp(tmp, "system")) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter.")); return SYSINFO_RET_FAIL; } if (NULL == (tmp = get_rparam(request, 2)) || '\0' == *tmp || 0 == strcmp(tmp, "avg1")) { interval = 1 * SEC_PER_MIN; } else if (0 == strcmp(tmp, "avg5")) { interval = 5 * SEC_PER_MIN; } else if (0 == strcmp(tmp, "avg15")) { interval = 15 * SEC_PER_MIN; } else { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid third parameter.")); return SYSINFO_RET_FAIL; } if (SUCCEED == get_cpu_perf_counter_value(cpu_num, interval, &value, &error)) { SET_DBL_RESULT(result, value); return SYSINFO_RET_OK; } SET_MSG_RESULT(result, NULL != error ? error : zbx_strdup(NULL, "Cannot obtain performance information from collector.")); return SYSINFO_RET_FAIL; } int system_cpu_load(AGENT_REQUEST *request, AGENT_RESULT *result) { char *tmp, *error = NULL; double value; int cpu_num, ret = FAIL; if (0 == cpu_collector_started()) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Collector is not started.")); return SYSINFO_RET_FAIL; } if (2 < request->nparam) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters.")); return SYSINFO_RET_FAIL; } if (NULL == (tmp = get_rparam(request, 0)) || '\0' == *tmp || 0 == strcmp(tmp, "all")) { cpu_num = 1; } else if (0 == strcmp(tmp, "percpu")) { if (0 >= (cpu_num = get_cpu_num_win32())) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain number of CPUs.")); return SYSINFO_RET_FAIL; } } else { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter.")); return SYSINFO_RET_FAIL; } if (NULL == (tmp = get_rparam(request, 1)) || '\0' == *tmp || 0 == strcmp(tmp, "avg1")) { ret = get_perf_counter_value((get_collector())->cpus.queue_counter, 1 * SEC_PER_MIN, &value, &error); } else if (0 == strcmp(tmp, "avg5")) { ret = get_perf_counter_value((get_collector())->cpus.queue_counter, 5 * SEC_PER_MIN, &value, &error); } else if (0 == strcmp(tmp, "avg15")) { ret = get_perf_counter_value((get_collector())->cpus.queue_counter, 15 * SEC_PER_MIN, &value, &error); } else { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter.")); return SYSINFO_RET_FAIL; } if (SUCCEED == ret) { SET_DBL_RESULT(result, value / cpu_num); return SYSINFO_RET_OK; } SET_MSG_RESULT(result, NULL != error ? error : zbx_strdup(NULL, "Cannot obtain performance information from collector.")); return SYSINFO_RET_FAIL; }