From 4abb18373598e986af7a0204010a23d021fdf8e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 1 Jul 2025 20:53:46 +0800 Subject: [PATCH] CPUUsage(Windows): add new method using Windows Performance Library --- CMakeLists.txt | 5 + src/detection/cpuusage/cpuusage.c | 9 ++ src/detection/cpuusage/cpuusage_windows.c | 109 ++++++++++++++++++++++ src/modules/cpuusage/cpuusage.c | 9 ++ src/util/windows/perflib_.h | 7 ++ 5 files changed, 139 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 71e9a50a3..4c101356a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,7 @@ cmake_dependent_option(ENABLE_ELF "Enable libelf" ON "LINUX OR ANDROID OR Dragon cmake_dependent_option(ENABLE_THREADS "Enable multithreading" ON "Threads_FOUND" OFF) cmake_dependent_option(ENABLE_LIBZFS "Enable libzfs" ON "LINUX OR FreeBSD OR SunOS" OFF) cmake_dependent_option(ENABLE_PCIACCESS "Enable libpciaccess" ON "NetBSD OR OpenBSD" OFF) +cmake_dependent_option(ENABLE_CPUUSAGE_PERFLIB "Use perflib (Processor Information) to calcuate CPU usage (used by task manager) instead of CPU times (used by all other *nix platforms)" ON "WIN32" OFF) option(ENABLE_SYSTEM_YYJSON "Use system provided (instead of fastfetch embedded) yyjson library" OFF) option(ENABLE_ASAN "Build fastfetch with ASAN (address sanitizer)" OFF) @@ -1231,6 +1232,10 @@ add_library(libfastfetch OBJECT ${LIBFASTFETCH_SRC} ) +if(ENABLE_CPUUSAGE_PERFLIB) + target_compile_definitions(libfastfetch PUBLIC FF_ENABLE_CPUUSAGE_PERFLIB) +endif() + if(yyjson_FOUND) target_compile_definitions(libfastfetch PUBLIC FF_USE_SYSTEM_YYJSON) target_link_libraries(libfastfetch PUBLIC yyjson::yyjson) diff --git a/src/detection/cpuusage/cpuusage.c b/src/detection/cpuusage/cpuusage.c index d89c54919..b9b944d32 100644 --- a/src/detection/cpuusage/cpuusage.c +++ b/src/detection/cpuusage/cpuusage.c @@ -5,12 +5,14 @@ #include static FFlist cpuTimes1; +static uint64_t startTime; void ffPrepareCPUUsage(void) { assert(cpuTimes1.elementSize == 0); ffListInit(&cpuTimes1, sizeof(FFCpuUsageInfo)); ffGetCpuUsageInfo(&cpuTimes1); + startTime = ffTimeGetNow(); } const char* ffGetCpuUsageResult(FFCPUUsageOptions* options, FFlist* result) @@ -23,6 +25,12 @@ const char* ffGetCpuUsageResult(FFCPUUsageOptions* options, FFlist* result) if(error) return error; ffTimeSleep(options->waitTime); } + else + { + uint64_t elapsedTime = ffTimeGetNow() - startTime; + if (elapsedTime < options->waitTime) + ffTimeSleep(options->waitTime - (uint32_t)elapsedTime); + } if(cpuTimes1.length == 0) return "No CPU cores found"; @@ -57,5 +65,6 @@ retry: cpuTime1->inUseAll = cpuTime2->inUseAll; cpuTime1->totalAll = cpuTime2->totalAll; } + startTime = ffTimeGetNow(); return NULL; } diff --git a/src/detection/cpuusage/cpuusage_windows.c b/src/detection/cpuusage/cpuusage_windows.c index 6acb70f68..5847676c1 100644 --- a/src/detection/cpuusage/cpuusage_windows.c +++ b/src/detection/cpuusage/cpuusage_windows.c @@ -3,6 +3,7 @@ #include "util/mallocHelper.h" +#if !FF_ENABLE_CPUUSAGE_PERFLIB #include #include @@ -35,3 +36,111 @@ const char* ffGetCpuUsageInfo(FFlist* cpuTimes) return NULL; } +#else +#include +#include "util/windows/perflib_.h" +#include + +static inline void ffPerfCloseQueryHandle(HANDLE* phQuery) +{ + if (*phQuery != NULL) + { + PerfCloseQueryHandle(*phQuery); + *phQuery = NULL; + } +} + +const char* ffGetCpuUsageInfo(FFlist* cpuTimes) +{ + struct FFPerfQuerySpec + { + PERF_COUNTER_IDENTIFIER Identifier; + WCHAR Name[16]; + } querySpec = { + .Identifier = { + // Processor Information GUID + // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\_V2Providers\{383487a6-3676-4870-a4e7-d45b30c35629}\{b4fc721a-0378-476f-89ba-a5a79f810b36} + .CounterSetGuid = { 0xb4fc721a, 0x0378, 0x476f, {0x89, 0xba, 0xa5, 0xa7, 0x9f, 0x81, 0x0b, 0x36} }, + .Size = sizeof(querySpec), + .CounterId = PERF_WILDCARD_COUNTER, // https://learn.microsoft.com/en-us/windows/win32/perfctrs/using-the-perflib-functions-to-consume-counter-data + .InstanceId = PERF_WILDCARD_COUNTER, + }, + .Name = PERF_WILDCARD_INSTANCE, + }; + + __attribute__((__cleanup__(ffPerfCloseQueryHandle))) + HANDLE hQuery = NULL; + + if (PerfOpenQueryHandle(NULL, &hQuery) != ERROR_SUCCESS) + return "PerfOpenQueryHandle() failed"; + + if (PerfAddCounters(hQuery, &querySpec.Identifier, sizeof(querySpec)) != ERROR_SUCCESS) + return "PerfAddCounters() failed"; + + if (querySpec.Identifier.Status != ERROR_SUCCESS) + return "PerfAddCounters() reports invalid identifier"; + + DWORD dataSize = 0; + if (PerfQueryCounterData(hQuery, NULL, 0, &dataSize) != ERROR_NOT_ENOUGH_MEMORY) + return "PerfQueryCounterData(NULL) failed"; + + if (dataSize <= sizeof(PERF_DATA_HEADER) + sizeof(PERF_COUNTER_HEADER)) + return "instance doesn't exist"; + + FF_AUTO_FREE PERF_DATA_HEADER* const pDataHeader = (PERF_DATA_HEADER*)malloc(dataSize); + if (PerfQueryCounterData(hQuery, pDataHeader, dataSize, &dataSize) != ERROR_SUCCESS) + return "PerfQueryCounterData(pDataHeader) failed"; + + PERF_COUNTER_HEADER* pCounterHeader = (PERF_COUNTER_HEADER*)(pDataHeader + 1); + if (pCounterHeader->dwType != PERF_COUNTERSET) + return "Invalid counter type"; + + PERF_MULTI_COUNTERS* pMultiCounters = (PERF_MULTI_COUNTERS*)(pCounterHeader + 1); + if (pMultiCounters->dwCounters == 0) + return "No CPU counters found"; + + PERF_MULTI_INSTANCES* pMultiInstances = (PERF_MULTI_INSTANCES*)((BYTE*)pMultiCounters + pMultiCounters->dwSize); + if (pMultiInstances->dwInstances == 0) + return "No CPU instances found"; + + PERF_INSTANCE_HEADER* pInstanceHeader = (PERF_INSTANCE_HEADER*)(pMultiInstances + 1); + for (DWORD iInstance = 0; iInstance < pMultiInstances->dwInstances; ++iInstance) + { + const wchar_t* instanceName = (const wchar_t*)((BYTE*)pInstanceHeader + sizeof(*pInstanceHeader)); + + PERF_COUNTER_DATA* pCounterData = (PERF_COUNTER_DATA*)((BYTE*)pInstanceHeader + pInstanceHeader->Size); + + uint64_t processorUtility = UINT64_MAX, utilityBase = UINT64_MAX; + for (ULONG iCounter = 0; iCounter != pMultiCounters->dwCounters; iCounter++) + { + DWORD* pCounterIds = (DWORD*)(pMultiCounters + 1); + // https://learn.microsoft.com/en-us/windows/win32/perfctrs/using-the-perflib-functions-to-consume-counter-data + switch (pCounterIds[iCounter]) { + case 26: // % Processor Utility (#26, Type=PERF_AVERAGE_BULK) + assert(pCounterData->dwDataSize == sizeof(uint64_t)); + processorUtility = *(uint64_t*)(pCounterData + 1); + break; + case 27: // % Utility Base (#27, Type=PERF_AVERAGE_BASE) + assert(pCounterData->dwDataSize == sizeof(uint32_t)); + utilityBase = *(uint32_t*)(pCounterData + 1) * 100LLU; + break; + } + + pCounterData = (PERF_COUNTER_DATA*)((BYTE*)pCounterData + pCounterData->dwSize); + } + + if (wcschr(instanceName, L'_') == NULL /* ignore `_Total` */ && processorUtility != UINT64_MAX && utilityBase != UINT64_MAX) + { + FFCpuUsageInfo* info = (FFCpuUsageInfo*) ffListAdd(cpuTimes); + *info = (FFCpuUsageInfo) { + .inUseAll = processorUtility, + .totalAll = utilityBase, + }; + } + + pInstanceHeader = (PERF_INSTANCE_HEADER*)pCounterData; + } + + return NULL; +} +#endif diff --git a/src/modules/cpuusage/cpuusage.c b/src/modules/cpuusage/cpuusage.c index 7149c667a..639a498f7 100644 --- a/src/modules/cpuusage/cpuusage.c +++ b/src/modules/cpuusage/cpuusage.c @@ -27,6 +27,11 @@ void ffPrintCPUUsage(FFCPUUsageOptions* options) if (*percent == *percent) { sumValue += *percent; + + #if FF_ENABLE_CPUUSAGE_PERFLIB + // Windows may return values greater than 100%, cap them to 100% + if (*percent > 100) *percent = 100; + #endif if (*percent > maxValue) { maxValue = *percent; @@ -42,6 +47,10 @@ void ffPrintCPUUsage(FFCPUUsageOptions* options) ++index; } double avgValue = sumValue / (double) valueCount; + #if FF_ENABLE_CPUUSAGE_PERFLIB + // See above comment + if (avgValue > 100) avgValue = 100; + #endif FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; diff --git a/src/util/windows/perflib_.h b/src/util/windows/perflib_.h index 5c6a3febc..0d261b963 100644 --- a/src/util/windows/perflib_.h +++ b/src/util/windows/perflib_.h @@ -63,6 +63,13 @@ typedef struct _PERF_COUNTER_HEADER { // If dwType == PERF_COUNTERSET: PERF_MULTI_COUNTERS block + PERF_MULTI_INSTANCES block. } PERF_COUNTER_HEADER, * PPERF_COUNTER_HEADER; +typedef struct _PERF_MULTI_INSTANCES { + ULONG dwTotalSize; // = sizeof(PERF_MULTI_INSTANCES) + sizeof(instance data blocks...) + ULONG dwInstances; // Number of instance data blocks. + // Followed by: + // Instance data blocks...; +} PERF_MULTI_INSTANCES, * PPERF_MULTI_INSTANCES; + typedef struct _PERF_MULTI_COUNTERS { ULONG dwSize; // sizeof(PERF_MULTI_COUNTERS) + sizeof(CounterIds) ULONG dwCounters; // Number of counter ids.