ohmu
·
2026-10-15
1#include "detection/cpuusage/cpuusage.h"
2#include "common/mallocHelper.h"
3#include "common/debug.h"
4
5#include <ntstatus.h>
6#include <windows.h>
7#include <wchar.h>
8#include "common/windows/perflib_.h"
9#include "common/windows/nt.h"
10
11static const char* getInfoByNqsi(FFlist* cpuTimes) {
12 ULONG size = 0;
13 NTSTATUS status = NtQuerySystemInformation(SystemProcessorPerformanceInformation, nullptr, 0, &size);
14 if (status != STATUS_INFO_LENGTH_MISMATCH) {
15 FF_DEBUG("NtQuerySystemInformation(SystemProcessorPerformanceInformation, nullptr) failed: %s", ffDebugNtStatus(status));
16 return "NtQuerySystemInformation(SystemProcessorPerformanceInformation, nullptr) failed";
17 }
18
19 FF_AUTO_FREE SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION* pinfo = (SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION*) malloc(size);
20 status = NtQuerySystemInformation(SystemProcessorPerformanceInformation, pinfo, size, &size);
21 if (!NT_SUCCESS(status)) {
22 FF_DEBUG("NtQuerySystemInformation(SystemProcessorPerformanceInformation, size) failed: %s", ffDebugNtStatus(status));
23 return "NtQuerySystemInformation(SystemProcessorPerformanceInformation, size) failed";
24 }
25
26 for (uint32_t i = 0; i < size / sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION); ++i) {
27 SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION* coreInfo = pinfo + i;
28
29 // KernelTime includes IdleTime and DpcTime.
30 coreInfo->KernelTime.QuadPart -= coreInfo->IdleTime.QuadPart;
31
32 uint64_t inUse = (uint64_t) (coreInfo->UserTime.QuadPart + coreInfo->KernelTime.QuadPart);
33 uint64_t total = inUse + (uint64_t) coreInfo->IdleTime.QuadPart;
34
35 FFCpuUsageInfo* info = FF_LIST_ADD(FFCpuUsageInfo, *cpuTimes);
36 *info = (FFCpuUsageInfo) {
37 .inUseAll = inUse,
38 .totalAll = total,
39 };
40 }
41
42 return nullptr;
43}
44
45static const char* getInfoByPerflib(FFlist* cpuTimes) {
46 static HANDLE hQuery = nullptr;
47
48 if (hQuery == nullptr) {
49 struct FFPerfQuerySpec {
50 PERF_COUNTER_IDENTIFIER Identifier;
51 WCHAR Name[16];
52 } querySpec = {
53 .Identifier = {
54 // Processor Information GUID
55 // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\_V2Providers\{383487a6-3676-4870-a4e7-d45b30c35629}\{b4fc721a-0378-476f-89ba-a5a79f810b36}
56 .CounterSetGuid = { 0xb4fc721a, 0x0378, 0x476f, { 0x89, 0xba, 0xa5, 0xa7, 0x9f, 0x81, 0x0b, 0x36 } },
57 .Size = sizeof(querySpec),
58 .CounterId = PERF_WILDCARD_COUNTER, // https://learn.microsoft.com/en-us/windows/win32/perfctrs/using-the-perflib-functions-to-consume-counter-data
59 .InstanceId = PERF_WILDCARD_COUNTER,
60 },
61 .Name = PERF_WILDCARD_INSTANCE,
62 };
63
64 DWORD ret = PerfOpenQueryHandle(nullptr, &hQuery);
65 if (ret != ERROR_SUCCESS) {
66 FF_DEBUG("PerfOpenQueryHandle() failed: %s", ffDebugWin32Error(ret));
67 PerfCloseQueryHandle(hQuery);
68 hQuery = INVALID_HANDLE_VALUE;
69 return "PerfOpenQueryHandle() failed";
70 }
71
72 ret = PerfAddCounters(hQuery, &querySpec.Identifier, sizeof(querySpec));
73 if (ret != ERROR_SUCCESS) {
74 FF_DEBUG("PerfAddCounters() failed: %s", ffDebugWin32Error(ret));
75 PerfCloseQueryHandle(hQuery);
76 hQuery = INVALID_HANDLE_VALUE;
77 return "PerfAddCounters() failed";
78 }
79
80 if (querySpec.Identifier.Status != ERROR_SUCCESS) {
81 FF_DEBUG("PerfAddCounters() reports invalid identifier: %s", ffDebugWin32Error(querySpec.Identifier.Status));
82 PerfCloseQueryHandle(hQuery);
83 hQuery = INVALID_HANDLE_VALUE;
84 return "PerfAddCounters() reports invalid identifier";
85 }
86 }
87
88 if (hQuery == INVALID_HANDLE_VALUE) {
89 FF_DEBUG("Init hQuery failed");
90 return "Init hQuery failed";
91 }
92
93 DWORD dataSize = 0;
94 DWORD ret = PerfQueryCounterData(hQuery, nullptr, 0, &dataSize);
95 if (ret != ERROR_NOT_ENOUGH_MEMORY) {
96 FF_DEBUG("PerfQueryCounterData(nullptr) failed: %s", ffDebugWin32Error(ret));
97 return "PerfQueryCounterData(nullptr) failed";
98 }
99
100 if (dataSize <= sizeof(PERF_DATA_HEADER) + sizeof(PERF_COUNTER_HEADER)) {
101 FF_DEBUG("instance doesn't exist");
102 return "instance doesn't exist";
103 }
104
105 FF_AUTO_FREE PERF_DATA_HEADER* const pDataHeader = (PERF_DATA_HEADER*) malloc(dataSize);
106 ret = PerfQueryCounterData(hQuery, pDataHeader, dataSize, &dataSize);
107 if (ret != ERROR_SUCCESS) {
108 FF_DEBUG("PerfQueryCounterData(pDataHeader) failed: %s", ffDebugWin32Error(ret));
109 return "PerfQueryCounterData(pDataHeader) failed";
110 }
111
112 PERF_COUNTER_HEADER* pCounterHeader = (PERF_COUNTER_HEADER*) (pDataHeader + 1);
113 if (pCounterHeader->dwType != PERF_COUNTERSET) {
114 FF_DEBUG("Invalid counter type");
115 return "Invalid counter type";
116 }
117
118 PERF_MULTI_COUNTERS* pMultiCounters = (PERF_MULTI_COUNTERS*) (pCounterHeader + 1);
119 if (pMultiCounters->dwCounters == 0) {
120 FF_DEBUG("No CPU counters found");
121 return "No CPU counters found";
122 }
123
124 PERF_MULTI_INSTANCES* pMultiInstances = (PERF_MULTI_INSTANCES*) ((BYTE*) pMultiCounters + pMultiCounters->dwSize);
125 if (pMultiInstances->dwInstances == 0) {
126 FF_DEBUG("No CPU instances found");
127 return "No CPU instances found";
128 }
129
130 PERF_INSTANCE_HEADER* pInstanceHeader = (PERF_INSTANCE_HEADER*) (pMultiInstances + 1);
131 for (DWORD iInstance = 0; iInstance < pMultiInstances->dwInstances; ++iInstance) {
132 const wchar_t* instanceName = (const wchar_t*) ((BYTE*) pInstanceHeader + sizeof(*pInstanceHeader));
133
134 PERF_COUNTER_DATA* pCounterData = (PERF_COUNTER_DATA*) ((BYTE*) pInstanceHeader + pInstanceHeader->Size);
135
136 uint64_t processorUtility = UINT64_MAX, utilityBase = UINT64_MAX;
137 for (ULONG iCounter = 0; iCounter != pMultiCounters->dwCounters; iCounter++) {
138 DWORD* pCounterIds = (DWORD*) (pMultiCounters + 1);
139 // https://learn.microsoft.com/en-us/windows/win32/perfctrs/using-the-perflib-functions-to-consume-counter-data
140 switch (pCounterIds[iCounter]) {
141 case 26: // % Processor Utility (#26, Type=PERF_AVERAGE_BULK)
142 assert(pCounterData->dwDataSize == sizeof(uint64_t));
143 processorUtility = *(uint64_t*) (pCounterData + 1);
144 break;
145 case 27: // % Utility Base (#27, Type=PERF_AVERAGE_BASE)
146 assert(pCounterData->dwDataSize == sizeof(uint32_t));
147 utilityBase = *(uint32_t*) (pCounterData + 1) * 100LLU;
148 break;
149 }
150
151 pCounterData = (PERF_COUNTER_DATA*) ((BYTE*) pCounterData + pCounterData->dwSize);
152 }
153
154 if (wcschr(instanceName, L'_') == nullptr /* ignore `_Total` */) {
155 if (processorUtility == UINT64_MAX) {
156 FF_DEBUG("Counter \"%% Processor Utility\" are not supported");
157 return "Counter \"% Processor Utility\" are not supported";
158 }
159
160 FFCpuUsageInfo* info = FF_LIST_ADD(FFCpuUsageInfo, *cpuTimes);
161 *info = (FFCpuUsageInfo) {
162 .inUseAll = processorUtility,
163 .totalAll = utilityBase,
164 };
165 }
166
167 pInstanceHeader = (PERF_INSTANCE_HEADER*) pCounterData;
168 }
169
170 return nullptr;
171}
172
173const char* ffGetCpuUsageInfo(FFlist* cpuTimes) {
174 const char* error = nullptr;
175
176 if (ffIsWindows10OrGreater()) {
177 error = getInfoByPerflib(cpuTimes);
178 FF_DEBUG("Get CPU usage info by Perflib: %s", error ?: "success");
179 if (!error) {
180 return nullptr;
181 }
182 ffListClear(cpuTimes);
183 }
184
185 error = getInfoByNqsi(cpuTimes);
186 FF_DEBUG("Get CPU usage info by NtQuerySystemInformation: %s", error ?: "success");
187 return error;
188}