CPU (Windows): use perflib.h instead of pdh.h for querying CPU temp

Fix #1787
This commit is contained in:
李通洲
2025-06-07 15:48:15 +08:00
parent a66c26f4df
commit 9c37f1b370
3 changed files with 230 additions and 24 deletions
-1
View File
@@ -1593,7 +1593,6 @@ elseif(WIN32)
PRIVATE "winbrand"
PRIVATE "propsys"
PRIVATE "secur32"
PRIVATE "pdh"
)
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "ARM64")
# WoA only works on Windows 10 or higher
+97 -23
View File
@@ -4,42 +4,116 @@
#include "util/mallocHelper.h"
#include "util/smbiosHelper.h"
#include <pdh.h>
#include <windows.h>
#include "perflib_.h"
#include <wchar.h>
static void ffPdhOpenCloseQuery(HQUERY* query)
static inline void ffPerfCloseQueryHandle(HANDLE* phQuery)
{
assert(query);
if (*query)
if (*phQuery != NULL)
{
PdhCloseQuery(*query);
*query = NULL;
PerfCloseQueryHandle(*phQuery);
*phQuery = NULL;
}
}
static const char* detectThermalTemp(double* result)
const char* detectThermalTemp(double* result)
{
// typeperf.exe -sc 1 "\Thermal Zone Information(*)\Temperature"
struct FFPerfQuerySpec
{
PERF_COUNTER_IDENTIFIER Identifier;
WCHAR Name[16];
} querySpec = {
.Identifier = {
// Thermal Zone Information
// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\_V2Providers\{383487a6-3676-4870-a4e7-d45b30c35629}\{52bc5412-dac2-449c-8bc2-96443888fe6b}
.CounterSetGuid = { 0x52bc5412, 0xdac2, 0x449c, {0x8b, 0xc2, 0x96, 0x44, 0x38, 0x88, 0xfe, 0x6b} },
.Size = sizeof(querySpec),
.CounterId = PERF_WILDCARD_COUNTER,
.InstanceId = PERF_WILDCARD_COUNTER,
},
.Name = L"\\_TZ.CPUZ", // The standard(?) instance name for CPU temperature in the thermal provider
};
__attribute__((__cleanup__(ffPdhOpenCloseQuery))) HQUERY query = NULL;
DWORD dataSize = 0;
if (PerfEnumerateCounterSetInstances(NULL, &querySpec.Identifier.CounterSetGuid, NULL, 0, &dataSize) != ERROR_NOT_ENOUGH_MEMORY)
return "PerfEnumerateCounterSetInstances() failed";
if (PdhOpenQueryW(NULL, 0, &query) != ERROR_SUCCESS)
return "Failed to open PDH query";
if (dataSize <= sizeof(PERF_INSTANCE_HEADER))
return "No `Thermal Zone Information` instances found";
HCOUNTER counter = NULL;
if (PdhAddEnglishCounterW(query,
L"\\Thermal Zone Information(*)\\Temperature",
0,
&counter) != ERROR_SUCCESS)
return "Failed to add TZI temperature counter";
{
FF_AUTO_FREE PERF_INSTANCE_HEADER* const pHead = malloc(dataSize);
if (PerfEnumerateCounterSetInstances(NULL, &querySpec.Identifier.CounterSetGuid, pHead, dataSize, &dataSize) != ERROR_SUCCESS)
return "PerfEnumerateCounterSetInstances() failed to get instance headers";
if (PdhCollectQueryData(query) != ERROR_SUCCESS)
return "Failed to collect query data";
PERF_INSTANCE_HEADER* pInstanceHeader = pHead;
while (1)
{
const wchar_t* instanceName = (const wchar_t*)((BYTE*)pInstanceHeader + sizeof(*pInstanceHeader));
if (wcscmp(instanceName, querySpec.Name) == 0)
break;
PDH_FMT_COUNTERVALUE value;
if (PdhGetFormattedCounterValue(counter, PDH_FMT_DOUBLE, NULL, &value) != ERROR_SUCCESS)
return "Failed to format counter value";
dataSize -= pInstanceHeader->Size;
if (dataSize == 0)
break;
pInstanceHeader = (PERF_INSTANCE_HEADER*)((BYTE*)pInstanceHeader + pInstanceHeader->Size);
}
*result = value.doubleValue - 273;
if (dataSize == 0)
{
const wchar_t* instanceName = (const wchar_t*)((BYTE*)pHead + sizeof(*pHead));
wcscpy(querySpec.Name, instanceName); // Use the first instance name if the specific one is not found
}
}
__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";
if (PerfQueryCounterData(hQuery, NULL, 0, &dataSize) != ERROR_NOT_ENOUGH_MEMORY)
return "PerfQueryCounterData(NULL) failed";
if (dataSize <= sizeof(PERF_DATA_HEADER) + sizeof(PERF_COUNTER_HEADER)) // PERF_ERROR_RETURN, should not happen
return "instance doesn't exist";
FF_AUTO_FREE PERF_DATA_HEADER* const pDataHeader = 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_MULTIPLE_COUNTERS)
return "Invalid counter type";
PERF_MULTI_COUNTERS* pMultiCounters = (PERF_MULTI_COUNTERS*)(pCounterHeader + 1);
PERF_COUNTER_DATA* pCounterData = (PERF_COUNTER_DATA*)((BYTE*)pMultiCounters + pMultiCounters->dwSize);
for (ULONG iCounter = 0; iCounter != pMultiCounters->dwCounters; iCounter++)
{
if (pCounterData->dwDataSize == sizeof(int32_t))
{
DWORD* pCounterIds = (DWORD*)(pMultiCounters + 1);
switch (pCounterIds[iCounter]) {
case 0: // Temperature
*result = *(int32_t*)(pCounterData + 1) - 273;
break;
case 3: // High Precision Temperature
*result = *(int32_t*)(pCounterData + 1) / 10.0 - 273;
break;
}
}
pCounterData = (PERF_COUNTER_DATA*)((BYTE*)pCounterData + pCounterData->dwSize);
}
return NULL;
}
+133
View File
@@ -0,0 +1,133 @@
#pragma once
#include <windows.h>
#include <perflib.h>
// Missing from <perflib.h> of MinGW-w64 SDK
#define PERF_WILDCARD_COUNTER 0xFFFFFFFF
#define PERF_WILDCARD_INSTANCE L"*"
#define PERF_AGGREGATE_INSTANCE L"_Total"
#define PERF_MAX_INSTANCE_NAME 1024
typedef struct _PERF_INSTANCE_HEADER {
ULONG Size; // = sizeof(PERF_INSTANCE_HEADER) + sizeof(InstanceName) + sizeof(Padding)
ULONG InstanceId; // Instance ID.
// Followed by:
// WCHAR InstanceName[]; // Nul-terminated.
// WCHAR Padding[]; // Pad to a multiple of 8 bytes
} PERF_INSTANCE_HEADER, *PPERF_INSTANCE_HEADER;
typedef struct _PERF_COUNTER_IDENTIFIER {
GUID CounterSetGuid; // The GUID of the counterset.
ULONG Status; // Win32 error code indicating success/failure of the add/delete operation.
ULONG Size; // sizeof(PERF_COUNTER_IDENTIFIER) + sizeof(InstanceName) + sizeof(Padding)
ULONG CounterId; // CounterId, or PERF_WILDCARD_COUNTER for all counters.
ULONG InstanceId; // InstanceId, or 0xFFFFFFFF to not filter on instance ID.
ULONG Index; // Set by PerfQueryCounterInfo to the position in which the corresponding counter data is returned.
ULONG Reserved; // Reserved.
// Followed by:
// WCHAR InstanceName[];
// WCHAR Padding[];
} PERF_COUNTER_IDENTIFIER, * PPERF_COUNTER_IDENTIFIER;
typedef struct _PERF_DATA_HEADER {
ULONG dwTotalSize; // = sizeof(PERF_DATA_HEADER) + sizeof(PERF_COUNTER_HEADER blocks...)
ULONG dwNumCounters; // The number of PERF_COUNTER_HEADER blocks.
LONGLONG PerfTimeStamp; // Timestamp from a high-resolution clock.
LONGLONG PerfTime100NSec; // The number of 100 nanosecond intervals since January 1, 1601, in Coordinated Universal Time (UTC).
LONGLONG PerfFreq; // The frequency of a high-resolution clock.
SYSTEMTIME SystemTime; // The time at which data is collected on the provider side.
// Followed by:
// PERF_COUNTER_HEADER blocks...;
} PERF_DATA_HEADER, * PPERF_DATA_HEADER;
typedef enum _PerfCounterDataType {
PERF_ERROR_RETURN = 0, /* An error occurred when the performance counter value was queried. */
PERF_SINGLE_COUNTER = 1, /* Query returned a single counter from a single-instance. */
PERF_MULTIPLE_COUNTERS = 2, /* Query returned multiple counters from a single instance. */
PERF_MULTIPLE_INSTANCES = 4, /* Query returned a single counter from each of multiple instances. */
PERF_COUNTERSET = 6 /* Query returned multiple counters from each of multiple instances. */
} PerfCounterDataType;
typedef struct _PERF_COUNTER_HEADER {
ULONG dwStatus; // Win32 error code indicating success/failure of the query operation.
PerfCounterDataType dwType; // Result type - error, single/single, multi/single, single/multi, multi/multi.
ULONG dwSize; // = sizeof(PERF_COUNTER_HEADER) + sizeof(Additional data)
ULONG Reserved; // Reserved.
// Followed by additional data:
// If dwType == PERF_ERROR_RETURN: nothing.
// If dwType == PERF_SINGLE_COUNTER: PERF_COUNTER_DATA block.
// If dwType == PERF_MULTIPLE_COUNTERS: PERF_MULTI_COUNTERS block + PERF_COUNTER_DATA blocks.
// If dwType == PERF_MULTIPLE_INSTANCES: PERF_MULTI_INSTANCES block.
// If dwType == PERF_COUNTERSET: PERF_MULTI_COUNTERS block + PERF_MULTI_INSTANCES block.
} PERF_COUNTER_HEADER, * PPERF_COUNTER_HEADER;
typedef struct _PERF_MULTI_COUNTERS {
ULONG dwSize; // sizeof(PERF_MULTI_COUNTERS) + sizeof(CounterIds)
ULONG dwCounters; // Number of counter ids.
// Followed by:
// DWORD CounterIds[dwCounters];
} PERF_MULTI_COUNTERS, * PPERF_MULTI_COUNTERS;
typedef struct _PERF_COUNTER_DATA {
ULONG dwDataSize; // Size of the counter data, in bytes.
ULONG dwSize; // = sizeof(PERF_COUNTER_DATA) + sizeof(Data) + sizeof(Padding)
// Followed by:
// BYTE Data[dwDataSize];
// BYTE Padding[];
} PERF_COUNTER_DATA, * PPERF_COUNTER_DATA;
_Success_(return == ERROR_SUCCESS)
ULONG
WINAPI
PerfEnumerateCounterSetInstances(
_In_opt_z_ LPCWSTR szMachine,
_In_ LPCGUID pCounterSetId,
_Out_opt_bytecap_post_bytecount_(cbInstances, *pcbInstancesActual) PPERF_INSTANCE_HEADER pInstances,
DWORD cbInstances,
_Out_ LPDWORD pcbInstancesActual
);
_Success_(return == ERROR_SUCCESS)
ULONG
WINAPI
PerfOpenQueryHandle(
_In_opt_z_ LPCWSTR szMachine,
_Out_ HANDLE * phQuery
);
_Success_(return == ERROR_SUCCESS)
ULONG
WINAPI
PerfCloseQueryHandle(
_In_ HANDLE hQuery
);
_Success_(return == ERROR_SUCCESS)
ULONG
WINAPI
PerfAddCounters(
_In_ HANDLE hQuery,
_Inout_bytecount_(cbCounters) PPERF_COUNTER_IDENTIFIER pCounters,
DWORD cbCounters
);
_Success_(return == ERROR_SUCCESS)
ULONG
WINAPI
PerfDeleteCounters(
_In_ HANDLE hQuery,
_Inout_bytecount_(cbCounters) PPERF_COUNTER_IDENTIFIER pCounters,
DWORD cbCounters
);
_Success_(return == ERROR_SUCCESS)
ULONG
WINAPI
PerfQueryCounterData(
_In_ HANDLE hQuery,
_Out_opt_bytecap_post_bytecount_(cbCounterBlock, *pcbCounterBlockActual) PPERF_DATA_HEADER pCounterBlock,
DWORD cbCounterBlock,
_Out_ LPDWORD pcbCounterBlockActual
);