diff --git a/Makefile b/Makefile index d69c2a0..13a6204 100644 --- a/Makefile +++ b/Makefile @@ -48,10 +48,14 @@ static: $(LIB)/libsmax.a # Run regression tests .PHONY: test -test: SRC := tests test: shared static make -f test.mk +# Build benchmark program +.PHONY: benchmark +benchmark: shared static + make -f test.mk benchmark + # 'test' + 'analyze' .PHONY: check check: test analyze diff --git a/test.mk b/test.mk index e82c698..4fad74c 100644 --- a/test.mk +++ b/test.mk @@ -1,3 +1,5 @@ +SRC := tests + include config.mk # Load the common Makefile definitions... @@ -10,8 +12,10 @@ TESTS = $(BIN)/simpleIntTest $(BIN)/simpleIntsTest $(BIN)/structTest $(BIN)/queu $(BIN)/waitTest $(BIN)/resilientTest # Top level make targets... +.PHONY: build build: $(TESTS) +.PHONY: run run: build $(BIN)/simpleIntTest $(BIN)/simpleIntsTest @@ -21,4 +25,10 @@ run: build $(BIN)/waitTest $(BIN)/resilientTest +.PHONY: benchmark +benchmark: $(BIN)/benchmark + +$(BIN)/benchmark: LDFLAGS += -lpopt -lbsd + + include build.mk \ No newline at end of file diff --git a/tests/benchmark.c b/tests/benchmark.c new file mode 100644 index 0000000..4874619 --- /dev/null +++ b/tests/benchmark.c @@ -0,0 +1,205 @@ +/* + * benchmark.c + * + * Created on: Dec 20, 2024 + * Author: Attila Kovacs + * + * A simple tool for benchmarking SMA-X performance from a client to a designated server. + * + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include "smax.h" + +#define PI 3.14159265358979 + +static void readBenchmark(int n, boolean pipelined, boolean withMeta); +static int readBunch(XMeta *meta); +static int queueBunch(XMeta *meta); + +static void writeBenchmark(int n); +static int writeBunch(); + +static void printVersion(const char *name) { + printf("%s %s\n", name, SMAX_VERSION_STRING); +} + +int main(int argc, const char *argv[]) { + const char *fn = "benchmark"; + + char *host = "smax"; + int port = 6379; + char *user = NULL; + char *password = NULL; + int askpass = FALSE; + int cycles = 100; + int dbIndex = 0; + int verbose = FALSE; + int debug = FALSE; + + struct poptOption options[] = { // + {"host", 'h', POPT_ARG_STRING | POPT_ARGFLAG_SHOW_DEFAULT, &host, 0, "Server hostname.", ""}, // + {"port", 'p', POPT_ARG_INT | POPT_ARGFLAG_SHOW_DEFAULT, &port, 0, "Server port.", ""}, // + {"pass", 'a', POPT_ARG_STRING, &password, 0, "Password to use when connecting to the server.", ""}, // + {"user", 0, POPT_ARG_STRING, &user, 0, "Used to send ACL style 'AUTH username pass'. Needs -a.", ""}, // + {"askpass", 0, POPT_ARG_NONE, &askpass, 0, "Force user to input password with mask from STDIN. " // + "If this argument is used, '-a' will be ignored.", NULL // + }, // + {"repeat", 'r', POPT_ARG_INT | POPT_ARGFLAG_SHOW_DEFAULT, &cycles, 0, "Repeat this many times.", ""}, // + {"db", 'n', POPT_ARG_INT | POPT_ARGFLAG_SHOW_DEFAULT, &dbIndex, 0, "Database number.", ""}, // + {"verbose", 0, POPT_ARG_NONE, &verbose, 0, "Verbose mode.", NULL }, // + {"debug", 0, POPT_ARG_NONE | POPT_ARGFLAG_DOC_HIDDEN, &debug, 0, "Debug mode. Prints all network traffic.", NULL }, // + {"version", 0, POPT_ARG_NONE, NULL, 'v', "Output version and exit.", NULL }, // + POPT_AUTOHELP POPT_TABLEEND // + }; + + int rc; + + poptContext optcon = poptGetContext(fn, argc, argv, options, 0); + + while((rc = poptGetNextOpt(optcon)) != -1) { + if(rc < -1) { + fprintf(stderr, "ERROR! Bad syntax. Try running with --help to see command-line options.\n"); + exit(1); + } + + switch(rc) { + case 'v': printVersion(fn); return 0; + } + } + + if(askpass) { + password = (char *) malloc(1024); + if(readpassphrase("Enter password: ", password, 1024, 0) == NULL) { + free(password); + password = NULL; + } + } + + smaxSetPipelined(TRUE); + + if(user || password) smaxSetAuth(user, password); + if(verbose) smaxSetVerbose(TRUE); + if(debug) xSetDebug(TRUE); + + smaxSetServer(host, port); + if(dbIndex > 0) smaxSetDB(dbIndex); + + if(smaxConnect() != X_SUCCESS) { + fprintf(stderr, "ERROR connecting to Redis. Exiting...\n"); + exit(1); + } + + printf("Benchmarking pipelined writes...\n"); + writeBenchmark(cycles); + + printf("benchmarking reads (with meta)...\n"); + readBenchmark(cycles, FALSE, TRUE); + + printf("benchmarking reads (without meta)...\n"); + readBenchmark(cycles, FALSE, FALSE); + + printf("benchmarking piped reads with meta\n"); + readBenchmark(cycles, TRUE, TRUE); + + printf("benchmarking piped reads without meta\n"); + readBenchmark(cycles, TRUE, FALSE); + + printf("closing...\n"); + smaxDisconnect(); + + return 0; +} + + +static void readBenchmark(int n, boolean pipelined, boolean withMeta) { + struct timespec start, end; + double dt; + int k, N = 0; + XMeta meta; + XMeta *m = withMeta ? &meta : NULL; + + clock_gettime(CLOCK_REALTIME, &start); + + if(pipelined) { + XSyncPoint *s; + + for(k=n; --k >= 0; ) N += queueBunch(m); + s = smaxCreateSyncPoint(); + + if(smaxSync(s, 10000)) printf("WARNING! timed out...\n"); + } + else for(k=n; --k >= 0; ) N += readBunch(m); + + clock_gettime(CLOCK_REALTIME, &end); + + dt = end.tv_sec - start.tv_sec + 1e-9 * (end.tv_nsec - start.tv_nsec); + printf(">>> read: %.1f reads/s\n", N / dt); +} + +static void writeBenchmark(int n) { + struct timespec start, end; + double dt; + int k, N=0; + + clock_gettime(CLOCK_REALTIME, &start); + + for(k=n; --k >= 0; ) N += writeBunch(); + + clock_gettime(CLOCK_REALTIME, &end); + + dt = end.tv_sec - start.tv_sec + 1e-9 * (end.tv_nsec - start.tv_nsec); + printf(">>> write: %.1f writes/s\n", N / dt); +} + +// These variables must be persistent, or else we get segfaults +// (probably because they aren't being used actually...) +static boolean bval; +static int ival; +static float fval; +static double dval; +static float f10x2[10][2]; +static char name[100]; + +static int queueBunch(XMeta *meta) { + smaxQueue("_test_", "single_boolean_value", X_BOOLEAN, 1, &bval, meta); + smaxQueue("_test_", "single_int_value", X_INT, 1, &ival, meta); + smaxQueue("_test_", "single_float_value", X_FLOAT, 1, &fval, meta); + smaxQueue("_test_", "single_double_value", X_DOUBLE, 1, &dval, meta); + smaxQueue("_test_", "single_string_value", X_STRING, 1, &name, meta); + smaxQueue("_test_", "small_float_array", X_FLOAT, 20, f10x2, meta); + return 6; +} + +static int readBunch(XMeta *meta) { + smaxPull("_test_", "single_boolean_value", X_BOOLEAN, 1, &bval, meta); + smaxPull("_test_", "single_int_value", X_INT, 1, &ival, meta); + smaxPull("_test_", "single_float_value", X_FLOAT, 1, &fval, meta); + smaxPull("_test_", "single_double_value", X_DOUBLE, 1, &name, meta); + smaxPull("_test_", "single_string_value", X_STRING, 1, &name, meta); + smaxPull("_test_", "small_float_array", X_FLOAT, 20, f10x2, meta); + return 6; +} + +static int writeBunch() { + static int count; + + float f = PI; + + smaxShareBoolean("_test_", "single_boolean_value", count % 2); + smaxShareInt("_test_", "single_int_value", (count % 2 ? -1 : 1) * (1 << (count % 30)) + count); + smaxShare("_test_", "single_float_value", &f, X_FLOAT, 1); + smaxShareDouble("_test_", "single_double_value", PI); + smaxShareString("_test_", "single_string_value", "Hello world! I'm a string value right here."); + smaxShareFloats("_test_", "small_float_array", (float *) f10x2, 20); + + return 6; +}