aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/008-tree-store-edit-cost/bench.c
diff options
context:
space:
mode:
Diffstat (limited to 'research/experiments/008-tree-store-edit-cost/bench.c')
-rw-r--r--research/experiments/008-tree-store-edit-cost/bench.c104
1 files changed, 104 insertions, 0 deletions
diff --git a/research/experiments/008-tree-store-edit-cost/bench.c b/research/experiments/008-tree-store-edit-cost/bench.c
new file mode 100644
index 0000000..f16f8db
--- /dev/null
+++ b/research/experiments/008-tree-store-edit-cost/bench.c
@@ -0,0 +1,104 @@
+/* Durable edit cost of one selected byte store, through its public API only.
+ * Built once per store source; the store directory must be on the device
+ * being measured (not tmpfs, where fsync is free). */
+#define _POSIX_C_SOURCE 200809L
+#include "store.h"
+#include <dirent.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+
+static double now_ms(void) {
+ struct timespec t;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ return (double)t.tv_sec * 1e3 + (double)t.tv_nsec / 1e6;
+}
+
+/* Linux accounting: wchar is bytes passed to write calls, write_bytes is
+ * bytes this process caused to be sent to the block layer. */
+static uint64_t io_field(const char *name) {
+ FILE *file = fopen("/proc/self/io", "r");
+ char line[128];
+ uint64_t value = 0;
+ size_t length = strlen(name);
+ while (file && fgets(line, sizeof(line), file))
+ if (!strncmp(line, name, length) && line[length] == ':') value = strtoull(line + length + 1, NULL, 10);
+ if (file) fclose(file);
+ return value;
+}
+
+static uint64_t store_bytes(const char *path) {
+ DIR *directory = opendir(path);
+ struct dirent *entry;
+ uint64_t total = 0;
+ char name[4096];
+ while (directory && (entry = readdir(directory))) {
+ struct stat info;
+ snprintf(name, sizeof(name), "%s/%s", path, entry->d_name);
+ if (!stat(name, &info) && S_ISREG(info.st_mode)) total += (uint64_t)info.st_size;
+ }
+ if (directory) closedir(directory);
+ return total;
+}
+
+static int compare(const void *a, const void *b) {
+ double x = *(const double *)a, y = *(const double *)b;
+ return (x > y) - (x < y);
+}
+
+int main(int argc, char **argv) {
+ if (argc != 6) {
+ fputs("bench STORE_NAME DIRECTORY SIZE insert-middle|delete-middle|replace-end|append-end COUNT\n", stderr);
+ return 2;
+ }
+ const char *name = argv[1], *path = argv[2], *op = argv[4];
+ size_t size = strtoull(argv[3], NULL, 10), count = strtoull(argv[5], NULL, 10);
+ unsigned char *body = malloc(size + 1);
+ double *samples = malloc(count * sizeof(*samples)), reads[16];
+ if (!body || !samples || pure_store_init(path) != PURE_OK) return 1;
+ for (size_t i = 0; i < size; ++i) body[i] = (unsigned char)('a' + i % 26);
+ int64_t revision;
+ if (pure_store_put(path, "doc", 0, (PureSpan){body, size}, &revision) != PURE_OK) return 1;
+ size_t length = size;
+ uint64_t wchar = io_field("wchar"), device = io_field("write_bytes");
+ for (size_t i = 0; i < count; ++i) {
+ size_t offset, remove, insert;
+ if (!strcmp(op, "insert-middle")) { offset = length / 2; remove = 0; insert = 1; }
+ else if (!strcmp(op, "delete-middle")) { offset = length / 2; remove = 1; insert = 0; }
+ else if (!strcmp(op, "replace-end")) { offset = length - 1; remove = 1; insert = 1; }
+ else if (!strcmp(op, "append-end")) { offset = length; remove = 0; insert = 1; }
+ else return 2;
+ unsigned char byte = (unsigned char)('A' + i % 26);
+ double start = now_ms();
+ PureResult result = pure_store_splice(path, "doc", revision, offset, remove,
+ (PureSpan){&byte, insert}, PURE_OBJECT_LIMIT, &revision);
+ samples[i] = now_ms() - start;
+ if (result != PURE_OK) { fprintf(stderr, "splice failed: %d\n", (int)result); return 1; }
+ length = length - remove + insert;
+ }
+ wchar = io_field("wchar") - wchar;
+ device = io_field("write_bytes") - device;
+ uint64_t checksum = 0;
+ for (size_t i = 0; i < 16; ++i) {
+ PureStoredView view;
+ double start = now_ms();
+ if (pure_store_get(path, "doc", PURE_OBJECT_LIMIT, &view) != PURE_OK || view.body.size != length) return 1;
+ for (size_t k = 0; k < view.body.size; k += 64) checksum += view.body.data[k];
+ pure_store_release(&view);
+ reads[i] = now_ms() - start;
+ }
+ qsort(samples, count, sizeof(*samples), compare);
+ qsort(reads, 16, sizeof(*reads), compare);
+ printf("{\"store\":\"%s\",\"op\":\"%s\",\"document_bytes\":%zu,\"edits\":%zu,"
+ "\"edit_median_ms\":%.3f,\"edit_p90_ms\":%.3f,\"edit_max_ms\":%.3f,"
+ "\"write_call_bytes_per_edit\":%" PRIu64 ",\"device_write_bytes_per_edit\":%" PRIu64 ","
+ "\"store_file_bytes_after\":%" PRIu64 ",\"whole_read_median_ms\":%.3f,\"read_checksum\":%" PRIu64 "}\n",
+ name, op, size, count, samples[count / 2], samples[count * 9 / 10], samples[count - 1],
+ wchar / count, device / count, store_bytes(path), reads[8], checksum);
+ free(samples);
+ free(body);
+ return 0;
+}