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:
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user