diff --git a/bindings/c/local.mk b/bindings/c/local.mk index 4df783d5c1..baa757c16a 100644 --- a/bindings/c/local.mk +++ b/bindings/c/local.mk @@ -23,14 +23,18 @@ fdb_c_CFLAGS := $(fdbclient_CFLAGS) fdb_c_LDFLAGS := $(fdbrpc_LDFLAGS) fdb_c_LIBS := lib/libfdbclient.a lib/libfdbrpc.a lib/libflow.a +fdb_c_tests_LIBS := -Llib -lfdb_c +fdb_c_tests_HEADERS := -Ibindings/c ifeq ($(PLATFORM),linux) fdb_c_LIBS += lib/libstdc++.a -lm -lpthread -lrt -ldl fdb_c_LDFLAGS += -Wl,--version-script=bindings/c/fdb_c.map -static-libgcc -Wl,-z,nodelete + fdb_c_tests_LIBS += -lpthread endif ifeq ($(PLATFORM),osx) fdb_c_LDFLAGS += -lc++ -Xlinker -exported_symbols_list -Xlinker bindings/c/fdb_c.symbols + fdb_c_tests_LIBS += -lpthread lib/libfdb_c.dylib: bindings/c/fdb_c.symbols @@ -74,3 +78,24 @@ fdb_c_BUILD_SOURCES += bindings/c/fdb_c.g.S bindings/c/foundationdb/fdb_c_options.g.h: bin/vexillographer.exe fdbclient/vexillographer/fdb.options $(ALL_MAKEFILES) @echo "Building $@" @$(MONO) bin/vexillographer.exe fdbclient/vexillographer/fdb.options c $@ + +bin/fdb_c_performance_test: bindings/c/test/performance_test.c bindings/c/test/test.h fdb_c + @echo "Compiling fdb_c_performance_test" + @$(CC) $(CFLAGS) $(fdb_c_tests_LIBS) $(fdb_c_tests_HEADERS) -o $@ bindings/c/test/performance_test.c + +bin/fdb_c_ryw_benchmark: bindings/c/test/ryw_benchmark.c bindings/c/test/test.h fdb_c + @echo "Compiling fdb_c_ryw_benchmark" + @$(CC) $(CFLAGS) $(fdb_c_tests_LIBS) $(fdb_c_tests_HEADERS) -o $@ bindings/c/test/ryw_benchmark.c + +packages/fdb-c-tests-$(VERSION)-$(PLATFORM).tar.gz: bin/fdb_c_performance_test bin/fdb_c_ryw_benchmark + @echo "Packaging $@" + @rm -rf packages/fdb-c-tests-$(VERSION)-$(PLATFORM) + @mkdir -p packages/fdb-c-tests-$(VERSION)-$(PLATFORM)/bin + @cp bin/fdb_c_performance_test packages/fdb-c-tests-$(VERSION)-$(PLATFORM)/bin + @cp bin/fdb_c_ryw_benchmark packages/fdb-c-tests-$(VERSION)-$(PLATFORM)/bin + @tar -C packages -czvf $@ fdb-c-tests-$(VERSION)-$(PLATFORM) > /dev/null + @rm -rf packages/fdb-c-tests-$(VERSION)-$(PLATFORM) + +fdb_c_tests: packages/fdb-c-tests-$(VERSION)-$(PLATFORM).tar.gz + +packages: fdb_c_tests diff --git a/bindings/c/test/performance_test.c b/bindings/c/test/performance_test.c new file mode 100644 index 0000000000..2377e450fb --- /dev/null +++ b/bindings/c/test/performance_test.c @@ -0,0 +1,626 @@ +/* + * performance_test.c + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project 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 + * + * http://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. + */ + +#define FDB_API_VERSION 500 + +#include "test.h" +#include +#include + +#include +#include + +pthread_t netThread; + +int numKeys = 1000000; +int keySize = 16; +uint8_t** keys = NULL; +int valueSize = 100; +uint8_t *valueStr = NULL; + +fdb_error_t waitError(FDBFuture *f) { + fdb_error_t blockError = fdb_future_block_until_ready(f); + if(!blockError) { + return fdb_future_get_error(f); + } else { + return blockError; + } +} + +struct RunResult run(struct ResultSet *rs, FDBDatabase *db, struct RunResult (*func)(struct ResultSet*, FDBTransaction*)) { + FDBTransaction *tr = NULL; + checkError(fdb_database_create_transaction(db, &tr), "create transaction", rs); + fdb_error_t e = fdb_database_create_transaction(db, &tr); + checkError(e, "create transaction", rs); + + while(1) { + struct RunResult r = func(rs, tr); + e = r.e; + if(!e) { + FDBFuture *f = fdb_transaction_commit(tr); + e = waitError(f); + fdb_future_destroy(f); + } + + if(e) { + FDBFuture *f = fdb_transaction_on_error(tr, e); + fdb_error_t retryE = waitError(f); + fdb_future_destroy(f); + if (retryE) { + return (struct RunResult) {0, retryE}; + } + } else { + return r; + } + } + + return RES(0, 4100); // internal_error ; we should never get here +} + +int runTest(struct RunResult (*testFxn)(struct ResultSet*, FDBTransaction*), FDBDatabase *db, struct ResultSet *rs, const char *kpiName) { + int numRuns = 25; + int *results = malloc(sizeof(int)*numRuns); + int i = 0; + for(; i < numRuns; ++i) { + struct RunResult res = run(rs, db, testFxn); + if(res.e) { + logError(res.e, kpiName, rs); + free(results); + return 0; + } + results[i] = res.res; + if(results[i] < 0) { + free(results); + return -1; + } + } + + int result = median(results, numRuns); + free(results); + + addKpi(rs, kpiName, result, "keys/s"); + + return result; +} + +int runTestDb(struct RunResult (*testFxn)(struct ResultSet*, FDBDatabase*), FDBDatabase *db, struct ResultSet *rs, const char *kpiName) { + int numRuns = 25; + int *results = malloc(sizeof(int)*numRuns); + int i = 0; + for(; i < numRuns; ++i) { + struct RunResult res = testFxn(rs, db); + if(res.e) { + logError(res.e, kpiName, rs); + free(results); + return 0; + } + results[i] = res.res; + if(results[i] < 0) { + free(results); + return -1; + } + } + + int result = median(results, numRuns); + free(results); + + addKpi(rs, kpiName, result, "keys/s"); + + return result; +} + + +struct RunResult clearAll(struct ResultSet *rs, FDBTransaction *tr) { + fdb_transaction_clear_range(tr, (uint8_t*)"", 0, (uint8_t*)"\xff", 1); + return RES(0, 0); +} + +uint32_t start = 0; +uint32_t stop = 0; +struct RunResult insertRange(struct ResultSet *rs, FDBTransaction *tr) { + int i; + for(i = start; i < stop; i++) { + fdb_transaction_set(tr, keys[i], keySize, valueStr, valueSize); + } + return RES(0, 0); +} + +void insertData(struct ResultSet *rs, FDBDatabase *db) { + checkError(run(rs, db, &clearAll).e, "clearing database", rs); + + // TODO: Do this asynchronously. + start = 0; + while(start < numKeys) { + stop = start + 1000; + if(stop > numKeys) stop = numKeys; + checkError(run(rs, db, &insertRange).e, "inserting data range", rs); + start = stop; + } +} + +fdb_error_t setRetryLimit(struct ResultSet *rs, FDBTransaction *tr, uint64_t limit) { + return fdb_transaction_set_option(tr, FDB_TR_OPTION_RETRY_LIMIT, (const uint8_t*)&limit, sizeof(uint64_t)); +} + +uint32_t FUTURE_LATENCY_COUNT = 100000; +const char *FUTURE_LATENCY_KPI = "C future throughput (local client)"; +struct RunResult futureLatency(struct ResultSet *rs, FDBTransaction *tr) { + fdb_error_t e = maybeLogError(setRetryLimit(rs, tr, 5), "setting retry limit", rs); + if(e) return RES(0, e); + + FDBFuture *f = fdb_transaction_get_read_version(tr); + e = waitError(f); + fdb_future_destroy(f); + maybeLogError(e, "getting initial read version", rs); + if(e) return RES(0, e); + + double start = getTime(); + int i; + for(i = 0; i < FUTURE_LATENCY_COUNT; i++) { + FDBFuture *f = fdb_transaction_get_read_version(tr); + e = waitError(f); + fdb_future_destroy(f); + maybeLogError(e, "getting read version", rs); + if(e) return RES(0, e); + } + double end = getTime(); + + return RES(FUTURE_LATENCY_COUNT/(end - start), 0); +} + +uint32_t CLEAR_COUNT = 100000; +const char *CLEAR_KPI = "C clear throughput (local client)"; +struct RunResult clear(struct ResultSet *rs, FDBTransaction *tr) { + double start = getTime(); + int i; + for(i = 0; i < CLEAR_COUNT; i++) { + int k = ((uint64_t)rand()) % numKeys; + fdb_transaction_clear(tr, keys[k], keySize); + } + double end = getTime(); + + fdb_transaction_reset(tr); // Don't actually clear things. + return RES(CLEAR_COUNT/(end - start), 0); +} + +uint32_t CLEAR_RANGE_COUNT = 100000; +const char *CLEAR_RANGE_KPI = "C clear range throughput (local client)"; +struct RunResult clearRange(struct ResultSet *rs, FDBTransaction *tr) { + double start = getTime(); + int i; + for(i = 0; i < CLEAR_RANGE_COUNT; i++) { + int k = ((uint64_t)rand()) % (numKeys - 1); + fdb_transaction_clear_range(tr, keys[k], keySize, keys[k+1], keySize); + } + double end = getTime(); + + fdb_transaction_reset(tr); // Don't actually clear things. + return RES(CLEAR_RANGE_COUNT/(end - start), 0); +} + +uint32_t SET_COUNT = 100000; +const char *SET_KPI = "C set throughput (local client)"; +struct RunResult set(struct ResultSet *rs, FDBTransaction *tr) { + double start = getTime(); + int i; + for(i = 0; i < CLEAR_COUNT; i++) { + int k = ((uint64_t)rand()) % numKeys; + fdb_transaction_set(tr, keys[k], keySize, valueStr, valueSize); + } + double end = getTime(); + + fdb_transaction_reset(tr); // Don't actually set things. + return RES(SET_COUNT/(end - start), 0); +} + +uint32_t PARALLEL_GET_COUNT = 10000; +const char *PARALLEL_GET_KPI = "C parallel get throughput (local client)"; +struct RunResult parallelGet(struct ResultSet *rs, FDBTransaction *tr) { + fdb_error_t e = maybeLogError(setRetryLimit(rs, tr, 5), "setting retry limit", rs); + if(e) return RES(0, e); + + double start = getTime(); + + FDBFuture **futures = (FDBFuture**)malloc((sizeof(FDBFuture*)) * PARALLEL_GET_COUNT); + + int i; + for(i = 0; i < PARALLEL_GET_COUNT; i++) { + int k = ((uint64_t)rand()) % numKeys; + futures[i] = fdb_transaction_get(tr, keys[k], keySize, 0); + } + + fdb_bool_t present; + uint8_t const *outValue; + int outValueLength; + + for(i = 0; i < PARALLEL_GET_COUNT; i++) { + e = maybeLogError(fdb_future_block_until_ready(futures[i]), "waiting for get future", rs); + if(e) { + fdb_future_destroy(futures[i]); + return RES(0, e); + } + + e = maybeLogError(fdb_future_get_value(futures[i], &present, &outValue, &outValueLength), "getting future value", rs); + if(e) { + fdb_future_destroy(futures[i]); + return RES(0, e); + } + + fdb_future_destroy(futures[i]); + } + + double end = getTime(); + + free(futures); + return RES(PARALLEL_GET_COUNT/(end - start), 0); +} + +uint32_t ALTERNATING_GET_SET_COUNT = 2000; +const char *ALTERNATING_GET_SET_KPI = "C alternating get set throughput (local client)"; +struct RunResult alternatingGetSet(struct ResultSet *rs, FDBTransaction *tr) { + fdb_error_t e = maybeLogError(setRetryLimit(rs, tr, 5), "setting retry limit", rs); + if(e) return RES(0, e); + + double start = getTime(); + + FDBFuture **futures = (FDBFuture**)malloc((sizeof(FDBFuture*)) * ALTERNATING_GET_SET_COUNT); + + int i; + for(i = 0; i < ALTERNATING_GET_SET_COUNT; i++) { + int k = ((uint64_t)rand()) % numKeys; + fdb_transaction_set(tr, keys[k], keySize, valueStr, valueSize); + futures[i] = fdb_transaction_get(tr, keys[k], keySize, 0); + } + + fdb_bool_t present; + uint8_t const *outValue; + int outValueLength; + + for(i = 0; i < ALTERNATING_GET_SET_COUNT; i++) { + e = maybeLogError(fdb_future_block_until_ready(futures[i]), "waiting for get future", rs); + if(e) { + fdb_future_destroy(futures[i]); + return RES(0, e); + } + + e = maybeLogError(fdb_future_get_value(futures[i], &present, &outValue, &outValueLength), "getting future value", rs); + if(e) { + fdb_future_destroy(futures[i]); + return RES(0, e); + } + + fdb_future_destroy(futures[i]); + } + + double end = getTime(); + + free(futures); + return RES(ALTERNATING_GET_SET_COUNT/(end - start), 0); +} + +uint32_t SERIAL_GET_COUNT = 2000; +const char *SERIAL_GET_KPI = "C serial get throughput (local client)"; +struct RunResult serialGet(struct ResultSet *rs, FDBTransaction *tr) { + fdb_error_t e = maybeLogError(setRetryLimit(rs, tr, 5), "setting retry limit", rs); + if(e) return RES(0, e); + + int i; + uint32_t *keyIndices = (uint32_t*)malloc((sizeof(uint32_t)) * SERIAL_GET_COUNT); + + if(SERIAL_GET_COUNT > numKeys/2) { + for(i = 0; i < SERIAL_GET_COUNT; i++) { + keyIndices[i] = ((uint64_t)rand()) % numKeys; + } + } else { + for(i = 0; i < SERIAL_GET_COUNT; i++) { + while(1) { + // Yes, this is a linear scan. This happens outside + // the part we are measuring. + uint32_t index = ((uint64_t)rand()) % numKeys; + int j; + fdb_bool_t found = 0; + for(j = 0; j < i; j++) { + if(keyIndices[j] == index) { + found = 1; + break; + } + } + + if(!found) { + keyIndices[i] = index; + break; + } + } + } + } + + double start = getTime(); + + fdb_bool_t present; + uint8_t const *outValue; + int outValueLength; + + for(i = 0; i < SERIAL_GET_COUNT; i++) { + FDBFuture *f = fdb_transaction_get(tr, keys[keyIndices[i]], keySize, 0); + fdb_error_t e = maybeLogError(fdb_future_block_until_ready(f), "getting key in serial", rs); + if(e) { + free(keyIndices); + fdb_future_destroy(f); + return RES(0, e); + } + + e = maybeLogError(fdb_future_get_value(f, &present, &outValue, &outValueLength), "getting future value", rs); + fdb_future_destroy(f); + if(e) { + free(keyIndices); + return RES(0, e); + } + } + + free(keyIndices); + + double end = getTime(); + + return RES(SERIAL_GET_COUNT/(end - start), 0); +} + +uint32_t GET_RANGE_COUNT = 100000; +const char *GET_RANGE_KPI = "C get range throughput (local client)"; +struct RunResult getRange(struct ResultSet *rs, FDBTransaction *tr) { + fdb_error_t e = maybeLogError(setRetryLimit(rs, tr, 5), "setting retry limit", rs); + if(e) return RES(0, e); + + uint32_t startKey = ((uint64_t)rand()) % (numKeys - GET_RANGE_COUNT - 1); + + double start = getTime(); + + const FDBKeyValue *outKv; + int outCount; + fdb_bool_t outMore = 1; + int totalOut = 0; + int iteration = 0; + + FDBFuture *f = fdb_transaction_get_range(tr, + keys[startKey], keySize, 1, 0, + keys[startKey + GET_RANGE_COUNT], keySize, 1, 0, + 0, 0, + FDB_STREAMING_MODE_WANT_ALL, ++iteration, 0, 0); + + while(outMore) { + e = maybeLogError(fdb_future_block_until_ready(f), "getting range", rs); + if(e) { + fdb_future_destroy(f); + return RES(0, e); + } + + e = maybeLogError(fdb_future_get_keyvalue_array(f, &outKv, &outCount, &outMore), "reading range array", rs); + if(e) { + fdb_future_destroy(f); + return RES(0, e); + } + + totalOut += outCount; + + if(outMore) { + FDBFuture *f2 = fdb_transaction_get_range(tr, + outKv[outCount - 1].key, outKv[outCount - 1].key_length, 1, 1, + keys[startKey + GET_RANGE_COUNT], keySize, 1, 0, + 0, 0, + FDB_STREAMING_MODE_WANT_ALL, ++iteration, 0, 0); + fdb_future_destroy(f); + f = f2; + } + } + + if(totalOut != GET_RANGE_COUNT) { + char *msg = (char*)malloc((sizeof(char)) * 200); + sprintf(msg, "verifying out count (%d != %d)", totalOut, GET_RANGE_COUNT); + logError(4100, msg, rs); + free(msg); + fdb_future_destroy(f); + return RES(0, 4100); + } + if(outMore) { + logError(4100, "verifying no more in range", rs); + fdb_future_destroy(f); + return RES(0, 4100); + } + fdb_future_destroy(f); + + double end = getTime(); + + return RES(GET_RANGE_COUNT/(end - start), 0); +} + +uint32_t GET_KEY_COUNT = 2000; +const char *GET_KEY_KPI = "C get key throughput (local client)"; +struct RunResult getKey(struct ResultSet *rs, FDBTransaction *tr) { + fdb_error_t e = maybeLogError(setRetryLimit(rs, tr, 5), "setting retry limit", rs); + if(e) return RES(0, e); + + double start = getTime(); + + fdb_bool_t present; + uint8_t const *outValue; + int outValueLength; + + int i; + for(i = 0; i < GET_KEY_COUNT; i++) { + int key = ((uint64_t)rand()) % numKeys; + int offset = (((uint64_t)rand()) % 21) - 10; + FDBFuture *f = fdb_transaction_get_key(tr, keys[key], keySize, 1, offset, 0); + + e = maybeLogError(fdb_future_block_until_ready(f), "waiting for get key", rs); + if(e) { + fdb_future_destroy(f); + return RES(0, e); + } + + e = maybeLogError(fdb_future_get_value(f, &present, &outValue, &outValueLength), "getting future value", rs); + fdb_future_destroy(f); + if(e) { + return RES(0, e); + } + } + + double end = getTime(); + + return RES(GET_KEY_COUNT/(end - start), 0); +} + +uint32_t GET_SINGLE_KEY_RANGE_COUNT = 2000; +const char *GET_SINGLE_KEY_RANGE_KPI = "C get_single_key_range throughput (local client)"; +struct RunResult getSingleKeyRange(struct ResultSet *rs, FDBTransaction *tr) { + fdb_error_t e = maybeLogError(setRetryLimit(rs, tr, 5), "setting retry limit", rs); + if(e) return RES(0, e); + + double start = getTime(); + + const FDBKeyValue *outKv; + int outCount; + fdb_bool_t outMore; + + int i; + for(i = 0; i < GET_SINGLE_KEY_RANGE_COUNT; i++) { + int key = ((uint64_t)rand()) % (numKeys - 1); + FDBFuture *f = fdb_transaction_get_range(tr, + keys[key], keySize, 1, 0, + keys[key + 1], keySize, 1, 0, + 0, 0, + FDB_STREAMING_MODE_WANT_ALL, 1, 0, 0); + + e = maybeLogError(fdb_future_block_until_ready(f), "waiting for single key range", rs); + if(e) { + fdb_future_destroy(f); + return RES(0, e); + } + + e = maybeLogError(fdb_future_get_keyvalue_array(f, &outKv, &outCount, &outMore), "reading single key range array", rs); + if(e) { + fdb_future_destroy(f); + return RES(0, e); + } + + if(outCount != 1) { + logError(4100, "non-1 number of keys returned in single key range read", rs); + fdb_future_destroy(f); + return RES(0, 4100); + } + if(outMore) { + logError(4100, "more keys to read in single key range read", rs); + fdb_future_destroy(f); + return RES(0, 4100); + } + + fdb_future_destroy(f); + } + + double end = getTime(); + + return RES(GET_SINGLE_KEY_RANGE_COUNT/(end - start), 0); +} + +struct RunResult singleKey(struct ResultSet *rs, FDBTransaction *tr) { + int k = ((uint64_t)rand()) % numKeys; + fdb_transaction_set(tr, keys[k], keySize, valueStr, valueSize); + return RES(0, 0); +} + +uint32_t WRITE_TRANSACTION_COUNT = 1000; +const char *WRITE_TRANSACTION_KPI = "C write_transaction throughput (local client)"; +struct RunResult writeTransaction(struct ResultSet *rs, FDBDatabase *db) { + double start = getTime(); + + int i; + for(i = 0; i < WRITE_TRANSACTION_COUNT; i++) { + struct RunResult res = run(rs, db, &singleKey); + if(res.e) return res; + } + + double end = getTime(); + + return RES(WRITE_TRANSACTION_COUNT/(end - start), 0); +} + +void runTests(struct ResultSet *rs) { + FDBDatabase *db = openDatabase(rs, &netThread); + + printf("Loading database...\n"); + insertData(rs, db); + + printf("future_latency\n"); + runTest(&futureLatency, db, rs, FUTURE_LATENCY_KPI); + + printf("clear\n"); + runTest(&clear, db, rs, CLEAR_KPI); + + printf("clear_range\n"); + runTest(&clearRange, db, rs, CLEAR_RANGE_KPI); + + printf("set\n"); + runTest(&set, db, rs, SET_KPI); + + printf("parallel_get\n"); + runTest(¶llelGet, db, rs, PARALLEL_GET_KPI); + + printf("alternating_get_set\n"); + runTest(&alternatingGetSet, db, rs, ALTERNATING_GET_SET_KPI); + + printf("serial_get\n"); + runTest(&serialGet, db, rs, SERIAL_GET_KPI); + + printf("get_range\n"); + runTest(&getRange, db, rs, GET_RANGE_KPI); + + printf("get_key\n"); + runTest(&getKey, db, rs, GET_KEY_KPI); + + printf("get_single_key_range\n"); + runTest(&getSingleKeyRange, db, rs, GET_SINGLE_KEY_RANGE_KPI); + + printf("write_transaction\n"); + runTestDb(&writeTransaction, db, rs, WRITE_TRANSACTION_KPI); + + fdb_database_destroy(db); + fdb_stop_network(); +} + +int main(int argc, char **argv) { + srand(time(NULL)); + struct ResultSet *rs = newResultSet(); + checkError(fdb_select_api_version(500), "select API version", rs); + printf("Running performance test at client version: %s\n", fdb_get_client_version()); + + valueStr = (uint8_t*)malloc((sizeof(uint8_t))*valueSize); + int i; + for(i = 0; i < valueSize; i++) { + valueStr[i] = (uint8_t)'x'; + } + + keys = generateKeys(numKeys, keySize); + runTests(rs); + writeResultSet(rs); + + free(valueStr); + freeResultSet(rs); + freeKeys(keys, numKeys); + + return 0; +} diff --git a/bindings/c/test/ryw_benchmark.c b/bindings/c/test/ryw_benchmark.c new file mode 100644 index 0000000000..6f0fcb1fcf --- /dev/null +++ b/bindings/c/test/ryw_benchmark.c @@ -0,0 +1,259 @@ +/* + * ryw_benchmark.c + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project 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 + * + * http://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. + */ + +#define FDB_API_VERSION 500 + +#include "test.h" +#include +#include + +#include +#include +#include +#include +#include + +pthread_t netThread; + +int numKeys = 10000; +int keySize = 16; +uint8_t** keys; + +void insertData(FDBTransaction *tr) { + fdb_transaction_clear_range(tr, (uint8_t*)"", 0, (uint8_t*)"\xff", 1); + + uint8_t *v = (uint8_t*)"foo"; + uint32_t i; + for(i = 0; i <= numKeys; ++i) { + fdb_transaction_set(tr, keys[i], keySize, v, 3); + } +} + +int runTest(int (*testFxn)(FDBTransaction*, struct ResultSet*), FDBTransaction *tr, struct ResultSet *rs, const char *kpiName) { + int numRuns = 25; + int *results = malloc(sizeof(int)*numRuns); + int i = 0; + for(; i < numRuns; ++i) { + results[i] = testFxn(tr, rs); + if(results[i] < 0) { + free(results); + return -1; + } + } + + int result = median(results, numRuns); + free(results); + + addKpi(rs, kpiName, result, "keys/s"); + + return result; +} + +int getSingle(FDBTransaction *tr, struct ResultSet *rs) { + int present; + uint8_t const *value; + int length; + int i; + + double start = getTime(); + for(i = 0; i < numKeys; ++i) { + FDBFuture *f = fdb_transaction_get(tr, keys[5001], keySize, 0); + if(getError(fdb_future_block_until_ready(f), "GetSingle (block for get)", rs)) return -1; + if(getError(fdb_future_get_value(f, &present, &value, &length), "GetSingle (get result)", rs)) return -1; + fdb_future_destroy(f); + } + double end = getTime(); + + return numKeys / (end - start); +} + +int getManySequential(FDBTransaction *tr, struct ResultSet *rs) { + int present; + uint8_t const *value; + int length; + int i; + + double start = getTime(); + for(i = 0; i < numKeys; ++i) { + FDBFuture *f = fdb_transaction_get(tr, keys[i], keySize, 0); + if(getError(fdb_future_block_until_ready(f), "GetManySequential (block for get)", rs)) return -1; + if(getError(fdb_future_get_value(f, &present, &value, &length), "GetManySequential (get result)", rs)) return -1; + fdb_future_destroy(f); + } + double end = getTime(); + + return numKeys / (end - start); +} + +int getRangeBasic(FDBTransaction *tr, struct ResultSet *rs) { + int count; + const FDBKeyValue *kvs; + int more; + int i; + + double start = getTime(); + for(i = 0; i < 100; ++i) { + FDBFuture *f = fdb_transaction_get_range(tr, FDB_KEYSEL_LAST_LESS_OR_EQUAL(keys[0], keySize), FDB_KEYSEL_LAST_LESS_OR_EQUAL(keys[numKeys], keySize), numKeys, 0, 0, 1, 0, 0); + + if(getError(fdb_future_block_until_ready(f), "GetRangeBasic (block for get range)", rs)) return -1; + if(getError(fdb_future_get_keyvalue_array(f, &kvs, &count, &more), "GetRangeBasic (get range results)", rs)) return -1; + + if(count != numKeys) { + fprintf(stderr, "Bad count %d (expected %d)\n", count, numKeys); + addError(rs, "GetRangeBasic bad count"); + return -1; + } + } + double end = getTime(); + + return 100 * numKeys / (end - start); +} + +int singleClearGetRange(FDBTransaction *tr, struct ResultSet *rs) { + int count; + const FDBKeyValue *kvs; + int more; + int i; + + for(i = 0; i < numKeys; i+=2) { + fdb_transaction_clear(tr, keys[i], keySize); + } + + double start = getTime(); + for(i = 0; i < 100; ++i) { + FDBFuture *f = fdb_transaction_get_range(tr, FDB_KEYSEL_LAST_LESS_OR_EQUAL(keys[0], keySize), FDB_KEYSEL_LAST_LESS_OR_EQUAL(keys[numKeys], keySize), numKeys, 0, 0, 1, 0, 0); + + if(getError(fdb_future_block_until_ready(f), "SingleClearGetRange (block for get range)", rs)) return -1; + if(getError(fdb_future_get_keyvalue_array(f, &kvs, &count, &more), "SingleClearGetRange (get range results)", rs)) return -1; + + fdb_future_destroy(f); + + if(count != numKeys/2) { + fprintf(stderr, "Bad count %d (expected %d)\n", count, numKeys); + addError(rs, "SingleClearGetRange bad count"); + return -1; + } + } + double end = getTime(); + + insertData(tr); + return 100 * numKeys / 2 / (end - start); +} + +int clearRangeGetRange(FDBTransaction *tr, struct ResultSet *rs) { + int count; + const FDBKeyValue *kvs; + int more; + int i; + + for(i = 0; i < numKeys; i+=4) { + fdb_transaction_clear_range(tr, keys[i], keySize, keys[i+1], keySize); + } + + double start = getTime(); + for(i = 0; i < 100; ++i) { + FDBFuture *f = fdb_transaction_get_range(tr, FDB_KEYSEL_LAST_LESS_OR_EQUAL(keys[0], keySize), FDB_KEYSEL_LAST_LESS_OR_EQUAL(keys[numKeys], keySize), numKeys, 0, 0, 1, 0, 0); + + if(getError(fdb_future_block_until_ready(f), "ClearRangeGetRange (block for get range)", rs)) return -1; + if(getError(fdb_future_get_keyvalue_array(f, &kvs, &count, &more), "ClearRangeGetRange (get range results)", rs)) return -1; + + fdb_future_destroy(f); + + if(count != numKeys*3/4) { + fprintf(stderr, "Bad count %d (expected %d)\n", count, numKeys*3/4); + addError(rs, "ClearRangeGetRange bad count"); + return -1; + } + } + double end = getTime(); + + insertData(tr); + return 100 * numKeys * 3 / 4 / (end - start); +} + +int interleavedSetsGets(FDBTransaction *tr, struct ResultSet *rs) { + int present; + uint8_t const *value; + int length; + int i; + + uint8_t *k = (uint8_t*)"foo"; + uint8_t v[10]; + int num = 1; + + double start = getTime(); + sprintf((char*)v, "%d", num); + fdb_transaction_set(tr, k, 3, v, strlen((char*)v)); + + for(i = 0; i < 10000; ++i) { + FDBFuture *f = fdb_transaction_get(tr, k, 3, 0); + if(getError(fdb_future_block_until_ready(f), "InterleavedSetsGets (block for get)", rs)) return -1; + if(getError(fdb_future_get_value(f, &present, &value, &length), "InterleavedSetsGets (get result)", rs)) return -1; + fdb_future_destroy(f); + + sprintf((char*)v, "%d", ++num); + fdb_transaction_set(tr, k, 3, v, strlen((char*)v)); + } + double end = getTime(); + + return 10000 / (end - start); +} + +void runTests(struct ResultSet *rs) { + FDBDatabase *db = openDatabase(rs, &netThread); + + FDBTransaction *tr; + checkError(fdb_database_create_transaction(db, &tr), "create transaction", rs); + + FDBFuture *f = fdb_transaction_get_read_version(tr); + checkError(fdb_future_block_until_ready(f), "block for read version", rs); + + int64_t version; + checkError(fdb_future_get_version(f, &version), "get version", rs); + fdb_future_destroy(f); + + insertData(tr); + + runTest(&getSingle, tr, rs, "C: get single cached value throughput"); + runTest(&getManySequential, tr, rs, "C: get sequential cached values throughput"); + runTest(&getRangeBasic, tr, rs, "C: get range cached values throughput"); + runTest(&singleClearGetRange, tr, rs, "C: get range cached values with clears throughput"); + runTest(&clearRangeGetRange, tr, rs, "C: get range cached values with clear ranges throughput"); + runTest(&interleavedSetsGets, tr, rs, "C: interleaved sets and gets on a single key throughput"); + + fdb_database_destroy(db); + fdb_stop_network(); +} + +int main(int argc, char **argv) { + srand(time(NULL)); + struct ResultSet *rs = newResultSet(); + checkError(fdb_select_api_version(500), "select API version", rs); + printf("Running RYW Benchmark test at client version: %s\n", fdb_get_client_version()); + + keys = generateKeys(numKeys, keySize); + runTests(rs); + writeResultSet(rs); + freeResultSet(rs); + freeKeys(keys, numKeys); + + return 0; +} + diff --git a/bindings/c/test/test.h b/bindings/c/test/test.h new file mode 100644 index 0000000000..2a1436dd96 --- /dev/null +++ b/bindings/c/test/test.h @@ -0,0 +1,266 @@ +/* + * test.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project 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 + * + * http://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 +#include + +#include +#include +#include +#include + +#ifndef FDB_API_VERSION +#error "Cannot include test.h without defining FDB_API_VERSION" +#endif + +#include +#include + +double getTime() { + static struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_usec/1000000.0 + tv.tv_sec; +} + +void writeKey(uint8_t **dest, int key, int keySize) { + *dest = (uint8_t*)malloc((sizeof(uint8_t))*keySize); + sprintf((char*)*dest, "%0*d", keySize, key); +} + +uint8_t **generateKeys(int numKeys, int keySize) { + uint8_t **keys = (uint8_t**)malloc(sizeof(uint8_t*)*(numKeys+1)); + + uint32_t i; + for(i = 0; i <= numKeys; ++i) { + writeKey(keys + i, i, keySize); + } + + return keys; +} +void freeKeys(uint8_t **keys, int numKeys) { + uint32_t i; + for(i = 0; i < numKeys; i++) { + free(keys[i]); + } + free(keys); +} + +int cmpfunc(const void* a, const void* b) { + return (*(int*)a - *(int*)b); +} + +int median(int *values, int length) { + qsort(values, length, sizeof(int), cmpfunc); + return values[length/2]; +} + +struct RunResult { + int res; + fdb_error_t e; +}; +#define RES(x, y) (struct RunResult) { x, y } + +struct Kpi { + const char *name; + int value; + const char *units; + + struct Kpi *next; +}; + +struct Error { + char *message; + + struct Error *next; +}; + +struct ResultSet { + struct Kpi *kpis; + struct Error *errors; +}; + +struct ResultSet* newResultSet() { + struct ResultSet *rs = malloc(sizeof(struct ResultSet)); + + rs->kpis = NULL; + rs->errors = NULL; + + return rs; +} + +void addKpi(struct ResultSet *rs, const char *name, int value, const char *units) { + struct Kpi *k = malloc(sizeof(struct Kpi)); + k->name = name; + k->value = value; + k->units = units; + k->next = rs->kpis; + rs->kpis = k; +} + +void addError(struct ResultSet *rs, const char *message) { + struct Error *e = malloc(sizeof(struct Error)); + e->message = (char*)malloc(strlen(message)+1); + strcpy(e->message, message); + e->next = rs->errors; + rs->errors = e; +} + +void writeResultSet(struct ResultSet *rs) { + uint64_t id = ((uint64_t)rand() << 32) + rand(); + char name[100]; + sprintf(name, "fdb-c_result-%llu.json", id); + FILE *fp = fopen(name, "w"); + if(!fp) { + fprintf(stderr, "Could not open results file %s\n", name); + exit(1); + } + + fprintf(fp, "{\n"); + fprintf(fp, "\t\"kpis\": {\n"); + + struct Kpi *k = rs->kpis; + while(k != NULL) { + fprintf(fp, "\t\t\"%s\": { \"units\": \"%s\", \"value\": %d }", k->name, k->units, k->value); + if(k->next != NULL) { + fprintf(fp, ","); + } + fprintf(fp, "\n"); + k = k->next; + } + + fprintf(fp, "\t},\n"); + fprintf(fp, "\t\"errors\": [\n"); + + struct Error *e = rs->errors; + while(e != NULL) { + fprintf(fp, "\t\t\"%s\"", e->message); + if(e->next != NULL) { + fprintf(fp, ","); + } + fprintf(fp, "\n"); + e = e->next; + } + + fprintf(fp, "\t]\n"); + fprintf(fp, "}\n"); + + fclose(fp); +} + +void freeResultSet(struct ResultSet *rs) { + struct Kpi *k = rs->kpis; + while(k != NULL) { + struct Kpi *next = k->next; + free(k); + k = next; + } + + struct Error *e = rs->errors; + while(e != NULL) { + struct Error *next = e->next; + free(e->message); + free(e); + e = next; + } + + free(rs); +} + +fdb_error_t getError(fdb_error_t err, const char* context, struct ResultSet *rs) { + if(err) { + char *msg = (char*)malloc(strlen(context) + 100); + sprintf(msg, "Error in %s: %s", context, fdb_get_error(err)); + fprintf(stderr, "%s\n", msg); + if(rs != NULL) { + addError(rs, msg); + } + + free(msg); + } + + return err; +} + +void checkError(fdb_error_t err, const char* context, struct ResultSet *rs) { + if(getError(err, context, rs)) { + if(rs != NULL) { + writeResultSet(rs); + freeResultSet(rs); + } + exit(1); + } +} + +fdb_error_t maybeLogError(fdb_error_t err, const char* context, struct ResultSet *rs) { + if(err && !fdb_error_predicate( FDB_ERROR_PREDICATE_RETRYABLE, err ) ) { + char *msg = (char*)malloc(strlen(context) + 100); + sprintf(msg, "Error in %s: %s", context, fdb_get_error(err)); + fprintf(stderr, "%s\n", msg); + if(rs != NULL) { + addError(rs, msg); + } + + free(msg); + } + + return err; +} + +fdb_error_t logError(fdb_error_t err, const char* context, struct ResultSet *rs) { + char *msg = (char*)malloc(strlen(context) + 100); + sprintf(msg, "Error in %s: %s", context, fdb_get_error(err)); + fprintf(stderr, "%s\n", msg); + if(rs != NULL) { + addError(rs, msg); + } + + free(msg); + return err; +} + +void* runNetwork() { + checkError(fdb_run_network(), "run network", NULL); + return NULL; +} + + +FDBDatabase* openDatabase(struct ResultSet *rs, pthread_t *netThread) { + checkError(fdb_setup_network(), "setup network", rs); + pthread_create(netThread, NULL, &runNetwork, NULL); + + FDBFuture *f = fdb_create_cluster(NULL); + checkError(fdb_future_block_until_ready(f), "block for cluster", rs); + + FDBCluster *cluster; + checkError(fdb_future_get_cluster(f, &cluster), "get cluster", rs); + + fdb_future_destroy(f); + + f = fdb_cluster_create_database(cluster, (uint8_t*)"DB", 2); + checkError(fdb_future_block_until_ready(f), "block for database", rs); + + FDBDatabase *db; + checkError(fdb_future_get_database(f, &db), "get database", rs); + + fdb_future_destroy(f); + fdb_cluster_destroy(cluster); + + return db; +}