Skip to content

Commit 4abb183

Browse files
committed
CPUUsage(Windows): add new method using Windows Performance Library
1 parent d5b56be commit 4abb183

File tree

5 files changed

+139
-0
lines changed

5 files changed

+139
-0
lines changed

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ cmake_dependent_option(ENABLE_ELF "Enable libelf" ON "LINUX OR ANDROID OR Dragon
8383
cmake_dependent_option(ENABLE_THREADS "Enable multithreading" ON "Threads_FOUND" OFF)
8484
cmake_dependent_option(ENABLE_LIBZFS "Enable libzfs" ON "LINUX OR FreeBSD OR SunOS" OFF)
8585
cmake_dependent_option(ENABLE_PCIACCESS "Enable libpciaccess" ON "NetBSD OR OpenBSD" OFF)
86+
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)
8687

8788
option(ENABLE_SYSTEM_YYJSON "Use system provided (instead of fastfetch embedded) yyjson library" OFF)
8889
option(ENABLE_ASAN "Build fastfetch with ASAN (address sanitizer)" OFF)
@@ -1231,6 +1232,10 @@ add_library(libfastfetch OBJECT
12311232
${LIBFASTFETCH_SRC}
12321233
)
12331234

1235+
if(ENABLE_CPUUSAGE_PERFLIB)
1236+
target_compile_definitions(libfastfetch PUBLIC FF_ENABLE_CPUUSAGE_PERFLIB)
1237+
endif()
1238+
12341239
if(yyjson_FOUND)
12351240
target_compile_definitions(libfastfetch PUBLIC FF_USE_SYSTEM_YYJSON)
12361241
target_link_libraries(libfastfetch PUBLIC yyjson::yyjson)

src/detection/cpuusage/cpuusage.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
#include <stdint.h>
66

77
static FFlist cpuTimes1;
8+
static uint64_t startTime;
89

910
void ffPrepareCPUUsage(void)
1011
{
1112
assert(cpuTimes1.elementSize == 0);
1213
ffListInit(&cpuTimes1, sizeof(FFCpuUsageInfo));
1314
ffGetCpuUsageInfo(&cpuTimes1);
15+
startTime = ffTimeGetNow();
1416
}
1517

1618
const char* ffGetCpuUsageResult(FFCPUUsageOptions* options, FFlist* result)
@@ -23,6 +25,12 @@ const char* ffGetCpuUsageResult(FFCPUUsageOptions* options, FFlist* result)
2325
if(error) return error;
2426
ffTimeSleep(options->waitTime);
2527
}
28+
else
29+
{
30+
uint64_t elapsedTime = ffTimeGetNow() - startTime;
31+
if (elapsedTime < options->waitTime)
32+
ffTimeSleep(options->waitTime - (uint32_t)elapsedTime);
33+
}
2634

2735
if(cpuTimes1.length == 0) return "No CPU cores found";
2836

@@ -57,5 +65,6 @@ const char* ffGetCpuUsageResult(FFCPUUsageOptions* options, FFlist* result)
5765
cpuTime1->inUseAll = cpuTime2->inUseAll;
5866
cpuTime1->totalAll = cpuTime2->totalAll;
5967
}
68+
startTime = ffTimeGetNow();
6069
return NULL;
6170
}

src/detection/cpuusage/cpuusage_windows.c

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include "util/mallocHelper.h"
55

6+
#if !FF_ENABLE_CPUUSAGE_PERFLIB
67
#include <ntstatus.h>
78
#include <winternl.h>
89

@@ -35,3 +36,111 @@ const char* ffGetCpuUsageInfo(FFlist* cpuTimes)
3536

