aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/008-tree-store-edit-cost/bench.c
blob: f16f8dbffef3ece1ce532acfd285b3ea4d9a8e7b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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;
}