master ohmu/fastfetch-openbsd / src / detection / cpuusage / cpuusage_windows.c
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}