3637
return NULL;
3738
}
39+
#else
40+
#include <windows.h>
41+
#include "util/windows/perflib_.h"
42+
#include <wchar.h>
43+
44+
static inline void ffPerfCloseQueryHandle(HANDLE* phQuery)
45+
{
46+
if (*phQuery != NULL)
47+
{
48+
PerfCloseQueryHandle(*phQuery);
49+
*phQuery = NULL;
50+
}
51+
}
52+
53+
const char* ffGetCpuUsageInfo(FFlist* cpuTimes)
54+
{
55+
struct FFPerfQuerySpec
56+
{
57+
PERF_COUNTER_IDENTIFIER Identifier;
58+
WCHAR Name[16];
59+
} querySpec = {
60+
.Identifier = {
61+
// Processor Information GUID
62+
// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\_V2Providers\{383487a6-3676-4870-a4e7-d45b30c35629}\{b4fc721a-0378-476f-89ba-a5a79f810b36}
63+
.CounterSetGuid = { 0xb4fc721a, 0x0378, 0x476f, {0x89, 0xba, 0xa5, 0xa7, 0x9f, 0x81, 0x0b, 0x36} },
64+
.Size = sizeof(querySpec),
65+
.CounterId = PERF_WILDCARD_COUNTER, // https://learn.microsoft.com/en-us/windows/win32/perfctrs/using-the-perflib-functions-to-consume-counter-data
66+
.InstanceId = PERF_WILDCARD_COUNTER,
67+
},
68+
.Name = PERF_WILDCARD_INSTANCE,
69+
};
70+
71+
__attribute__((__cleanup__(ffPerfCloseQueryHandle)))
72+
HANDLE hQuery = NULL;
73+
74+
if (PerfOpenQueryHandle(NULL, &hQuery) != ERROR_SUCCESS)
75+
return "PerfOpenQueryHandle() failed";
76+
77+
if (PerfAddCounters(hQuery, &querySpec.Identifier, sizeof(querySpec)) != ERROR_SUCCESS)
78+
return "PerfAddCounters() failed";
79+
80+
if (querySpec.Identifier.Status != ERROR_SUCCESS)
81+
return "PerfAddCounters() reports invalid identifier";
82+
83+
DWORD dataSize = 0;
84+
if (PerfQueryCounterData(hQuery, NULL, 0, &dataSize) != ERROR_NOT_ENOUGH_MEMORY)
85+
return "PerfQueryCounterData(NULL) failed";
86+
87+
if (dataSize <= sizeof(PERF_DATA_HEADER) + sizeof(PERF_COUNTER_HEADER))
88+
return "instance doesn't exist";
89+
90+
FF_AUTO_FREE PERF_DATA_HEADER* const pDataHeader = (PERF_DATA_HEADER*)malloc(dataSize);
91+
if (PerfQueryCounterData(hQuery, pDataHeader, dataSize, &dataSize) != ERROR_SUCCESS)
92+
return "PerfQueryCounterData(pDataHeader) failed";
93+
94+
PERF_COUNTER_HEADER* pCounterHeader = (PERF_COUNTER_HEADER*)(pDataHeader + 1);
95+
if (pCounterHeader->dwType != PERF_COUNTERSET)
96+
return "Invalid counter type";
97+
98+
PERF_MULTI_COUNTERS* pMultiCounters = (PERF_MULTI_COUNTERS*)(pCounterHeader + 1);
99+
if (pMultiCounters->dwCounters == 0)
100+
return "No CPU counters found";
101+
102+
PERF_MULTI_INSTANCES* pMultiInstances = (PERF_MULTI_INSTANCES*)((BYTE*)pMultiCounters + pMultiCounters->dwSize);
103+
if (pMultiInstances->dwInstances == 0)
104+
return "No CPU instances found";
105+
106+
PERF_INSTANCE_HEADER* pInstanceHeader = (PERF_INSTANCE_HEADER*)(pMultiInstances + 1);
107+
for (DWORD iInstance = 0; iInstance < pMultiInstances->dwInstances; ++iInstance)
108+
{
109+
const wchar_t* instanceName = (const wchar_t*)((BYTE*)pInstanceHeader + sizeof(*pInstanceHeader));
110+
111+
PERF_COUNTER_DATA* pCounterData = (PERF_COUNTER_DATA*)((BYTE*)pInstanceHeader + pInstanceHeader->Size);
112+
113+
uint64_t processorUtility = UINT64_MAX, utilityBase = UINT64_MAX;
114+
for (ULONG iCounter = 0; iCounter != pMultiCounters->dwCounters; iCounter++)
115+
{
116+
DWORD* pCounterIds = (DWORD*)(pMultiCounters + 1);
117+
// https://learn.microsoft.com/en-us/windows/win32/perfctrs/using-the-perflib-functions-to-consume-counter-data
118+
switch (pCounterIds[iCounter]) {
119+
case 26: // % Processor Utility (#26, Type=PERF_AVERAGE_BULK)
120+
assert(pCounterData->dwDataSize == sizeof(uint64_t));
121+
processorUtility = *(uint64_t*)(pCounterData + 1);
122+
break;
123+
case 27: // % Utility Base (#27, Type=PERF_AVERAGE_BASE)
124+
assert(pCounterData->dwDataSize == sizeof(uint32_t));
125+
utilityBase = *(uint32_t*)(pCounterData + 1) * 100LLU;
126+
break;
127+
}
128+
129+
pCounterData = (PERF_COUNTER_DATA*)((BYTE*)pCounterData + pCounterData->dwSize);
130+
}
131+
132+
if (wcschr(instanceName, L'_') == NULL /* ignore `_Total` */ && processorUtility != UINT64_MAX && utilityBase != UINT64_MAX)
133+
{
134+
FFCpuUsageInfo* info = (FFCpuUsageInfo*) ffListAdd(cpuTimes);
135+
*info = (FFCpuUsageInfo) {
136+
.inUseAll = processorUtility,
137+
.totalAll = utilityBase,
138+
};
139+
}
140+
141+
pInstanceHeader = (PERF_INSTANCE_HEADER*)pCounterData;
142+
}
143+
144+
return NULL;
145+
}
146+
#endif

