blob: fdc1a5404db59d8a852eb8ae471ea01a1000983f [file] [log] [blame]
// Copyright 2017 The Puffs Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#define BUFFER_SIZE (64 * 1024 * 1024)
#define PALETTE_BUFFER_SIZE (4 * 245)
#define PUFFS_TESTLIB_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
uint8_t global_got_buffer[BUFFER_SIZE];
uint8_t global_want_buffer[BUFFER_SIZE];
uint8_t global_src_buffer[BUFFER_SIZE];
uint8_t global_palette_buffer[PALETTE_BUFFER_SIZE];
char fail_msg[65536] = {0};
#define FAIL(...) snprintf(fail_msg, sizeof(fail_msg), ##__VA_ARGS__)
#define INCR_FAIL(msg, ...) \
msg += snprintf(msg, sizeof(fail_msg) - (msg - fail_msg), ##__VA_ARGS__)
int tests_run = 0;
typedef void (*proc)();
proc benches[];
proc tests[];
const char* proc_filename;
const char* proc_funcname = "";
// The order matters here. Clang also defines "__GNUC__".
#if defined(__clang__)
const char* cc = "clang";
const char* cc_version = __clang_version__;
#elif defined(__GNUC__)
const char* cc = "gcc";
const char* cc_version = __VERSION__;
#elif defined(_MSC_VER)
const char* cc = "cl";
const char* cc_version = "???";
#else
const char* cc = "cc";
const char* cc_version = "???";
#endif
typedef struct {
const char* want_filename;
const char* src_filename;
size_t src_offset0;
size_t src_offset1;
} golden_test;
bool bench_warm_up;
struct timeval bench_start_tv;
void bench_start() {
gettimeofday(&bench_start_tv, NULL);
}
void bench_finish(uint64_t reps, uint64_t n_bytes) {
struct timeval bench_finish_tv;
gettimeofday(&bench_finish_tv, NULL);
int64_t micros =
(int64_t)(bench_finish_tv.tv_sec - bench_start_tv.tv_sec) * 1000000 +
(int64_t)(bench_finish_tv.tv_usec - bench_start_tv.tv_usec);
uint64_t nanos = 1;
if (micros > 0) {
nanos = (uint64_t)(micros)*1000;
}
uint64_t kb_per_s = n_bytes * 1000000 / nanos;
const char* name = proc_funcname;
if (!strncmp(name, "bench_", 6)) {
name += 6;
}
if (bench_warm_up) {
printf("# (warm up) %s/%s\t%8" PRIu64 ".%06" PRIu64 " seconds\n", //
name, cc, nanos / 1000000000, (nanos % 1000000000) / 1000);
} else if (!n_bytes) {
printf("Benchmark%s/%s\t%8" PRIu64 "\t%8" PRIu64 " ns/op\n", //
name, cc, reps, nanos / reps);
} else {
printf("Benchmark%s/%s\t%8" PRIu64 "\t%8" PRIu64
" ns/op\t%8d.%03d MB/s\n", //
name, cc, reps, nanos / reps, //
(int)(kb_per_s / 1000), (int)(kb_per_s % 1000));
}
// Flush stdout so that "puffs bench | tee etc" still prints its numbers as
// soon as they are available.
fflush(stdout);
}
int main(int argc, char** argv) {
bool bench = false;
int proc_reps = 5;
int i;
for (i = 1; i < argc; i++) {
const char* arg = argv[i];
if (!strcmp(arg, "-bench")) {
bench = true;
} else if (!strncmp(arg, "-reps=", 6)) {
arg += 6;
if (!*arg) {
fprintf(stderr, "missing -reps=N value\n");
return 1;
}
char* end = NULL;
long int n = strtol(arg, &end, 10);
if (*end) {
fprintf(stderr, "invalid -reps=N value\n");
return 1;
}
if ((n < 0) || (1000000 < n)) {
fprintf(stderr, "out-of-range -reps=N value\n");
return 1;
}
proc_reps = n;
} else {
fprintf(stderr, "unknown flag \"%s\"\n", arg);
return 1;
}
}
proc* procs = tests;
if (!bench) {
proc_reps = 1;
} else {
proc_reps++; // +1 for the warm up run.
procs = benches;
printf("# %s version %s\n#\n", cc, cc_version);
printf(
"# The output format, including the \"Benchmark\" prefixes, is "
"compatible with the\n"
"# https://godoc.org/golang.org/x/perf/cmd/benchstat tool. To install "
"it, first\n"
"# install Go, then run \"go get golang.org/x/perf/cmd/benchstat\".\n");
}
for (i = 0; i < proc_reps; i++) {
bench_warm_up = i == 0;
proc* p;
for (p = procs; *p; p++) {
proc_funcname = "unknown_funcname";
(*p)();
if (fail_msg[0]) {
printf("%-16s%-8sFAIL %s: %s\n", proc_filename, cc, proc_funcname,
fail_msg);
return 1;
}
if (i == 0) {
tests_run++;
}
}
}
if (bench) {
printf("# %-16s%-8s(%d benchmarks run, 1+%d reps per benchmark)\n",
proc_filename, cc, tests_run, proc_reps - 1);
} else {
printf("%-16s%-8sPASS (%d tests run)\n", proc_filename, cc, tests_run);
}
return 0;
}
// PUFFS_BASE_HEADER_H is where puffs_base__buf1 is defined.
#ifdef PUFFS_BASE_HEADER_H
bool read_file(puffs_base__buf1* dst, const char* path) {
if (!dst || !path) {
FAIL("read_file: NULL argument");
return false;
}
if (dst->closed) {
FAIL("read_file: dst buffer closed for writes");
return false;
}
FILE* f = fopen(path, "r");
if (!f) {
FAIL("read_file(\"%s\"): %s (errno=%d)", path, strerror(errno), errno);
return false;
}
uint8_t dummy[1];
uint8_t* ptr = dst->ptr + dst->wi;
size_t len = dst->len - dst->wi;
while (true) {
if (!len) {
// We have read all that dst can hold. Check that we have read the full
// file by trying to read one more byte, which should fail with EOF.
ptr = dummy;
len = 1;
}
size_t n = fread(ptr, 1, len, f);
if (ptr != dummy) {
ptr += n;
len -= n;
dst->wi += n;
} else if (n) {
FAIL("read_file(\"%s\"): EOF not reached", path);
fclose(f);
return false;
}
if (feof(f)) {
break;
}
int err = ferror(f);
if (!err) {
continue;
}
if (err == EINTR) {
clearerr(f);
continue;
}
FAIL("read_file(\"%s\"): %s (errno=%d)", path, strerror(err), err);
fclose(f);
return false;
}
fclose(f);
dst->closed = true;
return true;
}
char* hex_dump(char* msg, puffs_base__buf1* buf, size_t i) {
if (!msg || !buf) {
FAIL("hex_dump: NULL argument");
return false;
}
if (buf->wi == 0) {
return msg;
}
size_t base = i - (i & 15);
int j;
for (j = -3 * 16; j <= +3 * 16; j += 16) {
if ((j < 0) && (base < (size_t)(-j))) {
continue;
}
size_t b = base + j;
if (b >= buf->wi) {
break;
}
size_t n = buf->wi - b;
INCR_FAIL(msg, " %06zx:", b);
size_t k;
for (k = 0; k < 16; k++) {
if (k % 2 == 0) {
INCR_FAIL(msg, " ");
}
if (k < n) {
INCR_FAIL(msg, "%02x", buf->ptr[b + k]);
} else {
INCR_FAIL(msg, " ");
}
}
INCR_FAIL(msg, " ");
for (k = 0; k < 16; k++) {
char c = ' ';
if (k < n) {
c = buf->ptr[b + k];
if ((c < 0x20) || (0x7F <= c)) {
c = '.';
}
}
INCR_FAIL(msg, "%c", c);
}
INCR_FAIL(msg, "\n");
if (n < 16) {
break;
}
}
return msg;
}
bool buf1s_equal(const char* prefix,
puffs_base__buf1* got,
puffs_base__buf1* want) {
if (!got || !want) {
FAIL("%sbuf1s_equal: NULL argument", prefix);
return false;
}
char* msg = fail_msg;
size_t i;
for (i = 0; (i < got->wi) && (i < want->wi); i++) {
if (got->ptr[i] != want->ptr[i]) {
break;
}
}
if (got->wi != want->wi) {
INCR_FAIL(msg, "%sbuf1s_equal: wi: got %zu, want %zu.\n", prefix, got->wi,
want->wi);
} else if (i < got->wi) {
INCR_FAIL(msg, "%sbuf1s_equal:\n", prefix);
} else {
return true;
}
INCR_FAIL(msg, "contents differ at byte %zu (in hex: 0x%06zx):\n", i, i);
msg = hex_dump(msg, got, i);
INCR_FAIL(msg, "excerpts of got (above) versus want (below):\n");
msg = hex_dump(msg, want, i);
return false;
}
// throughput_counter is whether to count dst or src bytes, or neither, when
// calculating a benchmark's MB/s throughput number.
//
// Decoders typically use tc_dst. Encoders and hashes typically use tc_src.
typedef enum {
tc_neither = 0,
tc_dst = 1,
tc_src = 2,
} throughput_counter;
bool proc_buf1_buf1(const char* (*codec_func)(puffs_base__buf1*,
puffs_base__buf1*,
uint64_t,
uint64_t),
throughput_counter tc,
golden_test* gt,
uint64_t wlimit,
uint64_t rlimit,
uint64_t reps,
bool bench) {
if (!codec_func) {
FAIL("NULL codec_func");
return false;
}
if (!gt) {
FAIL("NULL golden_test");
return false;
}
puffs_base__buf1 src = {.ptr = global_src_buffer, .len = BUFFER_SIZE};
puffs_base__buf1 got = {.ptr = global_got_buffer, .len = BUFFER_SIZE};
puffs_base__buf1 want = {.ptr = global_want_buffer, .len = BUFFER_SIZE};
if (!gt->src_filename) {
src.closed = true;
} else if (!read_file(&src, gt->src_filename)) {
return false;
}
if (gt->src_offset0 || gt->src_offset1) {
if (gt->src_offset0 > gt->src_offset1) {
FAIL("inconsistent src_offsets");
return false;
}
if (gt->src_offset1 > src.wi) {
FAIL("src_offset1 too large");
return false;
}
src.ri = gt->src_offset0;
src.wi = gt->src_offset1;
}
if (bench) {
bench_start();
}
uint64_t n_bytes = 0;
uint64_t i;
for (i = 0; i < reps; i++) {
got.wi = 0;
src.ri = gt->src_offset0;
const char* s = codec_func(&got, &src, wlimit, rlimit);
if (s) {
FAIL("%s", s);
return false;
}
switch (tc) {
case tc_neither:
break;
case tc_dst:
n_bytes += got.wi;
break;
case tc_src:
n_bytes += src.ri - gt->src_offset0;
break;
}
}
if (bench) {
bench_finish(reps, n_bytes);
return true;
}
if (!gt->want_filename) {
want.closed = true;
} else if (!read_file(&want, gt->want_filename)) {
return false;
}
if (!buf1s_equal("", &got, &want)) {
return false;
}
return true;
}
bool do_bench_buf1_buf1(const char* (*codec_func)(puffs_base__buf1*,
puffs_base__buf1*,
uint64_t,
uint64_t),
throughput_counter tc,
golden_test* gt,
uint64_t wlimit,
uint64_t rlimit,
uint64_t reps) {
return proc_buf1_buf1(codec_func, tc, gt, wlimit, rlimit, reps, true);
}
bool do_test_buf1_buf1(const char* (*codec_func)(puffs_base__buf1*,
puffs_base__buf1*,
uint64_t,
uint64_t),
golden_test* gt,
uint64_t wlimit,
uint64_t rlimit) {
return proc_buf1_buf1(codec_func, 0, gt, wlimit, rlimit, 1, false);
}
#endif // PUFFS_BASE_HEADER_H