diff --git a/Makefile b/Makefile index 29b4bda..daa084d 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,7 @@ DRIVER := src/main.c ASM_ARM64 := src/fossmark.S ASM_AMD64 := src/fossmark_x86_64.S SRC_PPC32 := src/fossmark_ppc32.c +ASM_PPC32 := src/fossmark_ppc32_ext.S # ---- host detection: normalise `uname -m` to our arch names ---- HOST_ARCH := $(shell uname -m) @@ -55,7 +56,7 @@ else ifneq (,$(filter x86_64 amd64,$(HOST_ARCH))) HOST_KERNEL := $(ASM_AMD64) else ifneq (,$(filter ppc powerpc ppc32 powerpc32,$(HOST_ARCH))) HOST_ARCHNAME := ppc32be - HOST_KERNEL := $(SRC_PPC32) + HOST_KERNEL := $(SRC_PPC32) $(ASM_PPC32) else HOST_ARCHNAME := $(HOST_ARCH) $(error unsupported host architecture '$(HOST_ARCH)') @@ -121,8 +122,8 @@ $(DIST)/fossmark-linux-amd64: $(DRIVER) $(ASM_AMD64) | $(DIST) $(CC_AMD64) $(CFLAGS) $(PTHREAD) -o $@ $(DRIVER) $(ASM_AMD64) $(LDLIBS) @echo "built $@" -$(DIST)/fossmark-linux-ppc32be: $(DRIVER) $(SRC_PPC32) | $(DIST) - $(CC_PPC32BE) $(CFLAGS) $(PTHREAD) -o $@ $(DRIVER) $(SRC_PPC32) $(LDLIBS) +$(DIST)/fossmark-linux-ppc32be: $(DRIVER) $(SRC_PPC32) $(ASM_PPC32) | $(DIST) + $(CC_PPC32BE) $(CFLAGS) $(PTHREAD) -o $@ $(DRIVER) $(SRC_PPC32) $(ASM_PPC32) $(LDLIBS) @echo "built $@" $(DIST)/fossmark-macos-arm64: $(DRIVER) $(ASM_ARM64) | $(DIST) diff --git a/README.md b/README.md index c6fb837..06a3d55 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ architecture-specific backend files. | Encryption | ChaCha20 with 20 rounds over a 1 MiB buffer | | Physics | Direct-sum gravity for 512 bodies | | Sorting | In-place heapsort of one million 32-bit integers | -| Memory latency | Dependent pointer chasing through a private 16 MiB cycle | +| Memory latency | Dependent pointer chasing through a private cache-exceeding cycle | The benchmark increases each test's iteration count until one run takes at least two seconds. It then keeps the fastest of three runs. Each kernel returns @@ -76,6 +76,27 @@ Run the benchmark with extra per-test details by passing `--verbose`: The exact filename depends on the host platform and architecture. +At startup, fossmark reports the detected CPU model, physical cores, logical +threads, installed memory, operating system, architecture, and compiler. At the +end it prints the composite scores and total benchmark duration, then asks +whether to upload the result. Uploading is opt-in and requires an API token in +the environment: + +```sh +FOSSMARK_API_TOKEN=your_token ./dist/fossmark-linux-amd64 +``` + +The API base URL is defined by `FM_API_BASE_URL` in `src/main.c` and defaults to +`http://localhost:8080`. A release build can override it without editing the +source: + +```sh +make CFLAGS='-O2 -Wall -Wextra -DFM_API_BASE_URL=\"http://bench.example.com\"' +``` + +The built-in uploader currently supports plain HTTP. An HTTPS production URL +will require TLS support (or submission through a TLS-terminating local proxy). + ## Continuous integration and releases Pushing a Git tag runs the GitHub Actions build and correctness tests. If they @@ -123,7 +144,15 @@ The kernel backends use only baseline instructions for their architecture: * `src/fossmark.S` uses ARMv8-A and NEON under AAPCS64. * `src/fossmark_x86_64.S` uses baseline x86-64 and SSE2 under the System V ABI. -* `src/fossmark_ppc32.c` is endian-safe and uses baseline 32-bit PowerPC operations. It avoids AltiVec so it runs on the Wii's PowerPC 750CL-class CPU. +* `src/fossmark_ppc32.c` is endian-safe and keeps a baseline 32-bit PowerPC + fallback. At runtime, the extended-instruction test uses Paired Singles when + the device-tree `compatible` property begins with `nintendo,`; otherwise it + selects VSX, AltiVec, or the scalar fallback in that order according to + Linux `AT_HWCAP`. + +The PPC32 build uses a 2 MiB pointer-chase cycle, which exceeds the 750CL's L2 +cache while keeping peak benchmark memory consumption below 32 MiB. Other +architectures retain the default 16 MiB cycle. The assembly kernel files contain no system calls or calls into the C library. The same ARM64 source can be assembled for Linux, macOS, Windows, and BSD object formats. @@ -155,6 +184,7 @@ src/main.c portable benchmark driver and scoring src/fossmark.S ARM64 kernels src/fossmark_x86_64.S x86-64 kernels src/fossmark_ppc32.c PPC32 big-endian kernels +src/fossmark_ppc32_ext.S optional PPC32 PS, VSX, and AltiVec kernels src/test_kernels.c correctness suite Makefile native and cross-build targets dist/ generated binaries diff --git a/dist/fossmark-linux-amd64 b/dist/fossmark-linux-amd64 index 5ec57a3..413de55 100755 Binary files a/dist/fossmark-linux-amd64 and b/dist/fossmark-linux-amd64 differ diff --git a/src/fossmark_ppc32.c b/src/fossmark_ppc32.c index 9024e3a..0080197 100644 --- a/src/fossmark_ppc32.c +++ b/src/fossmark_ppc32.c @@ -8,8 +8,13 @@ #include #include #include +#include #include +#if defined(__linux__) +#include +#endif + static uint32_t rotl32(uint32_t x, unsigned n) { return (x << n) | (x >> (32 - n)); @@ -59,7 +64,7 @@ uint64_t fm_primes(uint64_t limit, uint8_t *sieve) return count; } -uint64_t fm_simd(uint64_t iters, void *memory) +static uint64_t fm_simd_scalar(uint64_t iters, void *memory) { uint32_t *v = (uint32_t *)memory; uint32_t a[8]; uint64_t i; unsigned j; uint32_t sum = 0; @@ -72,6 +77,89 @@ uint64_t fm_simd(uint64_t iters, void *memory) return sum; } +/* These are kept in fossmark_ppc32_ext.S so this translation unit, and thus + * the executable's default code path, only requires baseline PPC32. */ +extern void fm_simd_ps_kernel(uint64_t iters, void *memory); +extern void fm_simd_vsx_kernel(uint64_t iters, void *memory); +extern void fm_simd_altivec_kernel(uint64_t iters, void *memory); + +typedef void (*fm_simd_kernel)(uint64_t, void *); + +static int device_is_nintendo(void) +{ +#if defined(__linux__) + static const char prefix[] = "nintendo,"; + static const char *const paths[] = { + "/proc/device-tree/compatible", + "/sys/firmware/devicetree/base/compatible" + }; + char compatible[sizeof prefix - 1]; + unsigned i; + + for (i = 0; i < sizeof paths / sizeof paths[0]; i++) { + FILE *fp = fopen(paths[i], "rb"); + int match; + if (fp == NULL) + continue; + match = fread(compatible, 1, sizeof compatible, fp) == sizeof compatible && + memcmp(compatible, prefix, sizeof compatible) == 0; + fclose(fp); + return match; + } + return 0; +#else + return 0; +#endif +} + +static fm_simd_kernel detect_simd_kernel(void) +{ + /* Linux exposes these in AT_HWCAP on both 32- and 64-bit PowerPC. + * Spell out the ABI values instead of depending on kernel-only headers. */ +#if defined(__linux__) && defined(AT_HWCAP) + const unsigned long hwcap = getauxval(AT_HWCAP); + const unsigned long has_altivec = 0x10000000UL; + const unsigned long has_vsx = 0x00000080UL; + + if (device_is_nintendo()) + return fm_simd_ps_kernel; + if (hwcap & has_vsx) + return fm_simd_vsx_kernel; + if (hwcap & has_altivec) + return fm_simd_altivec_kernel; +#else + if (device_is_nintendo()) + return fm_simd_ps_kernel; +#endif + return NULL; +} + +uint64_t fm_simd(uint64_t iters, void *memory) +{ + static fm_simd_kernel kernel; + static int detected; + fm_simd_kernel selected; + uint32_t *v = (uint32_t *)memory; + uint32_t sum = 0; + unsigned j; + + if (!iters) + return 0; + if (!__atomic_load_n(&detected, __ATOMIC_ACQUIRE)) { + fm_simd_kernel found = detect_simd_kernel(); + __atomic_store_n(&kernel, found, __ATOMIC_RELAXED); + __atomic_store_n(&detected, 1, __ATOMIC_RELEASE); + } + selected = __atomic_load_n(&kernel, __ATOMIC_RELAXED); + if (selected == NULL) + return fm_simd_scalar(iters, memory); + + selected(iters, memory); + for (j = 0; j < 8; j++) + sum ^= v[j]; + return sum; +} + static uint32_t load32_native(const uint8_t *p) { uint32_t v; memcpy(&v, p, sizeof v); return v; diff --git a/src/fossmark_ppc32_ext.S b/src/fossmark_ppc32_ext.S new file mode 100644 index 0000000..6bc8f88 --- /dev/null +++ b/src/fossmark_ppc32_ext.S @@ -0,0 +1,74 @@ +/* Optional PPC32 extended-instruction kernels. No instruction in this file + * is reached until fossmark_ppc32.c has checked the device tree or AT_HWCAP. + * Arguments use the PPC32 ABI: iters in r3:r4 and memory in r5. */ + + .text + .align 2 + + .globl fm_simd_ps_kernel + .type fm_simd_ps_kernel,@function +fm_simd_ps_kernel: + /* Raw encodings keep this buildable with standard Linux binutils, whose + * opcode tables do not always name Nintendo's Gekko/Broadway extension. */ + .long 0xe0050000 /* psq_l f0,0(r5),0,0 */ + .long 0xe0250008 /* psq_l f1,8(r5),0,0 */ +1: .long 0x1000082a /* ps_add f0,f0,f1 */ + .long 0x10210032 /* ps_mul f1,f1,f0 */ + .long 0x1000082a /* ps_add f0,f0,f1 */ + .long 0x10210032 /* ps_mul f1,f1,f0 */ + addic. 4,4,-1 + addme 3,3 + or. 6,3,4 + bne 1b + .long 0xf0050000 /* psq_st f0,0(r5),0,0 */ + .long 0xf0250008 /* psq_st f1,8(r5),0,0 */ + blr + .size fm_simd_ps_kernel,.-fm_simd_ps_kernel + + .globl fm_simd_vsx_kernel + .type fm_simd_vsx_kernel,@function +fm_simd_vsx_kernel: + .machine power7 + li 6,0 + lxvw4x 0,6,5 + li 6,16 + lxvw4x 1,6,5 +1: xvaddsp 0,0,1 + xvmulsp 1,1,0 + xvaddsp 0,0,1 + xvmulsp 1,1,0 + addic. 4,4,-1 + addme 3,3 + or. 6,3,4 + bne 1b + li 6,0 + stxvw4x 0,6,5 + li 6,16 + stxvw4x 1,6,5 + blr + .size fm_simd_vsx_kernel,.-fm_simd_vsx_kernel + + .globl fm_simd_altivec_kernel + .type fm_simd_altivec_kernel,@function +fm_simd_altivec_kernel: + .machine altivec + li 6,0 + lvx 0,6,5 + li 6,16 + lvx 1,6,5 +1: vadduwm 0,0,1 + vxor 1,1,0 + vadduwm 0,0,1 + vxor 1,1,0 + addic. 4,4,-1 + addme 3,3 + or. 6,3,4 + bne 1b + li 6,0 + stvx 0,6,5 + li 6,16 + stvx 1,6,5 + blr + .size fm_simd_altivec_kernel,.-fm_simd_altivec_kernel + + .section .note.GNU-stack,"",@progbits diff --git a/src/main.c b/src/main.c index 6f26f43..1266118 100644 --- a/src/main.c +++ b/src/main.c @@ -22,6 +22,24 @@ #include #include #include +#include +#include + +#if !defined(_WIN32) +# include +# include +# include +#endif +#if defined(__APPLE__) +# include +# include +#endif + +/* Change this at build time with -DFM_API_BASE_URL=\"http://host:port\". */ +#ifndef FM_API_BASE_URL +# define FM_API_BASE_URL "http://localhost:8080" +#endif +#define FM_VERSION "0.1.2" /* ---------- platform identification (for the banner only) ---------- */ @@ -49,7 +67,7 @@ # define FM_ARCH "PowerPC 32-bit big-endian" # define D_INT "PPC32 integer ALU and software 64-bit arithmetic" # define D_FP "PowerPC scalar double-precision floating point" -# define D_SIMD "PPC32 parallel integer workload" +# define D_SIMD "runtime-selected PS, VSX, AltiVec, or scalar" #else # define FM_ARCH "unknown" # define D_INT "64-bit integer ALU" @@ -101,7 +119,16 @@ extern uint64_t fm_chase(void **ptrs, uint64_t steps); #define SIMD_BUF 256 /* NEON scratch */ #define NBODY_N 512 /* bodies */ #define SORT_N (1u << 20) /* elements to sort */ -#define CHASE_NODES (1u << 21) /* 16 MiB cycle, > any L2 */ +/* PPC32 Wii Linux systems have less than 32 MiB available to a process. + * A 2 MiB chase remains well beyond the 750CL's 256 KiB L2 while keeping the + * complete benchmark (including setup's temporary permutation) below 32 MiB. */ +#if defined(__powerpc__) && !defined(__powerpc64__) +# define CHASE_NODES (1u << 19) /* 2 MiB with 32-bit pointers */ +# define CHASE_DETAIL "dependent-load pointer chase, 2 MiB" +#else +# define CHASE_NODES (1u << 21) /* 16 MiB cycle, > any L2 */ +# define CHASE_DETAIL "dependent-load pointer chase, 16 MiB" +#endif #define MIN_SECONDS 2.0 /* per-test measured floor */ #define REPEATS 3 /* best-of, to reject noise */ @@ -226,6 +253,106 @@ static uint8_t *g_simd_src; /* pristine NEON seed, copied per-core */ static double *g_bodies_src; /* pristine initial conditions */ static uint32_t *g_sort_src; /* pristine unsorted data */ +struct system_info { + char cpu[256]; + char operating_system[256]; + char compiler[128]; + long cpu_cores; + long cpu_threads; + long memory_mb; +}; + +static void trim(char *s) +{ + char *p = s; + size_t n; + while (isspace((unsigned char)*p)) p++; + if (p != s) memmove(s, p, strlen(p) + 1); + n = strlen(s); + while (n && isspace((unsigned char)s[n - 1])) s[--n] = '\0'; +} + +static void detect_system_info(struct system_info *info) +{ + memset(info, 0, sizeof(*info)); + info->cpu_threads = g_ncores; + info->cpu_cores = g_ncores; + strncpy(info->cpu, FM_ARCH, sizeof(info->cpu) - 1); + strncpy(info->operating_system, FM_OS, sizeof(info->operating_system) - 1); +#if defined(__clang__) + snprintf(info->compiler, sizeof(info->compiler), "Clang %s", __clang_version__); +#elif defined(__GNUC__) + snprintf(info->compiler, sizeof(info->compiler), "GCC %s", __VERSION__); +#elif defined(_MSC_VER) + snprintf(info->compiler, sizeof(info->compiler), "MSVC %d", _MSC_VER); +#else + strncpy(info->compiler, "Unknown", sizeof(info->compiler) - 1); +#endif + +#if defined(__linux__) + { + FILE *f = fopen("/proc/cpuinfo", "r"); + char line[512]; + int pairs[1024][2], npairs = 0, physical = -1, core = -1; + if (f) { + while (fgets(line, sizeof(line), f)) { + char *colon = strchr(line, ':'); + if (!colon) continue; + *colon++ = '\0'; trim(line); trim(colon); + if ((!strcmp(line, "model name") || !strcmp(line, "Processor") || + !strcmp(line, "cpu")) && info->cpu[0] && !strcmp(info->cpu, FM_ARCH)) + strncpy(info->cpu, colon, sizeof(info->cpu) - 1); + else if (!strcmp(line, "physical id")) physical = atoi(colon); + else if (!strcmp(line, "core id")) core = atoi(colon); + if (physical >= 0 && core >= 0) { + int i, seen = 0; + for (i = 0; i < npairs; i++) + if (pairs[i][0] == physical && pairs[i][1] == core) seen = 1; + if (!seen && npairs < 1024) { pairs[npairs][0] = physical; pairs[npairs++][1] = core; } + physical = core = -1; + } + } + fclose(f); + if (npairs > 0) info->cpu_cores = npairs; + } + } + { + FILE *f = fopen("/proc/meminfo", "r"); + char line[256]; long kb; + if (f) { + while (fgets(line, sizeof(line), f)) { + if (sscanf(line, "MemTotal: %ld kB", &kb) == 1) { + info->memory_mb = kb / 1024; + break; + } + } + fclose(f); + } + } + { + FILE *f = fopen("/etc/os-release", "r"); char line[512]; + if (f) { while (fgets(line, sizeof(line), f)) if (!strncmp(line, "PRETTY_NAME=", 12)) { + char *v = line + 12; trim(v); + if (v[0] == '\"') { memmove(v, v + 1, strlen(v)); if (strlen(v) && v[strlen(v)-1] == '\"') v[strlen(v)-1] = '\0'; } + snprintf(info->operating_system, sizeof(info->operating_system), "%s", v); break; + } fclose(f); } + } +#elif defined(__APPLE__) + { + size_t n = sizeof(info->cpu); uint64_t mem = 0; size_t mn = sizeof(mem); + int cores = 0; size_t cn = sizeof(cores); + if (sysctlbyname("machdep.cpu.brand_string", info->cpu, &n, NULL, 0) != 0) + sysctlbyname("hw.model", info->cpu, &n, NULL, 0); + if (sysctlbyname("hw.physicalcpu", &cores, &cn, NULL, 0) == 0) info->cpu_cores = cores; + if (sysctlbyname("hw.memsize", &mem, &mn, NULL, 0) == 0) info->memory_mb = (long)(mem / 1024 / 1024); + } + { + struct utsname u; if (uname(&u) == 0) + snprintf(info->operating_system, sizeof(info->operating_system), "macOS %s", u.release); + } +#endif +} + /* * Synthesise a compressible corpus. Random bytes would be incompressible and * would make the match-finder trivially miss every probe, measuring nothing @@ -392,6 +519,9 @@ static uint64_t run_primes(uint64_t n, struct workspace *ws) } static uint64_t run_simd(uint64_t n, struct workspace *ws) { + /* The kernel is allowed to use its scratch as an accumulator. Restore it + * before every timed run so calibration and repeats see identical input. */ + memcpy(ws->simd_buf, g_simd_src, SIMD_BUF); return fm_simd(n * 100000, ws->simd_buf); } static uint64_t run_compress(uint64_t n, struct workspace *ws) @@ -453,7 +583,7 @@ static const struct test tests[] = { { "Sorting", "heapsort, 1M uint32", run_sort, 1, (double)SORT_N * 20, "Mkey-cmp/s", FM_REF_SORT, FM_WEIGHT_SORT }, - { "Memory Latency", "dependent-load pointer chase, 16 MiB", + { "Memory Latency", CHASE_DETAIL, run_chase, 1, 1000000.0, "ns/access", FM_REF_CHASE, FM_WEIGHT_CHASE }, }; @@ -603,16 +733,116 @@ static double display_metric(const struct test *t, const struct result *r) return r->rate; } +/* ---------- optional result upload ---------- */ + +static void json_escape(const char *src, char *dst, size_t cap) +{ + size_t used = 0; + while (*src && used + 1 < cap) { + unsigned char c = (unsigned char)*src++; + const char *esc = NULL; + if (c == '\"') esc = "\\\""; + else if (c == '\\') esc = "\\\\"; + else if (c == '\n') esc = "\\n"; + else if (c == '\r') esc = "\\r"; + else if (c == '\t') esc = "\\t"; + if (esc) { + size_t n = strlen(esc); if (used + n >= cap) break; + memcpy(dst + used, esc, n); used += n; + } else if (c >= 0x20) dst[used++] = (char)c; + } + dst[used] = '\0'; +} + +#if !defined(_WIN32) +static int upload_results(const struct system_info *info, double score, + uint64_t duration_ms, const char *token) +{ + char host[256], port[16] = "80", path[512], payload[2048], request[4096]; + char cpu[512], os[512], compiler[256], response[512]; + const char *base = FM_API_BASE_URL, *p, *slash, *colon; + struct addrinfo hints, *addresses = NULL, *a; + int fd = -1, status = 0, payload_len, request_len; + + if (strncmp(base, "http://", 7) != 0) { + fprintf(stderr, " upload error: FM_API_BASE_URL must use http://\n"); + return 0; + } + p = base + 7; slash = strchr(p, '/'); + if (!slash) slash = p + strlen(p); + colon = memchr(p, ':', (size_t)(slash - p)); + if (colon) { + size_t hn = (size_t)(colon - p), pn = (size_t)(slash - colon - 1); + if (hn >= sizeof(host) || pn == 0 || pn >= sizeof(port)) return 0; + memcpy(host, p, hn); host[hn] = '\0'; memcpy(port, colon + 1, pn); port[pn] = '\0'; + } else { + size_t hn = (size_t)(slash - p); if (hn >= sizeof(host)) return 0; + memcpy(host, p, hn); host[hn] = '\0'; + } + { + int base_path_len = (int)strlen(slash); + while (base_path_len > 0 && slash[base_path_len - 1] == '/') base_path_len--; + snprintf(path, sizeof(path), "%.*s/api/v1/submissions", base_path_len, slash); + } + + json_escape(info->cpu, cpu, sizeof(cpu)); + json_escape(info->operating_system, os, sizeof(os)); + json_escape(info->compiler, compiler, sizeof(compiler)); + payload_len = snprintf(payload, sizeof(payload), + "{\"cpu\":\"%s\",\"cpu_cores\":%ld,\"cpu_threads\":%ld," + "\"memory_mb\":%ld,\"operating_system\":\"%s\",\"compiler\":\"%s\"," + "\"fossmark_version\":\"%s\",\"score\":%.2f,\"duration_ms\":%llu}", + cpu, info->cpu_cores, info->cpu_threads, info->memory_mb, os, compiler, + FM_VERSION, score, (unsigned long long)duration_ms); + if (payload_len < 0 || (size_t)payload_len >= sizeof(payload)) return 0; + request_len = snprintf(request, sizeof(request), + "POST %s HTTP/1.1\r\nHost: %s:%s\r\nContent-Type: application/json\r\n" + "Authorization: Bearer %s\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s", + path, host, port, token, payload_len, payload); + if (request_len < 0 || (size_t)request_len >= sizeof(request)) return 0; + + memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_UNSPEC; + if (getaddrinfo(host, port, &hints, &addresses) != 0) { fprintf(stderr, " upload error: cannot resolve %s\n", host); return 0; } + for (a = addresses; a; a = a->ai_next) { + fd = socket(a->ai_family, a->ai_socktype, a->ai_protocol); + if (fd >= 0 && connect(fd, a->ai_addr, a->ai_addrlen) == 0) break; + if (fd >= 0) close(fd); + fd = -1; + } + freeaddrinfo(addresses); + if (fd < 0) { fprintf(stderr, " upload error: cannot connect to %s:%s\n", host, port); return 0; } + { + size_t sent = 0; + while (sent < (size_t)request_len) { + ssize_t n = send(fd, request + sent, (size_t)request_len - sent, 0); + if (n <= 0) { close(fd); fprintf(stderr, " upload error: send failed\n"); return 0; } + sent += (size_t)n; + } + } + { + ssize_t n = recv(fd, response, sizeof(response) - 1, 0); close(fd); + if (n <= 0) { fprintf(stderr, " upload error: no server response\n"); return 0; } + response[n] = '\0'; + if (sscanf(response, "HTTP/%*s %d", &status) != 1) status = 0; + } + if (status < 200 || status >= 300) { fprintf(stderr, " upload failed: server returned HTTP %d\n", status); return 0; } + printf(" Results uploaded successfully (HTTP %d).\n", status); + return 1; +} +#endif + /* ---------- output ---------- */ -static void print_header(void) +static void print_header(const struct system_info *info) { printf("\n"); - printf(" fossbench1.0 - multi-core CPU benchmark\n"); + printf(" fossbench %s - multi-core CPU benchmark\n", FM_VERSION); printf(" ------------------------------------------------------------------\n"); - printf(" platform: %s/%s\n", FM_OS, FM_ARCH); - printf(" cores: %ld\n", - g_ncores, g_ncores); + printf(" CPU: %s\n", info->cpu); + printf(" cores: %ld physical / %ld threads\n", info->cpu_cores, info->cpu_threads); + printf(" memory: %ld MB\n", info->memory_mb); + printf(" OS: %s (%s)\n", info->operating_system, FM_ARCH); + printf(" compiler: %s\n", info->compiler); printf("\n"); printf(" %-24s %12s %-11s %8s %9s\n", "TEST", "RATE", "UNIT", "TIME", "SCORE"); @@ -623,8 +853,11 @@ static void print_header(void) int main(int argc, char **argv) { struct result multi[NTESTS], single[NTESTS]; + struct system_info system_info; double multi_log_sum = 0.0, single_log_sum = 0.0; double weight_sum = 0.0; + double benchmark_started, multicore_score, singlecore_score; + uint64_t duration_ms; int verbose = 0; size_t i; @@ -647,13 +880,15 @@ int main(int argc, char **argv) long n = sysconf(_SC_NPROCESSORS_ONLN); g_ncores = n > 0 ? n : 1; } + detect_system_info(&system_info); + benchmark_started = now_seconds(); printf("\n preparing workloads..."); fflush(stdout); setup(); printf(" done\n"); - print_header(); + print_header(&system_info); for (i = 0; i < NTESTS; i++) { double sm, ss; @@ -699,12 +934,35 @@ int main(int argc, char **argv) * The two passes share tests and weights, so MULTICORE / SINGLECORE is a * clean read of how much the machine gains from all its cores. */ - printf(" %-24s %44.0f\n", "MULTICORE SCORE", - exp(multi_log_sum / weight_sum)); - printf(" %-24s %44.0f\n", "SINGLECORE SCORE", - exp(single_log_sum / weight_sum)); + multicore_score = exp(multi_log_sum / weight_sum); + singlecore_score = exp(single_log_sum / weight_sum); + duration_ms = (uint64_t)((now_seconds() - benchmark_started) * 1000.0); + printf(" %-24s %44.0f\n", "MULTICORE SCORE", multicore_score); + printf(" %-24s %44.0f\n", "SINGLECORE SCORE", singlecore_score); + printf(" %-24s %41.2fs\n", "TOTAL DURATION", (double)duration_ms / 1000.0); printf("\n"); teardown(); + + { + char answer[16]; + printf(" Upload this result to %s? [y/N] ", FM_API_BASE_URL); + fflush(stdout); + if (fgets(answer, sizeof(answer), stdin) && + (answer[0] == 'y' || answer[0] == 'Y')) { + const char *token = getenv("FOSSMARK_API_TOKEN"); + if (!token || !*token) { + fprintf(stderr, " Upload skipped: set FOSSMARK_API_TOKEN to your API token.\n"); + } else { +#if defined(_WIN32) + fprintf(stderr, " Upload is not yet supported on Windows.\n"); +#else + upload_results(&system_info, multicore_score, duration_ms, token); +#endif + } + } else { + printf(" Result was not uploaded.\n"); + } + } return 0; }