src/modules/cpuusage/cpuusage.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ void ffPrintCPUUsage(FFCPUUsageOptions* options)
2727
if (*percent == *percent)
2828
{
2929
sumValue += *percent;
30+
31+
#if FF_ENABLE_CPUUSAGE_PERFLIB
32+
// Windows may return values greater than 100%, cap them to 100%
33+
if (*percent > 100) *percent = 100;
34+
#endif
3035
if (*percent > maxValue)
3136
{
3237
maxValue = *percent;
@@ -42,6 +47,10 @@ void ffPrintCPUUsage(FFCPUUsageOptions* options)
4247
++index;
4348
}
4449
double avgValue = sumValue / (double) valueCount;
50+
#if FF_ENABLE_CPUUSAGE_PERFLIB
51+
// See above comment
52+
if (avgValue > 100) avgValue = 100;
53+
#endif
4554

4655
FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;
4756

src/util/windows/perflib_.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ typedef struct _PERF_COUNTER_HEADER {
6363
// If dwType == PERF_COUNTERSET: PERF_MULTI_COUNTERS block + PERF_MULTI_INSTANCES block.
6464
} PERF_COUNTER_HEADER, * PPERF_COUNTER_HEADER;
6565

66+
typedef struct _PERF_MULTI_INSTANCES {
67+
ULONG dwTotalSize; // = sizeof(PERF_MULTI_INSTANCES) + sizeof(instance data blocks...)
68+
ULONG dwInstances; // Number of instance data blocks.
69+
// Followed by:
70+
// Instance data blocks...;
71+
} PERF_MULTI_INSTANCES, * PPERF_MULTI_INSTANCES;
72+
6673
typedef struct _PERF_MULTI_COUNTERS {
6774
ULONG dwSize; // sizeof(PERF_MULTI_COUNTERS) + sizeof(CounterIds)
6875
ULONG dwCounters; // Number of counter ids.

0 commit comments

Comments
 (0)