release v0.1.2
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+89
-1
@@ -8,8 +8,13 @@
|
||||
#include <math.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <sys/auxv.h>
|
||||
#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;
|
||||
|
||||
@@ -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
|
||||
+270
-12
@@ -22,6 +22,24 @@
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
|
||||
#if !defined(_WIN32)
|
||||
# include <sys/socket.h>
|
||||
# include <sys/utsname.h>
|
||||
# include <netdb.h>
|
||||
#endif
|
||||
#if defined(__APPLE__)
|
||||
# include <sys/types.h>
|
||||
# include <sys/sysctl.h>
|
||||
#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 */
|
||||
/* 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user