From 48455d1d57e3977b8a759dc69241143c19b00b64 Mon Sep 17 00:00:00 2001 From: Owen Rummage Date: Fri, 17 Jul 2026 17:10:35 -0500 Subject: [PATCH] release v0.1.2 --- Makefile | 7 +- README.md | 34 ++++- dist/fossmark-linux-amd64 | Bin 30440 -> 39752 bytes src/fossmark_ppc32.c | 90 +++++++++++- src/fossmark_ppc32_ext.S | 74 ++++++++++ src/main.c | 284 ++++++++++++++++++++++++++++++++++++-- 6 files changed, 470 insertions(+), 19 deletions(-) create mode 100644 src/fossmark_ppc32_ext.S 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 5ec57a3becbdbcdb229557076da9e6ab41b86287..413de55f799121592af9c72023db5f2de6c865b7 100755 GIT binary patch literal 39752 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 #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; }