Fix CPU telemetry temperature/clock speed on Windows and macOS

sample_cpu_temperature() only ever had a Linux implementation, so
Windows and macOS runs recorded empty temperature data points for
every sample. sample_cpu_clock_mhz() on both platforms read a static
nominal frequency once (Windows: the ~MHz registry value; macOS: the
hw.cpufrequency sysctl) instead of a live reading, so clock speed
showed as a constant throughout the benchmark.

Windows: temperature now queries the ACPI thermal zone over WMI
(MSAcpi_ThermalZoneTemperature), and clock speed reads live per-core
frequency via CallNtPowerInformation(ProcessorInformation). Both are
XP-compatible (verified via mingw cross-compile with the existing
windows-i386 XP toolchain/PE checks).

macOS: temperature reads the SMC directly (AppleSMC user client),
checking known CPU sensor keys across Intel and Apple Silicon
(M1-M5). There is no live-frequency API on macOS at all (Apple
Silicon has none, and hw.cpufrequency was always a fixed nominal
value even on Intel), so clock speed now reports as unavailable
instead of a misleading constant.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PcL8rcwwBeD6W3fZFCRLss
This commit is contained in:
2026-07-21 01:13:33 -05:00
co-authored by Claude Sonnet 5
parent dcc77408c0
commit ec7e75e105
3 changed files with 250 additions and 15 deletions
+1 -1
View File
@@ -175,5 +175,5 @@ jobs:
release/SHA256SUMS \
--repo "$GITHUB_REPOSITORY" \
--title "Release $RELEASE_TAG" \
--notes "Fixed x86/Linux builds on systems without OpenSSL development headers. Result uploads now use plain HTTP on every platform, removing the OpenSSL build and runtime dependency." \
--notes "Hotfix: CPU telemetry (temperature and clock speed) recorded during benchmark runs was not working on Windows or macOS, showing empty temperature data points and a constant clock speed. Windows temperature now reads the ACPI thermal zone over WMI, and clock speed reads live per-core frequency via CallNtPowerInformation instead of a static registry value. macOS temperature now reads the SMC directly across Intel and Apple Silicon (M1-M5) Macs. macOS clock speed now reports as unavailable rather than the fixed nominal frequency it previously showed, since no live-frequency API exists on macOS." \
--generate-notes
+6 -5
View File
@@ -178,20 +178,21 @@ $(DIST)/fossbench-linux-ppc64le: $(DRIVER) $(DRIVER_DEPS) $(ASM_PPC64LE) | $(DIS
@echo "built $@"
$(DIST)/fossbench-macos-arm64: $(DRIVER) $(DRIVER_DEPS) $(ASM_ARM64) | $(DIST)
$(CC_MACOS_ARM64) -arch arm64 $(CFLAGS) $(PTHREAD) $(LDFLAGS) -o $@ $(DRIVER) $(ASM_ARM64) $(LDLIBS)
$(CC_MACOS_ARM64) -arch arm64 $(CFLAGS) $(PTHREAD) $(LDFLAGS) -o $@ $(DRIVER) $(ASM_ARM64) $(LDLIBS) -framework IOKit -framework CoreFoundation
@echo "built $@"
$(DIST)/fossbench-macos-amd64: $(DRIVER) $(DRIVER_DEPS) $(ASM_AMD64) | $(DIST)
MACOSX_DEPLOYMENT_TARGET=$(MACOS_AMD64_MIN) $(CC_MACOS_AMD64) -arch x86_64 -mmacosx-version-min=$(MACOS_AMD64_MIN) $(CFLAGS) $(PTHREAD) $(LDFLAGS) -Wl,-no_fixup_chains -o $@ $(DRIVER) $(ASM_AMD64) $(LDLIBS)
MACOSX_DEPLOYMENT_TARGET=$(MACOS_AMD64_MIN) $(CC_MACOS_AMD64) -arch x86_64 -mmacosx-version-min=$(MACOS_AMD64_MIN) $(CFLAGS) $(PTHREAD) $(LDFLAGS) -Wl,-no_fixup_chains -o $@ $(DRIVER) $(ASM_AMD64) $(LDLIBS) -framework IOKit -framework CoreFoundation
@echo "built $@"
# Windows builds are static and use WinHTTP.
# Windows builds are static and use WinHTTP. Temperature/clock telemetry
# additionally needs WMI (ole32/oleaut32/wbemuuid) and PowrProf.
$(DIST)/fossbench-windows-amd64.exe: $(DRIVER) $(DRIVER_DEPS) $(ASM_AMD64) | $(DIST)
$(CC_WINDOWS_AMD64) $(CFLAGS) $(PTHREAD) -static -o $@ $(DRIVER) $(ASM_AMD64) -lm -lwinhttp -ladvapi32
$(CC_WINDOWS_AMD64) $(CFLAGS) $(PTHREAD) -static -o $@ $(DRIVER) $(ASM_AMD64) -lm -lwinhttp -ladvapi32 -lole32 -loleaut32 -lwbemuuid -lpowrprof
@echo "built $@"
$(DIST)/fossbench-windows-i386.exe: $(DRIVER) $(DRIVER_DEPS) $(SRC_I386) | $(DIST)
$(CC_WINDOWS_I386) -march=i386 $(WINDOWS_I386_XP_CFLAGS) $(CFLAGS) -static $(WINDOWS_I386_XP_LDFLAGS) -o $@ $(DRIVER) $(SRC_I386) -lm -lwinhttp -ladvapi32
$(CC_WINDOWS_I386) -march=i386 $(WINDOWS_I386_XP_CFLAGS) $(CFLAGS) -static $(WINDOWS_I386_XP_LDFLAGS) -o $@ $(DRIVER) $(SRC_I386) -lm -lwinhttp -ladvapi32 -lole32 -loleaut32 -lwbemuuid -lpowrprof
@echo "built $@"
# Add a native rule if one was not already made above.
+243 -9
View File
@@ -12,6 +12,20 @@
# include <tlhelp32.h>
# include <winhttp.h>
# include <process.h>
# include <powrprof.h>
# include <wbemidl.h>
# include <oleauto.h>
/* Documented by Microsoft but not declared in MinGW's powrprof.h; including
* <ddk/ntpoapi.h> for it collides with definitions already pulled in by
* <windows.h>, so declare the same layout locally instead. */
typedef struct _PROCESSOR_POWER_INFORMATION {
ULONG Number;
ULONG MaxMhz;
ULONG CurrentMhz;
ULONG MhzLimit;
ULONG MaxIdleState;
ULONG CurrentIdleState;
} PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION;
#else
# include <pthread.h>
# include <unistd.h>
@@ -32,6 +46,7 @@
# include <sys/sysctl.h>
# include <mach/mach_time.h>
# include <mach/mach.h>
# include <IOKit/IOKitLib.h>
#endif
/* The server URL can be changed when building. */
@@ -270,6 +285,179 @@ static int cpu_sensor_name(const char *name)
}
#endif
#if defined(_WIN32)
/* Reads MSAcpi_ThermalZoneTemperature from the ACPI thermal zone WMI namespace.
* Only populated on hardware whose firmware exposes it; many laptops do not. */
static IWbemServices *wmi_connect_thermal_namespace(void)
{
IWbemLocator *locator = NULL;
IWbemServices *services = NULL;
BSTR namespace_path = NULL;
HRESULT hr;
hr = CoCreateInstance(&CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, &IID_IWbemLocator, (LPVOID *)&locator);
if (FAILED(hr) || !locator) return NULL;
namespace_path = SysAllocString(L"ROOT\\WMI");
if (!namespace_path) { locator->lpVtbl->Release(locator); return NULL; }
hr = locator->lpVtbl->ConnectServer(locator, namespace_path, NULL, NULL, NULL, 0, NULL, NULL, &services);
SysFreeString(namespace_path);
locator->lpVtbl->Release(locator);
if (FAILED(hr) || !services) return NULL;
hr = CoSetProxyBlanket((IUnknown *)services, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
if (FAILED(hr)) { services->lpVtbl->Release(services); return NULL; }
return services;
}
static double wmi_query_temperature(IWbemServices *services)
{
IEnumWbemClassObject *enumerator = NULL;
BSTR language = NULL, query = NULL;
HRESULT hr;
double hottest = -1.0;
language = SysAllocString(L"WQL");
query = SysAllocString(L"SELECT CurrentTemperature FROM MSAcpi_ThermalZoneTemperature");
if (!language || !query) goto done;
hr = services->lpVtbl->ExecQuery(services, language, query,
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &enumerator);
if (FAILED(hr) || !enumerator) goto done;
for (;;) {
IWbemClassObject *obj = NULL;
ULONG returned = 0;
VARIANT value;
hr = enumerator->lpVtbl->Next(enumerator, WBEM_INFINITE, 1, &obj, &returned);
if (FAILED(hr) || returned == 0) break;
VariantInit(&value);
/* CurrentTemperature is reported in tenths of a Kelvin. */
if (SUCCEEDED(obj->lpVtbl->Get(obj, L"CurrentTemperature", 0, &value, NULL, NULL))) {
double tenths_kelvin = 0.0;
if (value.vt == VT_I4) tenths_kelvin = (double)value.lVal;
else if (value.vt == VT_UI4) tenths_kelvin = (double)value.ulVal;
if (tenths_kelvin > 0.0) {
double celsius = tenths_kelvin / 10.0 - 273.15;
if (celsius > -50.0 && celsius <= 150.0 && celsius > hottest) hottest = celsius;
}
}
VariantClear(&value);
obj->lpVtbl->Release(obj);
}
enumerator->lpVtbl->Release(enumerator);
done:
if (query) SysFreeString(query);
if (language) SysFreeString(language);
return hottest;
}
#endif
#if defined(__APPLE__)
/* Talks to the AppleSMC user client directly; there is no public framework
* for this. Struct layout and command bytes come from the SMC protocol that
* has been stable across Intel and Apple Silicon Macs for over a decade. */
typedef struct {
char major, minor, build, reserved_;
uint16_t release;
} fb_smc_vers_t;
typedef struct {
uint16_t version, length;
uint32_t cpu_p_limit, gpu_p_limit, mem_p_limit;
} fb_smc_plimit_t;
typedef struct {
uint32_t data_size;
uint32_t data_type;
char data_attributes;
} fb_smc_key_info_t;
typedef struct {
uint32_t key;
fb_smc_vers_t vers;
fb_smc_plimit_t plimit;
fb_smc_key_info_t key_info;
char result, status, data8;
uint32_t data32;
unsigned char bytes[32];
} fb_smc_data_t;
#define FB_SMC_KERNEL_INDEX 2
#define FB_SMC_CMD_READ_BYTES 5
#define FB_SMC_CMD_READ_KEY_INFO 9
static uint32_t smc_key_from_string(const char *s)
{
return ((uint32_t)(unsigned char)s[0] << 24) | ((uint32_t)(unsigned char)s[1] << 16) |
((uint32_t)(unsigned char)s[2] << 8) | (uint32_t)(unsigned char)s[3];
}
static kern_return_t smc_call(io_connect_t conn, fb_smc_data_t *in, fb_smc_data_t *out)
{
size_t out_size = sizeof(*out);
return IOConnectCallStructMethod(conn, FB_SMC_KERNEL_INDEX, in, sizeof(*in), out, &out_size);
}
static int smc_read_temperature(io_connect_t conn, const char *key, double *out)
{
fb_smc_data_t in, info_out, read_out;
memset(&in, 0, sizeof(in));
memset(&info_out, 0, sizeof(info_out));
in.key = smc_key_from_string(key);
in.data8 = FB_SMC_CMD_READ_KEY_INFO;
if (smc_call(conn, &in, &info_out) != KERN_SUCCESS || info_out.key_info.data_size == 0)
return 0;
memset(&in, 0, sizeof(in));
memset(&read_out, 0, sizeof(read_out));
in.key = smc_key_from_string(key);
in.key_info.data_size = info_out.key_info.data_size;
in.data8 = FB_SMC_CMD_READ_BYTES;
if (smc_call(conn, &in, &read_out) != KERN_SUCCESS)
return 0;
if (info_out.key_info.data_type == smc_key_from_string("sp78") && info_out.key_info.data_size >= 2) {
int16_t raw = (int16_t)((read_out.bytes[0] << 8) | read_out.bytes[1]);
*out = raw / 256.0;
return 1;
}
if (info_out.key_info.data_type == smc_key_from_string("flt ") && info_out.key_info.data_size >= 4) {
float f;
memcpy(&f, read_out.bytes, sizeof(f));
*out = (double)f;
return 1;
}
return 0;
}
static io_connect_t smc_open(void)
{
io_service_t service;
io_connect_t conn = IO_OBJECT_NULL;
service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleSMC"));
if (service == IO_OBJECT_NULL) return IO_OBJECT_NULL;
if (IOServiceOpen(service, mach_task_self(), 0, &conn) != KERN_SUCCESS) conn = IO_OBJECT_NULL;
IOObjectRelease(service);
return conn;
}
/* CPU-related SMC keys across Intel Macs and Apple Silicon M1-M5, gathered
* from publicly reverse-engineered sensor tables (e.g. exelban/stats,
* acidanthera/VirtualSMC). Coverage varies by exact model/chip revision. */
static const char *const smc_cpu_temp_keys[] = {
"TC0D", "TC0E", "TC0F", "TC0P",
"Tp00", "Tp01", "Tp04", "Tp05", "Tp08", "Tp09", "Tp0C", "Tp0D", "Tp0f", "Tp0G",
"Tp0H", "Tp0j", "Tp0K", "Tp0L", "Tp0m", "Tp0n", "Tp0O", "Tp0P", "Tp0p", "Tp0r",
"Tp0R", "Tp0T", "Tp0u", "Tp0U", "Tp0X", "Tp0a", "Tp0b", "Tp0d", "Tp0g", "Tp0y",
"Tp1h", "Tp1l", "Tp1p", "Tp1t",
"Tc0a", "Tc0b", "Tc0x", "Tc0z",
};
#endif
static double sample_cpu_temperature(void)
{
#if defined(__linux__)
@@ -318,6 +506,37 @@ static double sample_cpu_temperature(void)
closedir(dir);
}
return hottest;
#elif defined(_WIN32)
{
static int initialized = 0;
static IWbemServices *services = NULL;
if (!initialized) {
initialized = 1;
services = wmi_connect_thermal_namespace();
}
return services ? wmi_query_temperature(services) : -1.0;
}
#elif defined(__APPLE__)
{
static int initialized = 0;
static io_connect_t conn = IO_OBJECT_NULL;
double hottest = -1.0;
size_t i;
if (!initialized) {
initialized = 1;
conn = smc_open();
}
if (conn == IO_OBJECT_NULL) return -1.0;
for (i = 0; i < sizeof(smc_cpu_temp_keys) / sizeof(smc_cpu_temp_keys[0]); i++) {
double value;
if (smc_read_temperature(conn, smc_cpu_temp_keys[i], &value) &&
value > 0.0 && value <= 150.0 && value > hottest)
hottest = value;
}
return hottest;
}
#else
return -1.0;
#endif
@@ -355,17 +574,26 @@ static double sample_cpu_clock_mhz(void)
}
return count ? total / (double)count : -1.0;
#elif defined(__APPLE__)
{
uint64_t hz = 0; size_t size = sizeof(hz);
return sysctlbyname("hw.cpufrequency", &hz, &size, NULL, 0) == 0 && hz ? (double)hz / 1000000.0 : -1.0;
}
/* No public API reports live per-core frequency on macOS (none exists at
* all on Apple Silicon); sysctl hw.cpufrequency is a fixed nominal value,
* not a real-time reading, so report unavailable rather than a constant. */
return -1.0;
#elif defined(_WIN32)
{
HKEY key; DWORD mhz = 0, size = sizeof(mhz), type = 0;
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &key) != ERROR_SUCCESS) return -1.0;
if (RegQueryValueExA(key, "~MHz", NULL, &type, (LPBYTE)&mhz, &size) != ERROR_SUCCESS || type != REG_DWORD) mhz = 0;
RegCloseKey(key);
return mhz ? (double)mhz : -1.0;
PROCESSOR_POWER_INFORMATION info[64];
SYSTEM_INFO sysinfo;
DWORD count, i;
double total = 0.0;
GetSystemInfo(&sysinfo);
count = sysinfo.dwNumberOfProcessors;
if (count == 0) count = 1;
if (count > 64) count = 64;
if (CallNtPowerInformation(ProcessorInformation, NULL, 0, info,
count * sizeof(PROCESSOR_POWER_INFORMATION)) != 0)
return -1.0;
for (i = 0; i < count; i++) total += info[i].CurrentMhz;
return count ? total / (double)count : -1.0;
}
#else
return -1.0;
@@ -399,6 +627,11 @@ static void *telemetry_entry(void *arg)
{
struct telemetry_monitor *monitor = (struct telemetry_monitor *)arg;
double next = monitor->started;
#if defined(_WIN32)
/* sample_cpu_temperature() lazily opens a WMI connection on first use,
* which requires this thread's COM apartment to be initialized first. */
CoInitializeEx(NULL, COINIT_MULTITHREADED);
#endif
while (!monitor->stop) {
double current = now_seconds();
if (current >= next) { record_telemetry_sample(monitor); next += TELEMETRY_REFRESH_INTERVAL; }
@@ -406,6 +639,7 @@ static void *telemetry_entry(void *arg)
}
record_telemetry_sample(monitor);
#if defined(_WIN32)
CoUninitialize();
return 0;
#else
return NULL;