From 3288c8cc8c11276050e91e51d46d632fef144190 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Thu, 27 Aug 2026 17:23:49 +0200 Subject: [PATCH] Add machineinfo_struct to query and report system and CPU information in speed test --- cmdline/app.h | 35 +++ cmdline/mingwapp.c | 159 +++++++++++++ cmdline/speed.c | 162 +++++++++----- cmdline/unixapp.c | 540 +++++++++++++++++++++++++++++++++++++++------ 4 files changed, 772 insertions(+), 124 deletions(-) diff --git a/cmdline/app.h b/cmdline/app.h index a0df1941..4d9b523e 100644 --- a/cmdline/app.h +++ b/cmdline/app.h @@ -462,5 +462,40 @@ void app_default_conf(char* dst, size_t dst_size, const char* argv0); */ void app_signal_handler(int signum); +/****************************************************************************/ +/* machine */ + +#define MACHINEINFO_MAX 256 + +/** + * Host machine and CPU information. + */ +struct machineinfo_struct { + char cpu_brand[MACHINEINFO_MAX]; /**< CPU model brand name. */ + char cpu_vendor[MACHINEINFO_MAX]; /**< CPU vendor string (e.g. GenuineIntel, AuthenticAMD). */ + char cpu_arch[MACHINEINFO_MAX]; /**< CPU architecture string. */ + char cpu_clock[MACHINEINFO_MAX]; /**< CPU clock frequency description. */ + unsigned cpu_family; /**< CPU family id. */ + unsigned cpu_model; /**< CPU model id. */ + unsigned cpu_stepping; /**< CPU stepping id. */ + unsigned cpu_cores_physical; /**< Number of physical CPU cores (0 if unavailable). */ + unsigned cpu_cores_logical; /**< Number of logical CPU threads (0 if unavailable). */ + uint64_t cache_l1_data; /**< L1 data cache in bytes (0 if unavailable). */ + uint64_t cache_l1_instruction; /**< L1 instruction cache in bytes (0 if unavailable). */ + uint64_t cache_l2; /**< L2 cache in bytes (0 if unavailable). */ + uint64_t cache_l3; /**< L3 cache in bytes (0 if unavailable). */ + uint64_t memory_total_bytes; /**< Total system RAM in bytes (0 if unavailable). */ + uint32_t memory_page_size; /**< Page size in bytes (0 if unavailable). */ + char os_name[MACHINEINFO_MAX]; /**< Operating system name and kernel version. */ + char os_distribution[MACHINEINFO_MAX]; /**< Operating system distribution name. */ + char system_model[MACHINEINFO_MAX]; /**< Motherboard or system model name. */ + char hypervisor[MACHINEINFO_MAX]; /**< Hypervisor name (empty if bare metal). */ +}; + +/** + * Query host machine information. + */ +void machineinfo(struct machineinfo_struct* info); + #endif diff --git a/cmdline/mingwapp.c b/cmdline/mingwapp.c index 67e606c2..e592b58c 100644 --- a/cmdline/mingwapp.c +++ b/cmdline/mingwapp.c @@ -6,6 +6,7 @@ #ifdef __MINGW32__ /* Only for MingW */ #include "support.h" +#include "raid/cpu.h" /****************************************************************************/ /* global */ @@ -1931,5 +1932,163 @@ void app_default_conf(char* conf, size_t conf_size, const char* argv0) } } +/****************************************************************************/ +/* machine */ + +#ifdef CONFIG_X86 +static void format_freq(char* buf, size_t size, unsigned mhz) +{ + if (mhz >= 1000) + snprintf(buf, size, "%.2f GHz", mhz / 1000.0); + else + snprintf(buf, size, "%u MHz", mhz); +} +#endif + +void machineinfo(struct machineinfo_struct* info) +{ + memset(info, 0, sizeof(*info)); + +#if defined(__x86_64__) + snprintf(info->cpu_arch, sizeof(info->cpu_arch), "x86_64"); +#elif defined(__i386__) + snprintf(info->cpu_arch, sizeof(info->cpu_arch), "x86_32"); +#elif defined(__aarch64__) + snprintf(info->cpu_arch, sizeof(info->cpu_arch), "aarch64"); +#elif defined(__arm__) + snprintf(info->cpu_arch, sizeof(info->cpu_arch), "arm"); +#endif + +#ifdef CONFIG_X86 + raid_cpu_info(info->cpu_vendor, &info->cpu_family, &info->cpu_model); + + uint32_t reg[4]; + raid_cpuid(1, 0, reg); + info->cpu_stepping = reg[0] & 0xF; + + /* brand string from extended CPUID leaves 0x80000002..0x80000004 */ + raid_cpuid(0x80000000, 0, reg); + if (reg[0] >= 0x80000004) { + raid_cpuid(0x80000002, 0, reg); + memcpy(info->cpu_brand + 0, reg, sizeof(reg)); + raid_cpuid(0x80000003, 0, reg); + memcpy(info->cpu_brand + 16, reg, sizeof(reg)); + raid_cpuid(0x80000004, 0, reg); + memcpy(info->cpu_brand + 32, reg, sizeof(reg)); + info->cpu_brand[48] = 0; + strtrim(info->cpu_brand); + } + + /* base and maximum frequency from CPUID leaf 0x16 */ + raid_cpuid(0, 0, reg); + if (reg[0] >= 0x16) { + raid_cpuid(0x16, 0, reg); + unsigned base_mhz = reg[0] & 0xFFFF; + unsigned max_mhz = reg[1] & 0xFFFF; + if (base_mhz > 0 && max_mhz > 0 && base_mhz != max_mhz) { + char base_str[32], max_str[32]; + format_freq(base_str, sizeof(base_str), base_mhz); + format_freq(max_str, sizeof(max_str), max_mhz); + snprintf(info->cpu_clock, sizeof(info->cpu_clock), "%s (max %s)", base_str, max_str); + } else if (base_mhz > 0) { + format_freq(info->cpu_clock, sizeof(info->cpu_clock), base_mhz); + } else if (max_mhz > 0) { + format_freq(info->cpu_clock, sizeof(info->cpu_clock), max_mhz); + } + } + + /* hypervisor detection */ + raid_cpuid(1, 0, reg); + if (reg[2] & (1U << 31)) { + raid_cpuid(0x40000000, 0, reg); + memcpy(info->hypervisor + 0, ®[1], 4); + memcpy(info->hypervisor + 4, ®[2], 4); + memcpy(info->hypervisor + 8, ®[3], 4); + info->hypervisor[12] = 0; + strtrim(info->hypervisor); + } +#endif + + MEMORYSTATUSEX ms; + memset(&ms, 0, sizeof(ms)); + ms.dwLength = sizeof(ms); + if (GlobalMemoryStatusEx(&ms)) { + info->memory_total_bytes = ms.ullTotalPhys; + } + + SYSTEM_INFO si; + GetSystemInfo(&si); + info->memory_page_size = si.dwPageSize; + info->cpu_cores_logical = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS); + + /* Query processor topology and cache hierarchy via GetLogicalProcessorInformationEx */ + DWORD len = 0; + GetLogicalProcessorInformationEx(RelationAll, 0, &len); + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER && len > 0) { + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* buf = malloc_nofail(len); + if (GetLogicalProcessorInformationEx(RelationAll, buf, &len)) { + DWORD offset = 0; + while (offset < len) { + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* info_ex = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)((char*)buf + offset); + if (info_ex->Relationship == RelationProcessorCore) { + ++info->cpu_cores_physical; + } else if (info_ex->Relationship == RelationCache) { + CACHE_RELATIONSHIP* cache = &info_ex->Cache; + /* + * Select the coherent cache hierarchy associated with the primary + * core (Core 0). This prevents mixing cache levels from different + * core types on hybrid architectures (e.g. L1 from P-core, L2 from E-cluster). + */ + int matches_core0 = 0; + if (cache->GroupCount == 0) { + if (cache->GroupMask.Group == 0 && (cache->GroupMask.Mask & 1) != 0) + matches_core0 = 1; + } else { + for (WORD g = 0; g < cache->GroupCount; ++g) { + if (cache->GroupMasks[g].Group == 0 && (cache->GroupMasks[g].Mask & 1) != 0) { + matches_core0 = 1; + break; + } + } + } + + if (matches_core0) { + if (cache->Level == 1) { + if (cache->Type == CacheData || cache->Type == CacheUnified) + info->cache_l1_data = cache->CacheSize; + else if (cache->Type == CacheInstruction) + info->cache_l1_instruction = cache->CacheSize; + } else if (cache->Level == 2) { + info->cache_l2 = cache->CacheSize; + } else if (cache->Level == 3) { + info->cache_l3 = cache->CacheSize; + } + } + } + if (info_ex->Size == 0) + break; + offset += info_ex->Size; + } + } + free(buf); + } + + /* OS version via RtlGetVersion */ + typedef LONG (WINAPI* RtlGetVersionPtr)(LPOSVERSIONINFOW); + HMODULE ntdll = GetModuleHandleA("ntdll.dll"); + if (ntdll) { + RtlGetVersionPtr pRtlGetVersion = (RtlGetVersionPtr)(void*)GetProcAddress(ntdll, "RtlGetVersion"); + if (pRtlGetVersion) { + OSVERSIONINFOW vi; + memset(&vi, 0, sizeof(vi)); + vi.dwOSVersionInfoSize = sizeof(vi); + if (pRtlGetVersion(&vi) == 0) { + snprintf(info->os_name, sizeof(info->os_name), "Windows %lu.%lu (Build %lu)", + (unsigned long)vi.dwMajorVersion, (unsigned long)vi.dwMinorVersion, (unsigned long)vi.dwBuildNumber); + } + } + } +} + #endif diff --git a/cmdline/speed.c b/cmdline/speed.c index 7f7a933e..b18c2b7a 100644 --- a/cmdline/speed.c +++ b/cmdline/speed.c @@ -2278,14 +2278,16 @@ void speed_rec(int nd, void** v, int size, int delta, int period) void speed_affinity(void) { #if HAVE_LINUX_DEVICE - int num_cpus = sysconf(_SC_NPROCESSORS_CONF); + int num_cpus = sysconf(_SC_NPROCESSORS_ONLN); + if (num_cpus <= 0) + num_cpus = sysconf(_SC_NPROCESSORS_CONF); if (num_cpus <= 0) return; printf("Machine has %d cores\n", num_cpus); int cpu = os_get_optimal_cpu(); - if (cpu < 0) + if (cpu < 0 || cpu >= CPU_SETSIZE) return; cpu_set_t mask; @@ -2293,7 +2295,7 @@ void speed_affinity(void) CPU_SET(cpu, &mask); if (sched_setaffinity(0, sizeof(cpu_set_t), &mask) == 0) { - printf("Running on core %d\n", cpu); + printf("Running on cpu %d\n", cpu); } #endif } @@ -2304,7 +2306,7 @@ static int check_cpu_feature(const char* name) int value = 0; size_t len = sizeof(value); - if (sysctlbyname(name, &value, &len, NULL, 0) == 0) { + if (sysctlbyname(name, &value, &len, 0, 0) == 0) { return value; } @@ -2319,24 +2321,26 @@ static const char* decode_cpufamily(uint32_t family) case 0x462504d2 : return "Lightning/Thunder (A13)"; case 0x1b588bb3 : return "Firestorm/Icestorm (M1/A14)"; case 0xda33d83d : return "Blizzard/Avalanche (M2/A15)"; - case 0x8765edea : return "Everest/Sawtooth (M3/A16)"; - case 0xfa33415e : return "Ibiza (M4/A17)"; + case 0x8765edea : return "Everest/Sawtooth (A16)"; + case 0xfa33415e : return "Ibiza (M3)"; + case 0x72015832 : return "Palma (M3 Pro)"; + case 0x17d5e360 : return "Lobos (M3 Max)"; + case 0x2876f5b5 : return "Coll (A17 Pro)"; + case 0x6f5129ac : return "Donan (M4)"; + case 0x1774f74e : return "Brava (M4 Pro)"; + case 0x524c529b : return "Hidra (M4 Max)"; + case 0x7843b821 : return "Tahiti (A18)"; + case 0x6f64249a : return "Tupai (A18 Pro)"; case 0 : return "Unavailable"; default : return "Unknown"; } } -void print_apple(void) +void print_apple(const struct machineinfo_struct* info) { - char brand[128] = "Unknown Apple Silicon"; - size_t len = sizeof(brand); - if (sysctlbyname("machdep.cpu.brand_string", &brand, &len, NULL, 0) != 0) { - sysctlbyname("hw.model", &brand, &len, NULL, 0); - } - uint32_t family = 0; - len = sizeof(uint32_t); - sysctlbyname("hw.cpufamily", &family, &len, NULL, 0); + size_t len = sizeof(uint32_t); + sysctlbyname("hw.cpufamily", &family, &len, 0, 0); int has_aes = check_cpu_feature("hw.optional.arm.FEAT_AES"); int has_dotprod = check_cpu_feature("hw.optional.arm.FEAT_DotProd"); @@ -2344,44 +2348,27 @@ void print_apple(void) int has_sve = check_cpu_feature("hw.optional.arm.FEAT_SVE"); int has_sme = check_cpu_feature("hw.optional.arm.FEAT_SME"); + const char* name = info->cpu_brand[0] ? info->cpu_brand : (info->system_model[0] ? info->system_model : "Apple Silicon"); + printf("CPU %s, family %s (0x%08x), flags%s%s%s%s%s\n", - brand, decode_cpufamily(family), family, + name, decode_cpufamily(family), family, has_aes ? " aes" : "", has_dotprod ? " dotprod" : "", has_sha3 ? " sha3" : "", has_sve ? " sve" : "", has_sme ? " sme" : "" ); - - unsigned long l1_d_bytes = 0; - unsigned long l2_bytes = 0; - unsigned long l3_bytes = 0; - - len = sizeof(unsigned long); - sysctlbyname("hw.l1dcachesize", &l1_d_bytes, &len, 0, 0); - len = sizeof(unsigned long); - sysctlbyname("hw.l2cachesize", &l2_bytes, &len, 0, 0); - len = sizeof(unsigned long); - sysctlbyname("hw.l3cachesize", &l3_bytes, &len, 0, 0); - - printf("Cache L1 Data %lu KB, L2 %lu KB", l1_d_bytes / 1024, l2_bytes / 1024); - if (l3_bytes > 0) { - printf(", L3 %lu KB", l3_bytes / 1024); - } - printf("\n"); } #endif #ifdef CONFIG_X86 -void print_intel(void) +void print_intel(const struct machineinfo_struct* info) { - char vendor[CPU_VENDOR_MAX]; - unsigned family; - unsigned model; - - raid_cpu_info(vendor, &family, &model); - - printf("CPU %s, family %u, model %u (0x%x), flags%s%s%s%s%s%s%s%s%s%s\n", vendor, family, model, model, + printf("CPU %s, family %u (0x%x), model %u (0x%x), stepping %u, flags%s%s%s%s%s%s%s%s%s%s\n", + info->cpu_vendor[0] ? info->cpu_vendor : "x86", + info->cpu_family, info->cpu_family, + info->cpu_model, info->cpu_model, + info->cpu_stepping, raid_cpu_has_sse2() ? " sse2" : "", raid_cpu_has_ssse3() ? " ssse3" : "", raid_cpu_has_crc32() ? " crc32" : "", @@ -2393,18 +2380,6 @@ void print_intel(void) raid_cpu_has_slow_extendedreg() ? " slowext" : "", raid_cpu_has_avx512bw() && raid_cpu_has_slow_avx512() ? " slowavx512" : "" ); - -#ifdef __linux__ - long l1_d_bytes = sysconf(_SC_LEVEL1_DCACHE_SIZE); - long l2_bytes = sysconf(_SC_LEVEL2_CACHE_SIZE); - long l3_bytes = sysconf(_SC_LEVEL3_CACHE_SIZE); - - printf("Cache L1 Data %lu KB, L2 %lu KB", l1_d_bytes / 1024, l2_bytes / 1024); - if (l3_bytes > 0) { - printf(", L3 %lu KB", l3_bytes / 1024); - } - printf("\n"); -#endif } #endif @@ -2415,6 +2390,9 @@ void speed(int period, int nd, int size) int nv; void* v_alloc; void** v; + struct machineinfo_struct minfo; + + machineinfo(&minfo); if (nd < 0) nd = 8; /* default */ @@ -2448,20 +2426,20 @@ void speed(int period, int nd, int size) #endif #ifdef CONFIG_X86 - print_intel(); + print_intel(&minfo); #elif defined(__aarch64__) && defined(__APPLE__) - print_apple(); + print_apple(&minfo); #elif defined(__aarch64__) #if defined(CONFIG_NEON) - printf("CPU 64-bit ARM (AArch64), flags neon\n"); + printf("CPU %s, flags neon\n", minfo.cpu_brand[0] ? minfo.cpu_brand : "64-bit ARM (AArch64)"); #else - printf("CPU 64-bit ARM (AArch64)\n"); + printf("CPU %s\n", minfo.cpu_brand[0] ? minfo.cpu_brand : "64-bit ARM (AArch64)"); #endif #elif defined(__arm__) #if defined(CONFIG_NEON32) - printf("CPU 32-bit ARM, flags neon\n"); + printf("CPU %s, flags neon\n", minfo.cpu_brand[0] ? minfo.cpu_brand : "32-bit ARM"); #else - printf("CPU 32-bit ARM\n"); + printf("CPU %s\n", minfo.cpu_brand[0] ? minfo.cpu_brand : "32-bit ARM"); #endif #elif defined(__powerpc64__) printf("CPU 64-bit PowerPC\n"); @@ -2474,12 +2452,60 @@ void speed(int period, int nd, int size) #else printf("CPU of unknown architecture\n"); #endif + + if (minfo.cpu_brand[0] != 0) { + const char* p = minfo.cpu_brand; + int in_space = 0; + + printf("Model "); + while (*p != 0) { + if (*p == ' ') { + if (!in_space) { + putchar(' '); + in_space = 1; + } + } else { + putchar(*p); + in_space = 0; + } + ++p; + } + printf("\n"); + } + + if (minfo.cpu_clock[0] != 0) { + printf("Clock %s\n", minfo.cpu_clock); + } + + if (minfo.cache_l1_data > 0 || minfo.cache_l2 > 0) { + printf("Cache L1 Data %" PRIu64 " KB, L2 %" PRIu64 " KB", + minfo.cache_l1_data / 1024, minfo.cache_l2 / 1024); + if (minfo.cache_l3 > 0) { + printf(", L3 %" PRIu64 " KB", minfo.cache_l3 / 1024); + } + printf("\n"); + } + speed_affinity(); + + if (minfo.memory_total_bytes > 0) { + printf("Memory %" PRIu64 " MB", minfo.memory_total_bytes / (1024 * 1024)); #if WORDS_BIGENDIAN - printf("Memory is big-endian %d-bit\n", (int)sizeof(void*) * 8); + printf(", big-endian %d-bit", (int)sizeof(void*) * 8); #else - printf("Memory is little-endian %d-bit\n", (int)sizeof(void*) * 8); + printf(", little-endian %d-bit", (int)sizeof(void*) * 8); #endif + if (minfo.memory_page_size > 0) + printf(", page %u KB", minfo.memory_page_size / 1024); + printf("\n"); + } else { +#if WORDS_BIGENDIAN + printf("Memory is big-endian %d-bit\n", (int)sizeof(void*) * 8); +#else + printf("Memory is little-endian %d-bit\n", (int)sizeof(void*) * 8); +#endif + } + #if defined(__SIZEOF_INT128__) printf("128-bit integers are supported\n"); #else @@ -2496,6 +2522,20 @@ void speed(int period, int nd, int size) printf("Does not support nanosecond timestamps\n"); #endif + if (minfo.os_distribution[0] != 0 && minfo.os_name[0] != 0) { + printf("Host %s (%s)\n", minfo.os_distribution, minfo.os_name); + } else if (minfo.os_name[0] != 0) { + printf("Host %s\n", minfo.os_name); + } + + if (minfo.system_model[0] != 0 || minfo.hypervisor[0] != 0) { + printf("System %s%s%s%s\n", + minfo.system_model[0] ? minfo.system_model : "Generic", + minfo.hypervisor[0] ? " [" : "", + minfo.hypervisor[0] ? minfo.hypervisor : "", + minfo.hypervisor[0] ? "]" : ""); + } + printf("\n"); printf("Speed test using %u data buffers of %u bytes, for a total of %u KiB.\n", nd, size, nd * size / KIBI); diff --git a/cmdline/unixapp.c b/cmdline/unixapp.c index be880dd1..405f02df 100644 --- a/cmdline/unixapp.c +++ b/cmdline/unixapp.c @@ -7,6 +7,7 @@ #include "app.h" #include "support.h" +#include "raid/cpu.h" /****************************************************************************/ /* global */ @@ -502,70 +503,106 @@ static long long syslong(const char* path) } /* - * Parse a sysfs hexadecimal bitmask string (e.g. thread_siblings). + * Parse Linux cpulist format (e.g. "0-3,5,8-11") into an array of CPU IDs. * - * Linux sysfs outputs bitmasks in big-endian 32-bit hex word groups - * (highest CPUs on the left, lowest CPUs on the right). - * - * Left-shifting digits into a 64-bit unsigned long long operates modulo 2^64, - * discarding any high-order hex words (CPUs >= 64) and leaving bits 0..63 - * containing the exact mask for CPUs 0..63. Since CPUS_MAX is 64, this mask - * is always accurate for all evaluated CPUs even on systems with > 64 CPUs. + * Returns the total number of CPUs in the list. Stores up to max_cpus entries. */ -static unsigned long long syshex(const char* path) +static unsigned parse_cpulist(const char* str, unsigned* cpus, unsigned max_cpus) { - char buf[1152]; /* enough for 1024 CPUs */ - if (sysattr(path, buf, sizeof(buf)) != 0) - return 0; + const char* p = str; + unsigned count = 0; + + while (*p != 0) { + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') + ++p; + if (*p == 0) + break; + + char* end; + unsigned long start = strtoul(p, &end, 10); + if (end == p) + break; + + unsigned long stop = start; + p = end; + if (*p == '-') { + ++p; + stop = strtoul(p, &end, 10); + if (end == p) + break; + p = end; + } + + if (stop < start || stop >= CPU_SETSIZE) + break; + + for (unsigned long i = start;; ++i) { + if (cpus != 0 && count < max_cpus) + cpus[count] = (unsigned)i; + ++count; + + if (i == stop) + break; + } - unsigned long long v = 0; - for (char* p = buf; *p != 0 && *p != '\n'; ++p) { if (*p == ',') - continue; - - v <<= 4; - if (*p >= '0' && *p <= '9') - v |= *p - '0'; - else if (*p >= 'a' && *p <= 'f') - v |= *p - 'a' + 10; - else if (*p >= 'A' && *p <= 'F') - v |= *p - 'A' + 10; - else - break; /* hit unexpected character */ + ++p; } - return v; + return count; } -static int cpu_is_online(int i) +/* + * Read online CPU IDs from /sys/devices/system/cpu/online. + * + * Falls back to consecutive 0..N-1 IDs if sysfs is unavailable. + * Returns the total number of online CPUs. Stores up to max_cpus entries in cpus. + */ +static unsigned read_online_cpus(unsigned* cpus, unsigned max_cpus) { - char path[256]; - snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/online", i); - long long v = syslong(path); - if (v < 0) - return 1; /* cpu0 has no "online" knob — always on */ - return v; + char buf[1024]; + if (sysattr("/sys/devices/system/cpu/online", buf, sizeof(buf)) == 0) { + unsigned count = parse_cpulist(buf, cpus, max_cpus); + if (count > 0) + return count; + } + + /* fallback: consecutive IDs from 0 */ + long n = sysconf(_SC_NPROCESSORS_ONLN); + if (n <= 0) + n = sysconf(_SC_NPROCESSORS_CONF); + if (n <= 0) + n = 1; + + unsigned count = (unsigned)n; + unsigned store = count < max_cpus ? count : max_cpus; + for (unsigned i = 0; i < store; ++i) { + if (cpus != 0) + cpus[i] = i; + } + return count; } /* * Returns 1 if the specified cpu is the lowest-numbered logical CPU in its * physical core's SMT sibling set. - * - * thread_siblings is a hex bitmask where bit N = logical cpu N. - * The lowest set bit identifies the primary sibling. - * Safe for up to 64 logical CPUs (fits in unsigned long). */ -static int cpu_is_primary_sibling(int i) +static int cpu_is_primary_sibling(unsigned i) { char path[256]; - snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/topology/thread_siblings", i); - unsigned long long mask = syshex(path); - if (mask == 0) /* assume primary */ - return 1; - return __builtin_ctzll(mask) == i; -} + char buf[256]; -#define CPUS_MAX 64 + snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%u/topology/thread_siblings_list", i); + if (sysattr(path, buf, sizeof(buf)) != 0) + return 1; /* assume primary */ + + unsigned siblings[16]; + unsigned count = parse_cpulist(buf, siblings, sizeof(siblings) / sizeof(siblings[0])); + if (count == 0) + return 1; + + return i == siblings[0]; +} /** * os_get_optimal_cpu() – returns the logical CPU index of the "fastest" @@ -578,27 +615,25 @@ static int cpu_is_primary_sibling(int i) int os_get_optimal_cpu(void) { char path[256]; - - int num_cpus = sysconf(_SC_NPROCESSORS_CONF); - if (num_cpus <= 0) + unsigned cpus[CPU_SETSIZE]; + unsigned total_cpus = read_online_cpus(cpus, CPU_SETSIZE); + unsigned num_cpus = total_cpus < CPU_SETSIZE ? total_cpus : CPU_SETSIZE; + if (num_cpus == 0) return -1; - if (num_cpus > CPUS_MAX) - num_cpus = CPUS_MAX; /* ACPI CPPC: highest_perf with nominal_perf tiebreaker */ long long max_highest = -1; long long max_nominal = -1; int best_cppc = -1; - for (int i = 0; i < num_cpus; ++i) { - if (!cpu_is_online(i)) - continue; + for (unsigned k = 0; k < num_cpus; ++k) { + unsigned i = cpus[k]; - snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/acpi_cppc/highest_perf", i); + snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%u/acpi_cppc/highest_perf", i); long long hp = syslong(path); if (hp < 0) continue; - snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/acpi_cppc/nominal_perf", i); + snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%u/acpi_cppc/nominal_perf", i); long long np = syslong(path); /* np may be missing */ @@ -620,11 +655,10 @@ int os_get_optimal_cpu(void) /* find the global maximum frequency among online CPUs */ long long global_max_freq = -1; - for (int i = 0; i < num_cpus; ++i) { - if (!cpu_is_online(i)) - continue; + for (unsigned k = 0; k < num_cpus; ++k) { + unsigned i = cpus[k]; - snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", i); + snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%u/cpufreq/cpuinfo_max_freq", i); long long freq = syslong(path); if (freq < 0) continue; @@ -638,12 +672,11 @@ int os_get_optimal_cpu(void) int best_any = -1; /* any matching CPU, not CPU 0 */ int best_c0 = -1; /* CPU 0 fallback (always a primary sibling) */ - for (int i = 0; i < num_cpus; ++i) { - if (!cpu_is_online(i)) - continue; + for (unsigned k = 0; k < num_cpus; ++k) { + unsigned i = cpus[k]; if (global_max_freq != -1) { - snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", i); + snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%u/cpufreq/cpuinfo_max_freq", i); if (syslong(path) != global_max_freq) continue; } @@ -4224,5 +4257,386 @@ void app_default_conf(char* conf, size_t conf_size, const char* argv0) } } +#ifdef __linux__ +static void read_os_release(char* distro, size_t distro_size) +{ + distro[0] = 0; + + char buf[1024]; + ssize_t len = sysread("/etc/os-release", buf, sizeof(buf) - 1); + if (len <= 0) + len = sysread("/usr/lib/os-release", buf, sizeof(buf) - 1); + if (len <= 0) + return; + buf[len] = 0; + + char* p = buf; + while ((p = strstr(p, "PRETTY_NAME=")) != 0) { + if (p == buf || *(p - 1) == '\n') + break; + ++p; + } + if (!p) + return; + + p += 12; + char q = 0; + if (*p == '"' || *p == '\'') { + q = *p++; + } + + size_t d = 0; + while (*p != 0 && *p != '\n' && (q != 0 ? *p != q : 1)) { + if (*p == '\\' && *(p + 1) != 0 && *(p + 1) != '\n') { + ++p; + } + if (d + 1 < distro_size) { + distro[d++] = *p; + } + ++p; + } + distro[d] = 0; + strtrim(distro); +} +#endif + +/****************************************************************************/ +/* machine */ + +#if defined(__linux__) || defined(__APPLE__) +static void format_freq(char* buf, size_t size, unsigned mhz) +{ + if (mhz >= 1000) + snprintf(buf, size, "%.2f GHz", mhz / 1000.0); + else + snprintf(buf, size, "%u MHz", mhz); +} +#endif + +#ifdef __APPLE__ +static int sysctl_string(const char* name, char* buf, size_t size) +{ + size_t len = size; + + if (sysctlbyname(name, buf, &len, 0, 0) != 0) { + buf[0] = 0; + return -1; + } + + buf[size - 1] = 0; + strtrim(buf); + return 0; +} +#endif + +void machineinfo(struct machineinfo_struct* info) +{ + memset(info, 0, sizeof(*info)); + +#if defined(__x86_64__) + snprintf(info->cpu_arch, sizeof(info->cpu_arch), "x86_64"); +#elif defined(__i386__) + snprintf(info->cpu_arch, sizeof(info->cpu_arch), "x86_32"); +#elif defined(__aarch64__) + snprintf(info->cpu_arch, sizeof(info->cpu_arch), "aarch64"); +#elif defined(__arm__) + snprintf(info->cpu_arch, sizeof(info->cpu_arch), "arm"); +#elif defined(__powerpc64__) + snprintf(info->cpu_arch, sizeof(info->cpu_arch), "ppc64"); +#elif defined(__powerpc__) + snprintf(info->cpu_arch, sizeof(info->cpu_arch), "ppc"); +#elif defined(__riscv) + snprintf(info->cpu_arch, sizeof(info->cpu_arch), "riscv"); +#elif defined(__s390x__) + snprintf(info->cpu_arch, sizeof(info->cpu_arch), "s390x"); +#endif + +#ifdef CONFIG_X86 + raid_cpu_info(info->cpu_vendor, &info->cpu_family, &info->cpu_model); + + uint32_t reg[4]; + raid_cpuid(1, 0, reg); + info->cpu_stepping = reg[0] & 0xF; + + /* brand string from extended CPUID leaves 0x80000002..0x80000004 */ + raid_cpuid(0x80000000, 0, reg); + if (reg[0] >= 0x80000004) { + raid_cpuid(0x80000002, 0, reg); + memcpy(info->cpu_brand + 0, reg, sizeof(reg)); + raid_cpuid(0x80000003, 0, reg); + memcpy(info->cpu_brand + 16, reg, sizeof(reg)); + raid_cpuid(0x80000004, 0, reg); + memcpy(info->cpu_brand + 32, reg, sizeof(reg)); + info->cpu_brand[48] = 0; + strtrim(info->cpu_brand); + } + + /* hypervisor detection */ + raid_cpuid(1, 0, reg); + if (reg[2] & (1U << 31)) { + raid_cpuid(0x40000000, 0, reg); + memcpy(info->hypervisor + 0, ®[1], 4); + memcpy(info->hypervisor + 4, ®[2], 4); + memcpy(info->hypervisor + 8, ®[3], 4); + info->hypervisor[12] = 0; + strtrim(info->hypervisor); + } +#endif + + struct utsname un; + if (uname(&un) == 0) { + snprintf(info->os_name, sizeof(info->os_name), "%s %s", un.sysname, un.release); + } + + long ps = sysconf(_SC_PAGESIZE); + if (ps > 0) + info->memory_page_size = (uint32_t)ps; + +#ifdef _SC_NPROCESSORS_ONLN + long num_cpus = sysconf(_SC_NPROCESSORS_ONLN); + if (num_cpus <= 0) + num_cpus = sysconf(_SC_NPROCESSORS_CONF); + if (num_cpus > 0) + info->cpu_cores_logical = (unsigned)num_cpus; +#elif defined(_SC_NPROCESSORS_CONF) + long num_cpus = sysconf(_SC_NPROCESSORS_CONF); + if (num_cpus > 0) + info->cpu_cores_logical = (unsigned)num_cpus; +#endif + +#ifdef _SC_LEVEL1_DCACHE_SIZE + long l1_d = sysconf(_SC_LEVEL1_DCACHE_SIZE); + if (l1_d > 0) + info->cache_l1_data = (uint64_t)l1_d; +#endif +#ifdef _SC_LEVEL1_ICACHE_SIZE + long l1_i = sysconf(_SC_LEVEL1_ICACHE_SIZE); + if (l1_i > 0) + info->cache_l1_instruction = (uint64_t)l1_i; +#endif +#ifdef _SC_LEVEL2_CACHE_SIZE + long l2 = sysconf(_SC_LEVEL2_CACHE_SIZE); + if (l2 > 0) + info->cache_l2 = (uint64_t)l2; +#endif +#ifdef _SC_LEVEL3_CACHE_SIZE + long l3 = sysconf(_SC_LEVEL3_CACHE_SIZE); + if (l3 > 0) + info->cache_l3 = (uint64_t)l3; +#endif + +#ifdef __linux__ + struct sysinfo si; + if (sysinfo(&si) == 0) { + info->memory_total_bytes = (uint64_t)si.totalram * si.mem_unit; + } + + /* + * Group CPUs by distinct maximum frequency rather than relying on + * consecutive ID ranges. This handles non-consecutive ID layouts cleanly + * and avoids artificial cluster fragmentation. + */ + struct { + unsigned mhz; + unsigned count; + unsigned cpu; + } clusters[8]; + unsigned cluster_count = 0; + + unsigned cpus[CPU_SETSIZE]; + unsigned online_total = read_online_cpus(cpus, sizeof(cpus) / sizeof(cpus[0])); + unsigned scan_cpus = online_total < sizeof(cpus) / sizeof(cpus[0]) ? online_total : sizeof(cpus) / sizeof(cpus[0]); + + if (scan_cpus > 0) { + for (unsigned k = 0; k < scan_cpus; ++k) { + unsigned i = cpus[k]; + char path[PATH_MAX]; + snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%u/cpufreq/cpuinfo_max_freq", i); + long long khz = syslong(path); + if (khz > 0) { + unsigned mhz = (unsigned)(khz / 1000); + int found = 0; + for (unsigned c = 0; c < cluster_count; ++c) { + if (clusters[c].mhz == mhz) { + ++clusters[c].count; + found = 1; + break; + } + } + if (!found && cluster_count < sizeof(clusters) / sizeof(clusters[0])) { + clusters[cluster_count].mhz = mhz; + clusters[cluster_count].count = 1; + clusters[cluster_count].cpu = i; + ++cluster_count; + } + } + } + + /* sort clusters by frequency descending (fastest cores first) */ + for (unsigned i = 0; i < cluster_count; ++i) { + for (unsigned j = i + 1; j < cluster_count; ++j) { + if (clusters[j].mhz > clusters[i].mhz) { + unsigned tmp_mhz = clusters[i].mhz; + unsigned tmp_count = clusters[i].count; + unsigned tmp_cpu = clusters[i].cpu; + clusters[i].mhz = clusters[j].mhz; + clusters[i].count = clusters[j].count; + clusters[i].cpu = clusters[j].cpu; + clusters[j].mhz = tmp_mhz; + clusters[j].count = tmp_count; + clusters[j].cpu = tmp_cpu; + } + } + } + } + + if (cluster_count > 1) { + info->cpu_clock[0] = 0; + size_t pos = 0; + for (unsigned c = 0; c < cluster_count; ++c) { + char freq_str[32]; + format_freq(freq_str, sizeof(freq_str), clusters[c].mhz); + char part[64]; + if (clusters[c].count == 1) + snprintf(part, sizeof(part), "%s%s (1 cpu)", c > 0 ? ", " : "", freq_str); + else + snprintf(part, sizeof(part), "%s%s (%u cpus)", c > 0 ? ", " : "", freq_str, clusters[c].count); + if (pos < sizeof(info->cpu_clock)) { + int n = snprintf(info->cpu_clock + pos, sizeof(info->cpu_clock) - pos, "%s", part); + if (n > 0) + pos += (size_t)n; + } + } + } else if (cluster_count == 1) { + char path[PATH_MAX]; + snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%u/cpufreq/base_frequency", clusters[0].cpu); + long long base_khz = syslong(path); + unsigned base_mhz = base_khz > 0 ? (unsigned)(base_khz / 1000) : 0; + unsigned max_mhz = clusters[0].mhz; + char max_str[32]; + char base_str[32]; + format_freq(max_str, sizeof(max_str), max_mhz); + if (base_mhz > 0 && base_mhz != max_mhz) { + format_freq(base_str, sizeof(base_str), base_mhz); + snprintf(info->cpu_clock, sizeof(info->cpu_clock), "%s (max %s)", base_str, max_str); + } else { + snprintf(info->cpu_clock, sizeof(info->cpu_clock), "%s", max_str); + } + } + + /* Fallback if sysfs cpufreq is unavailable (e.g. Cloud VM) */ + if (info->cpu_clock[0] == 0) { +#ifdef CONFIG_X86 + raid_cpuid(0, 0, reg); + if (reg[0] >= 0x16) { + raid_cpuid(0x16, 0, reg); + unsigned base_mhz = reg[0] & 0xFFFF; + unsigned max_mhz = reg[1] & 0xFFFF; + if (base_mhz > 0 && max_mhz > 0 && base_mhz != max_mhz) { + char base_str[32], max_str[32]; + format_freq(base_str, sizeof(base_str), base_mhz); + format_freq(max_str, sizeof(max_str), max_mhz); + snprintf(info->cpu_clock, sizeof(info->cpu_clock), "%s (max %s)", base_str, max_str); + } else if (base_mhz > 0) { + format_freq(info->cpu_clock, sizeof(info->cpu_clock), base_mhz); + } else if (max_mhz > 0) { + format_freq(info->cpu_clock, sizeof(info->cpu_clock), max_mhz); + } + } +#endif + } + + if (info->cpu_clock[0] == 0) { + char buf[1024]; + ssize_t len = sysread("/proc/cpuinfo", buf, sizeof(buf) - 1); + if (len > 0) { + buf[len] = 0; + char* p = strstr(buf, "cpu MHz"); + if (p) { + p = strchr(p, ':'); + if (p) { + double mhz = strtod(p + 1, 0); + if (mhz > 0) + format_freq(info->cpu_clock, sizeof(info->cpu_clock), (unsigned)(mhz + 0.5)); + } + } + } + } + + char model_buf[128]; + if (sysattr("/sys/class/dmi/id/product_name", model_buf, sizeof(model_buf)) == 0 && model_buf[0]) { + char vendor_buf[128]; + if (sysattr("/sys/class/dmi/id/sys_vendor", vendor_buf, sizeof(vendor_buf)) == 0 && vendor_buf[0]) { + snprintf(info->system_model, sizeof(info->system_model), "%s %s", vendor_buf, model_buf); + } else { + snprintf(info->system_model, sizeof(info->system_model), "%s", model_buf); + } + } else if (sysattr("/proc/device-tree/model", model_buf, sizeof(model_buf)) == 0 && model_buf[0]) { + snprintf(info->system_model, sizeof(info->system_model), "%s", model_buf); + } + + read_os_release(info->os_distribution, sizeof(info->os_distribution)); +#endif + +#ifdef __APPLE__ + size_t len; + + if (info->cpu_brand[0] == 0) + sysctl_string("machdep.cpu.brand_string", info->cpu_brand, sizeof(info->cpu_brand)); + + sysctl_string("hw.model", info->system_model, sizeof(info->system_model)); + + unsigned val = 0; + len = sizeof(val); + if (sysctlbyname("hw.physicalcpu", &val, &len, 0, 0) == 0) + info->cpu_cores_physical = val; + + len = sizeof(val); + if (sysctlbyname("hw.logicalcpu", &val, &len, 0, 0) == 0) + info->cpu_cores_logical = val; + + uint64_t hz = 0; + len = sizeof(hz); + if (sysctlbyname("hw.cpufrequency", &hz, &len, 0, 0) == 0 && hz > 0) + format_freq(info->cpu_clock, sizeof(info->cpu_clock), (unsigned)(hz / 1000000)); + + unsigned long clen = 0; + len = sizeof(clen); + if (sysctlbyname("hw.l1dcachesize", &clen, &len, 0, 0) == 0) + info->cache_l1_data = clen; + + len = sizeof(clen); + if (sysctlbyname("hw.l1icachesize", &clen, &len, 0, 0) == 0) + info->cache_l1_instruction = clen; + + len = sizeof(clen); + if (sysctlbyname("hw.l2cachesize", &clen, &len, 0, 0) == 0) + info->cache_l2 = clen; + + len = sizeof(clen); + if (sysctlbyname("hw.l3cachesize", &clen, &len, 0, 0) == 0) + info->cache_l3 = clen; + + uint64_t mem = 0; + len = sizeof(mem); + if (sysctlbyname("hw.memsize", &mem, &len, 0, 0) == 0) + info->memory_total_bytes = mem; + + char osver[32]; + sysctl_string("kern.osproductversion", osver, sizeof(osver)); + + char osrel[32]; + sysctl_string("kern.osrelease", osrel, sizeof(osrel)); + + if (osver[0] && osrel[0]) { + snprintf(info->os_name, sizeof(info->os_name), "macOS %s (Darwin %s)", osver, osrel); + } else if (osver[0]) { + snprintf(info->os_name, sizeof(info->os_name), "macOS %s", osver); + } else if (osrel[0]) { + snprintf(info->os_name, sizeof(info->os_name), "Darwin %s", osrel); + } +#endif +} + #endif