X-Git-Url: https://code.grnet.gr/git/archipelago/blobdiff_plain/d38b3cdae5d747ae5d685fe3ce8027fc0c265e79..7456427f08f4d118f73f817d0df6559442e81f2e:/xseg/peers/user/bench-utils.c diff --git a/xseg/peers/user/bench-utils.c b/xseg/peers/user/bench-utils.c index ea3d8fe..77416d9 100644 --- a/xseg/peers/user/bench-utils.c +++ b/xseg/peers/user/bench-utils.c @@ -47,9 +47,157 @@ #include #include +#include + +#define PRINT_SIG(__who, __sig) \ + fprintf(stdout, "%s (%lu): id %lu, object %lu, offset %lu\n", \ + #__who, (uint64_t)(__sig), \ + ((struct signature *)__sig)->id, \ + ((struct signature *)__sig)->object, \ + ((struct signature *)__sig)->offset); struct timespec delay = {0, 4000000}; +__attribute__ ((unused)) +void inspect_obv(struct object_vars *obv) +{ + XSEGLOG2(&lc, D, "Struct object vars:\n" + "\tname: %s (%d),\n" + "\tprefix: %s (%d),\n" + "\tseed: %lu (%d),\n" + "\tobjnum: %lu (%d)", + obv->name, obv->namelen, obv->prefix, obv->prefixlen, + obv->seed, obv->seedlen, + obv->objnum, obv->objnumlen); +} + +uint64_t __get_object(struct bench *prefs, uint64_t new) +{ + if (prefs->ts > 0) + new = new / (prefs->os / prefs->bs); + return new; +} + +/******************************\ + * Static miscellaneous tools * +\******************************/ +static inline uint64_t __get_object_from_name(struct object_vars *obv, + char *name) +{ + /* In case of --objname switch */ + if (obv->name[0]) + return 0; + + /* Keep only the object number */ + return atol(name + obv->namelen - obv->objnumlen); +} + +static inline int __snap_to_bound8(uint64_t space) +{ + return space > 8 ? 8 : space; +} + +static inline double __timespec2double(struct timespec num) +{ + return (double) (num.tv_sec * pow(10, 9) + num.tv_nsec); +} + +static inline void __write_sig(struct bench_lfsr *sg, uint64_t *d, uint64_t s, + int pos) +{ + uint64_t i; + uint64_t last_val; + uint64_t space_left; + + /* Write random numbers (based on global_id) every 24 bytes */ + /* TODO: Should we use memcpy? */ + for (i = pos; i < (s / 8) - (3 - pos); i += 3) + *(d + i) = lfsr_next(sg); + + /* special care for last chunk */ + last_val = lfsr_next(sg); + space_left = s - (i * 8); + memcpy(d + i, &last_val, __snap_to_bound8(space_left)); +} + +static inline int __read_sig(struct bench_lfsr *sg, uint64_t *d, uint64_t s, + int pos) +{ + uint64_t i; + uint64_t last_val; + uint64_t space_left; + + /* TODO: Should we use memcmp? */ + for (i = pos; i < (s / 8) - (3 - pos); i += 3) { + if (*(d + i) != lfsr_next(sg)) + return 1; + } + /* special care for last chunk */ + last_val = lfsr_next(sg); + space_left = s - (i * 8); + if (memcmp(d + i, &last_val, __snap_to_bound8(space_left))) + return 1; + + return 0; +} + +/* + * Seperates a double number in seconds, msec, usec, nsec + * Expects a number in nanoseconds (e.g. a number from timespec2double) + */ +static struct tm_result __separate_by_order(double num) +{ + struct tm_result res; + + //The format we expect is the following: + // + // |-s-|-ms-|-us-|-ns| + //num = 123 456 789 012 . 000000000000 + res.s = num / pow(10,9); + num = fmod(num, pow(10,9)); + res.ms = num / pow(10,6); + num = fmod(num, pow(10,6)); + res.us = num / 1000; + res.ns = fmod(num, 1000); + + return res; +} + +static void __calculate_bw(struct bench *prefs, double iops, struct bw *bw) +{ + bw->val = iops * prefs->bs; + strcpy(bw->unit, "B/s"); + + if (bw->val < 1024) + return; + + bw->val = bw->val / 1024; + strcpy(bw->unit, "KB/s"); + + if (bw->val < 1024) + return; + + bw->val = bw->val / 1024; + strcpy(bw->unit, "MB/s"); + + if (bw->val < 1024) + return; + + bw->val = bw->val / 1024; + strcpy(bw->unit, "GB/s"); +} + +static double __calculate_iops(struct bench *prefs, double elapsed_ns) +{ + /* elapsed_ns is in nanoseconds, so we convert it to seconds */ + double elapsed = elapsed_ns / pow(10,9); + return (prefs->status->received / elapsed); +} + +/******************************\ + * Argument-parsing functions * +\******************************/ + /* * Convert string to size in bytes. * If syntax is invalid, return 0. Values such as zero and non-integer @@ -86,154 +234,395 @@ uint64_t str2num(char *str) /* * Converts struct timespec to double (units in nanoseconds) */ -static double timespec2double(struct timespec num) -{ - return (double) (num.tv_sec * pow(10, 9) + num.tv_nsec); -} - int read_insanity(char *insanity) { - if (strcmp(insanity, "sane") == 0) - return TM_SANE; - if (strcmp(insanity, "eccentric") == 0) - return TM_ECCENTRIC; - if (strcmp(insanity, "manic") == 0) - return TM_MANIC; - if (strcmp(insanity, "paranoid") == 0) - return TM_PARANOID; + if (strncmp(insanity, "sane", MAX_ARG_LEN + 1) == 0) + return INSANITY_SANE; + if (strncmp(insanity, "eccentric", MAX_ARG_LEN + 1) == 0) + return INSANITY_ECCENTRIC; + if (strncmp(insanity, "manic", MAX_ARG_LEN + 1) == 0) + return INSANITY_MANIC; + if (strncmp(insanity, "paranoid", MAX_ARG_LEN + 1) == 0) + return INSANITY_PARANOID; return -1; } int read_op(char *op) { - if (strcmp(op, "read") == 0) + if (strncmp(op, "read", MAX_ARG_LEN + 1) == 0) return X_READ; - if (strcmp(op, "write") == 0) + if (strncmp(op, "write", MAX_ARG_LEN + 1) == 0) return X_WRITE; - if (strcmp(op, "info") == 0) + if (strncmp(op, "info", MAX_ARG_LEN + 1) == 0) return X_INFO; - if (strcmp(op, "delete") == 0) + if (strncmp(op, "delete", MAX_ARG_LEN + 1) == 0) return X_DELETE; return -1; } +int read_verify(char *verify) +{ + if (strncmp(verify, "no", MAX_ARG_LEN + 1) == 0) + return VERIFY_NO; + if (strncmp(verify, "meta", MAX_ARG_LEN + 1) == 0) + return VERIFY_META; + if (strncmp(verify, "full", MAX_ARG_LEN + 1) == 0) + return VERIFY_FULL; + return -1; +} + +int read_progress(char *progress) +{ + if (strncmp(progress, "no", MAX_ARG_LEN + 1) == 0) + return PROGRESS_NO; + if (strncmp(progress, "yes", MAX_ARG_LEN + 1) == 0) + return PROGRESS_YES; + return -1; +} + +int read_ping(char *ping) +{ + if (strncmp(ping, "no", MAX_ARG_LEN + 1) == 0) + return PING_MODE_OFF; + if (strncmp(ping, "yes", MAX_ARG_LEN + 1) == 0) + return PING_MODE_ON; + return -1; +} + int read_pattern(char *pattern) { - if (strcmp(pattern, "seq") == 0) - return IO_SEQ; - if (strcmp(pattern, "rand") == 0) - return IO_RAND; + if (strncmp(pattern, "seq", MAX_ARG_LEN + 1) == 0) + return PATTERN_SEQ; + if (strncmp(pattern, "rand", MAX_ARG_LEN + 1) == 0) + return PATTERN_RAND; return -1; } -/* - * Seperates a double number in seconds, msec, usec, nsec - * Expects a number in nanoseconds (e.g. a number from timespec2double) - */ -static struct tm_result separate_by_order(double num) +int validate_seed(struct bench *prefs, unsigned long seed) { - struct tm_result res; + if (seed < pow(10, prefs->objvars->seedlen)) + return 0; + return -1; +} - //The format we expect is the following: - // - // |-s-|-ms-|-us-|-ns| - //num = 123 456 789 012 . 000000000000 - res.s = num / pow(10,9); - num = fmod(num, pow(10,9)); - res.ms = num / pow(10,6); - num = fmod(num, pow(10,6)); - res.us = num / 1000; - res.ns = fmod(num, 1000); +/*******************\ + * Print functions * +\*******************/ - return res; +void print_io_stats(struct bench *prefs, double elapsed) +{ + struct bw bw; + double iops; + + /* + * We could malloc struct bw in __calculate_bw, but it's safer in cases when + * there is no memory left. + */ + iops = __calculate_iops(prefs, elapsed); + __calculate_bw(prefs, iops, &bw); + + fprintf(stdout, " ~~~~~~~~~~~~~~~~~~~~~~~~\n"); + if (prefs->op == X_READ || prefs->op == X_WRITE) + fprintf(stdout, "Bandwidth: %.3lf %s\n", bw.val, bw.unit); + fprintf(stdout, "IOPS: %.3lf\n", iops); } void print_stats(struct bench *prefs) { - uint64_t remaining; + fprintf(stdout, "\n" + "Requests total: %10lu\n" + "Requests submitted: %10lu\n" + "Requests received: %10lu\n" + "Requests failed: %10lu\n", + prefs->status->max, + prefs->status->submitted, + prefs->status->received, + prefs->status->failed); + if ((prefs->op == X_READ) && (GET_FLAG(VERIFY, prefs->flags) != VERIFY_NO)) + fprintf(stdout, "Requests corrupted: %10lu\n", prefs->status->corrupted); + fprintf(stdout, "\n"); + fflush(stdout); +} - printf("\n"); - printf("Requests total: %10lu\n", prefs->max_requests); - printf("Requests submitted: %10lu\n", prefs->sub_tm->completed); - printf("Requests received: %10lu\n", prefs->rec_tm->completed); - printf("\n"); +void print_remaining(struct bench *prefs) +{ + uint64_t remaining; - remaining = prefs->max_requests - prefs->rec_tm->completed; + remaining = prefs->status->max - prefs->status->received; if (remaining) - printf("Requests remaining: %10lu\n", remaining); + fprintf(stdout, "Requests remaining: %10lu\n", remaining); else - printf("All requests have been served.\n"); + fprintf(stdout, "All requests have been served.\n"); + fflush(stdout); } -void print_res(struct bench *prefs, struct timer *tm, char *type) +void print_res(struct bench *prefs) { - struct tm_result res; - double sum; + struct timer *tm; + struct tm_result res, res_rec; + double sum, sum_rec; - sum = timespec2double(tm->sum); - res = separate_by_order(sum); + /* */ + tm = prefs->total_tm; + sum = __timespec2double(tm->sum); + res = __separate_by_order(sum); - printf("\n"); - printf(" %s\n", type); - printf("=======================================\n"); - printf(" |-s-||-ms-|-us-|-ns-|\n"); - printf("Total time: %3u. %03u %03u %03u\n", + fprintf(stdout, "\n"); + fprintf(stdout, " Benchmark results\n"); + fprintf(stdout, " ========================\n"); + fprintf(stdout, " |-s-||-ms-|-us-|-ns-|\n"); + fprintf(stdout, "Total time: %3u. %03u %03u %03u\n", res.s, res.ms, res.us, res.ns); - res = separate_by_order(sum / prefs->rec_tm->completed); + if (!prefs->status->received) { + fflush(stdout); + return; + } - printf("Mean Time: %3u. %03u %03u %03u\n", - res.s, res.ms, res.us, res.ns); + tm = prefs->rec_tm; + if (GET_FLAG(INSANITY, prefs->flags) < tm->insanity) + goto flush; - //TODO: Add std + sum_rec = __timespec2double(tm->sum); + res_rec = __separate_by_order(sum_rec / prefs->status->received); + + fprintf(stdout, "Avg. latency: %3u. %03u %03u %03u\n", + res_rec.s, res_rec.ms, res_rec.us, res_rec.ns); + +flush: + print_io_stats(prefs, sum); + fflush(stdout); } -void create_target(struct bench *prefs, struct xseg_request *req, - uint64_t new) +void print_progress(struct bench *prefs) +{ + int lines = 6; + + if ((prefs->op == X_READ) && (GET_FLAG(VERIFY, prefs->flags) != VERIFY_NO)) + lines++; + + fprintf(stdout, "\033[%dA\033[J", lines); + print_stats(prefs); +} + +/**************************\ + * Benchmarking functions * +\**************************/ + +void create_target(struct bench *prefs, struct xseg_request *req) { struct xseg *xseg = prefs->peer->xseg; + struct object_vars *obv = prefs->objvars; char *req_target; req_target = xseg_get_target(xseg, req); - //For read/write, the target object does not correspond to `new`, which is - //actually the chunk number. We need to div this number with the number of - //chunks in an object. - //FIXME: Make it more elegant - if (prefs->op == X_READ || prefs->op == X_WRITE) - new = new / (prefs->os / prefs->bs); - snprintf(req_target, TARGETLEN, "%s-%016lu", global_id, new); + /* + * For read/write, the target object may not correspond to `new`, which + * is actually the chunk number. + * Also, we use one extra byte while writting the target's name to store + * the null character and not overflow, but this will not be part of the + * target's name + */ + if (obv->prefix[0]) { + snprintf(req_target, obv->namelen + 1, "%s-%0*lu-%0*lu", + obv->prefix, obv->seedlen, obv->seed, + obv->objnumlen, obv->objnum); + } else { + strncpy(req_target, obv->name, obv->namelen); + } XSEGLOG2(&lc, D, "Target name of request is %s\n", req_target); } -void create_chunk(struct bench *prefs, struct xseg_request *req, uint64_t new) -{/* +uint64_t determine_next(struct bench *prefs) +{ + if (GET_FLAG(PATTERN, prefs->flags) == PATTERN_SEQ) + return prefs->status->submitted; + else + return lfsr_next(prefs->lfsr); +} + +uint64_t calculate_offset(struct bench *prefs, uint64_t new) +{ + if (prefs->ts > 0) + return (new * prefs->bs) % prefs->os; + else + return 0; +} + +uint64_t calculate_prog_quantum(struct bench *prefs) +{ + return round((double)prefs->status->max / 20.0); +} + + +/* + * *********************************************** + * `create_chunk` handles 3 identifiers: + * 1. The benchmark's global_id + * 2. The object's number + * 3. The chunk offset in the object + * + * ************************************************ + * `readwrite_chunk_full` takes the above 3 identifiers and feeds them as seeds + * in 63-bit LFSRs. The numbers generated are written consecutively in chunk's + * memory range. For example, for a 72-byte chunk: + * + * || 1 | 2 | 3 | 1 | 2 | 3 | 1 | 2 | 3 || + * ^ 8 16 24 32 40 48 56 64 ^ + * | | + * | | + * start end + * + * 1,2,3 differ between each iteration + * + * ************************************************** + * `_create_chunk_meta` simply writes the above 3 ids in the start and end of + * the chunk's memory range, so it should be much faster (but less safe) + * + * ************************************************** + * In both cases, special care is taken not to exceed the chunk's memory range. + * Also, the bare minimum chunk to verify should be 48 bytes. This limit is set + * by readwrite_chunk_meta, which expects to write in a memory at least this + * big. + * + * ************************************************** + * Note: The diagram above also represents the x86_64's endianness. + * Endianness must be taken into careful consideration when examining a memory + * chunk. + */ +static int readwrite_chunk_full(struct bench *prefs, struct xseg_request *req) +{ + struct bench_lfsr id_lfsr; + struct bench_lfsr obj_lfsr; + struct bench_lfsr off_lfsr; struct xseg *xseg = prefs->peer->xseg; - char *req_data; + uint64_t id = prefs->objvars->seed; + uint64_t object = prefs->objvars->objnum; + uint64_t *d = (uint64_t *)xseg_get_data(xseg, req); + uint64_t s = req->size; + + /* Create 63-bit LFSRs */ + lfsr_init(&id_lfsr, 0x7FFFFFFFFFFFFFFF, id, 0); + lfsr_init(&obj_lfsr, 0x7FFFFFFFFFFFFFFF, object, 0); + lfsr_init(&off_lfsr, 0x7FFFFFFFFFFFFFFF, req->offset, 0); + + if (s < sizeof(struct signature)) { + XSEGLOG2(&lc, E, "Too small chunk size (%lu butes). Leaving.", s); + return 1; + } - //TODO: Fill data depening on validation level - req_data = xseg_get_data(xseg, req); - */ + /* + * Every write operation has its read counterpart which, if it finds any + * corruption, returns 1 + */ + + if (req->op == X_WRITE) { + __write_sig(&id_lfsr, d, s, 0); + __write_sig(&obj_lfsr, d, s, 1); + __write_sig(&off_lfsr, d, s, 2); + } else { + if (__read_sig(&id_lfsr, d, s, 0)) + return 1; + if (__read_sig(&obj_lfsr, d, s, 1)) + return 1; + if(__read_sig(&off_lfsr, d, s, 2)) + return 1; + } + + return 0; } -uint64_t determine_next(struct bench *prefs) +static int readwrite_chunk_meta(struct bench *prefs, struct xseg_request *req) { - if ((prefs->flags & (1 << PATTERN_FLAG)) == IO_SEQ) - return prefs->sub_tm->completed; - else { - return lfsr_next(prefs->lfsr); + struct xseg *xseg = prefs->peer->xseg; + struct signature sig; + uint64_t id = prefs->objvars->seed; + uint64_t object = prefs->objvars->objnum; + char *d = xseg_get_data(xseg, req); + uint64_t s = req->size; + int sig_s = sizeof(struct signature); + int r = 0; + + sig.id = id; + sig.object = object; + sig.offset = req->offset; + + if (s < sig_s) { + XSEGLOG2(&lc, E, "Too small chunk size (%lu butes). " + "Leaving.", s); + return 1; } + + //PRINT_SIG(expected, (&sig)); + /* Read/Write chunk signature both at its start and at its end */ + if (req->op == X_WRITE) { + memcpy(d, &sig, sig_s); + memcpy(d + s - sig_s, &sig, sig_s); + } else { + if (memcmp(d, &sig, sig_s)) + r = 1; + else if (memcmp(d + s - sig_s, &sig, sig_s)) + r = 1; + } + //PRINT_SIG(start, d); + //PRINT_SIG(end, (d + s - sig_s)); + return r; } -//FIXME: this looks like a hack, handle it more elegantly -void create_id() +/* + * We want these functions to be as fast as possible in case we haven't asked + * for verification + */ +void create_chunk(struct bench *prefs, struct xseg_request *req, uint64_t new) { - struct timespec seed; + int verify; + + verify = GET_FLAG(VERIFY, prefs->flags); + switch (verify) { + case VERIFY_NO: + break; + case VERIFY_META: + inspect_obv(prefs->objvars); + readwrite_chunk_meta(prefs, req); + break; + case VERIFY_FULL: + inspect_obv(prefs->objvars); + readwrite_chunk_full(prefs, req); + break; + default: + XSEGLOG2(&lc, W, "Unexpected verification mode: %d\n", + verify); + } +} - clock_gettime(CLOCK_MONOTONIC_RAW, &seed); +int read_chunk(struct bench *prefs, struct xseg_request *req) +{ + struct xseg *xseg = prefs->peer->xseg; + struct object_vars *obv = prefs->objvars; + char *target; + int verify; + int r = 0; - global_seed = seed.tv_nsec; - //nanoseconds can't be more than 9 digits - snprintf(global_id, IDLEN, "bench-%09lu", global_seed); - XSEGLOG2(&lc, I, "Global ID is %s\n", global_id); + verify = GET_FLAG(VERIFY, prefs->flags); + switch (verify) { + case VERIFY_NO: + break; + case VERIFY_META: + target = xseg_get_target(xseg, req); + obv->objnum = __get_object_from_name(obv, target); + inspect_obv(prefs->objvars); + r = readwrite_chunk_meta(prefs, req); + break; + case VERIFY_FULL: + target = xseg_get_target(xseg, req); + obv->objnum = __get_object_from_name(obv, target); + inspect_obv(prefs->objvars); + r = readwrite_chunk_full(prefs, req); + break; + default: + XSEGLOG2(&lc, W, "Unexpected verification mode: %d\n", + verify); + } + return r; }