bench: Fix minor validation issue
[archipelago] / xseg / peers / user / bench-utils.c
1 /*
2  * Copyright 2012 GRNET S.A. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or
5  * without modification, are permitted provided that the following
6  * conditions are met:
7  *
8  *   1. Redistributions of source code must retain the above
9  *      copyright notice, this list of conditions and the following
10  *      disclaimer.
11  *   2. Redistributions in binary form must reproduce the above
12  *      copyright notice, this list of conditions and the following
13  *      disclaimer in the documentation and/or other materials
14  *      provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  *
29  * The views and conclusions contained in the software and
30  * documentation are those of the authors and should not be
31  * interpreted as representing official policies, either expressed
32  * or implied, of GRNET S.A.
33  */
34
35 #define _GNU_SOURCE
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <unistd.h>
39 #include <sys/syscall.h>
40 #include <sys/types.h>
41 #include <pthread.h>
42 #include <xseg/xseg.h>
43 #include <peer.h>
44 #include <time.h>
45 #include <sys/util.h>
46 #include <signal.h>
47 #include <bench-xseg.h>
48
49 #include <math.h>
50 #include <string.h>
51
52 #define PRINT_SIG(__who, __sig)                                         \
53         fprintf(stdout, "%s (%lu): id %lu, object %lu, offset %lu\n",   \
54                         #__who, (uint64_t)(__sig),                      \
55                         ((struct signature *)__sig)->id,                \
56                         ((struct signature *)__sig)->object,            \
57                         ((struct signature *)__sig)->offset);
58
59 struct timespec delay = {0, 4000000};
60
61 /******************************\
62  * Static miscellaneous tools *
63 \******************************/
64 static inline uint64_t __get_id()
65 {
66         return atol(global_id + 6); /* cut the "bench-" part*/
67 }
68
69 static inline uint64_t __get_object_from_name(char *name)
70 {
71         return atol(name + IDLEN + 1); /* cut the "bench-908135-" part*/
72 }
73
74 static inline uint64_t __get_object(struct bench *prefs, uint64_t new)
75 {
76         if (prefs->ts > 0)
77                 new = new / (prefs->os / prefs->bs);
78         return new;
79 }
80
81 static inline int __snap_to_bound8(uint64_t space)
82 {
83         return space > 8 ? 8 : space;
84 }
85
86 static inline double __timespec2double(struct timespec num)
87 {
88         return (double) (num.tv_sec * pow(10, 9) + num.tv_nsec);
89 }
90
91 static inline void __write_sig(struct bench_lfsr *sg, uint64_t *d, uint64_t s,
92                 int pos)
93 {
94         uint64_t i;
95         uint64_t last_val;
96         uint64_t space_left;
97
98         /* Write random numbers (based on global_id) every 24 bytes */
99         /* TODO: Should we use memcpy? */
100         for (i = pos; i < (s / 8) - (3 - pos); i += 3)
101                 *(d + i) = lfsr_next(sg);
102
103         /* special care for last chunk */
104         last_val = lfsr_next(sg);
105         space_left = s - (i * 8);
106         memcpy(d + i, &last_val, __snap_to_bound8(space_left));
107 }
108
109 static inline int __read_sig(struct bench_lfsr *sg, uint64_t *d, uint64_t s,
110                 int pos)
111 {
112         uint64_t i;
113         uint64_t last_val;
114         uint64_t space_left;
115
116         /* TODO: Should we use memcmp? */
117         for (i = pos; i < (s / 8) - (3 - pos); i += 3) {
118                 if (*(d + i) != lfsr_next(sg))
119                         return 1;
120         }
121         /* special care for last chunk */
122         last_val = lfsr_next(sg);
123         space_left = s - (i * 8);
124         if (memcmp(d + i, &last_val, __snap_to_bound8(space_left)))
125                 return 1;
126
127         return 0;
128 }
129
130 /*
131  * Seperates a double number in seconds, msec, usec, nsec
132  * Expects a number in nanoseconds (e.g. a number from timespec2double)
133  */
134 static struct tm_result __separate_by_order(double num)
135 {
136         struct tm_result res;
137
138         //The format we expect is the following:
139         //
140         //              |-s-|-ms-|-us-|-ns|
141         //num =  123 456  789  012 . 000000000000
142         res.s = num / pow(10,9);
143         num = fmod(num, pow(10,9));
144         res.ms = num / pow(10,6);
145         num = fmod(num, pow(10,6));
146         res.us = num / 1000;
147         res.ns = fmod(num, 1000);
148
149         return res;
150 }
151
152 static void __calculate_bw(struct bench *prefs, double iops, struct bw *bw)
153 {
154         bw->val = iops * prefs->bs;
155         strcpy(bw->unit, "B/s");
156
157         if (bw->val < 1024)
158                 return;
159
160         bw->val = bw->val / 1024;
161         strcpy(bw->unit, "KB/s");
162
163         if (bw->val < 1024)
164                 return;
165
166         bw->val = bw->val / 1024;
167         strcpy(bw->unit, "MB/s");
168
169         if (bw->val < 1024)
170                 return;
171
172         bw->val = bw->val / 1024;
173         strcpy(bw->unit, "GB/s");
174 }
175
176 static double __calculate_iops(struct bench *prefs, double elapsed_ns)
177 {
178         /* elapsed_ns is in nanoseconds, so we convert it to seconds */
179         double elapsed = elapsed_ns / pow(10,9);
180         return (prefs->status->received / elapsed);
181 }
182
183 /******************************\
184  * Argument-parsing functions *
185 \******************************/
186
187 /*
188  * Convert string to size in bytes.
189  * If syntax is invalid, return 0. Values such as zero and non-integer
190  * multiples of segment's page size should not be accepted.
191  */
192 uint64_t str2num(char *str)
193 {
194         char *unit;
195         uint64_t num;
196
197         num = strtoll(str, &unit, 10);
198         if (strlen(unit) > 1) //Invalid syntax
199                 return 0;
200         else if (strlen(unit) < 1) //Plain number in bytes
201                 return num;
202
203         switch (*unit) {
204                 case 'g':
205                 case 'G':
206                         num *= 1024;
207                 case 'm':
208                 case 'M':
209                         num *= 1024;
210                 case 'k':
211                 case 'K':
212                         num *= 1024;
213                         break;
214                 default:
215                         num = 0;
216         }
217         return num;
218 }
219
220 /*
221  * Converts struct timespec to double (units in nanoseconds)
222  */
223 int read_insanity(char *insanity)
224 {
225         if (strncmp(insanity, "sane", MAX_ARG_LEN + 1) == 0)
226                 return INSANITY_SANE;
227         if (strncmp(insanity, "eccentric", MAX_ARG_LEN + 1) == 0)
228                 return INSANITY_ECCENTRIC;
229         if (strncmp(insanity, "manic", MAX_ARG_LEN + 1) == 0)
230                 return INSANITY_MANIC;
231         if (strncmp(insanity, "paranoid", MAX_ARG_LEN + 1) == 0)
232                 return INSANITY_PARANOID;
233         return -1;
234 }
235
236 int read_op(char *op)
237 {
238         if (strncmp(op, "read", MAX_ARG_LEN + 1) == 0)
239                 return X_READ;
240         if (strncmp(op, "write", MAX_ARG_LEN + 1) == 0)
241                 return X_WRITE;
242         if (strncmp(op, "info", MAX_ARG_LEN + 1) == 0)
243                 return X_INFO;
244         if (strncmp(op, "delete", MAX_ARG_LEN + 1) == 0)
245                 return X_DELETE;
246         return -1;
247 }
248
249 int read_verify(char *verify)
250 {
251         if (strncmp(verify, "no", MAX_ARG_LEN + 1) == 0)
252                 return VERIFY_NO;
253         if (strncmp(verify, "meta", MAX_ARG_LEN + 1) == 0)
254                 return VERIFY_META;
255         if (strncmp(verify, "full", MAX_ARG_LEN + 1) == 0)
256                 return VERIFY_FULL;
257         return -1;
258 }
259
260 int read_progress(char *progress)
261 {
262         if (strncmp(progress, "no", MAX_ARG_LEN + 1) == 0)
263                 return PROGRESS_NO;
264         if (strncmp(progress, "yes", MAX_ARG_LEN + 1) == 0)
265                 return PROGRESS_YES;
266         return -1;
267 }
268
269 int read_ping(char *ping)
270 {
271         if (strncmp(ping, "no", MAX_ARG_LEN + 1) == 0)
272                 return PING_MODE_OFF;
273         if (strncmp(ping, "yes", MAX_ARG_LEN + 1) == 0)
274                 return PING_MODE_ON;
275         return -1;
276 }
277
278 int read_pattern(char *pattern)
279 {
280         if (strncmp(pattern, "seq", MAX_ARG_LEN + 1) == 0)
281                 return PATTERN_SEQ;
282         if (strncmp(pattern, "rand", MAX_ARG_LEN + 1) == 0)
283                 return PATTERN_RAND;
284         return -1;
285 }
286
287 /*******************\
288  * Print functions *
289 \*******************/
290
291 void print_io_stats(struct bench *prefs, double elapsed)
292 {
293         struct bw bw;
294         double iops;
295
296         /*
297          * We could malloc struct bw in __calculate_bw, but it's safer in cases when
298          * there is no memory left.
299          */
300         iops = __calculate_iops(prefs, elapsed);
301         __calculate_bw(prefs, iops, &bw);
302
303         fprintf(stdout, "           ~~~~~~~~~~~~~~~~~~~~~~~~\n");
304         if (prefs->op == X_READ || prefs->op == X_WRITE)
305                 fprintf(stdout, "Bandwidth:    %.3lf %s\n", bw.val, bw.unit);
306         fprintf(stdout, "IOPS:         %.3lf\n", iops);
307 }
308
309 void print_stats(struct bench *prefs)
310 {
311         fprintf(stdout, "\n"
312                         "Requests total:     %10lu\n"
313                         "Requests submitted: %10lu\n"
314                         "Requests received:  %10lu\n"
315                         "Requests failed:    %10lu\n",
316                         prefs->status->max,
317                         prefs->status->submitted,
318                         prefs->status->received,
319                         prefs->status->failed);
320         if ((prefs->op == X_READ) && (GET_FLAG(VERIFY, prefs->flags) != VERIFY_NO))
321                 fprintf(stdout, "Requests corrupted: %10lu\n", prefs->status->corrupted);
322         fprintf(stdout, "\n");
323         fflush(stdout);
324 }
325
326 void print_remaining(struct bench *prefs)
327 {
328         uint64_t remaining;
329
330         remaining = prefs->status->max - prefs->status->received;
331         if (remaining)
332                 fprintf(stdout, "Requests remaining: %10lu\n", remaining);
333         else
334                 fprintf(stdout, "All requests have been served.\n");
335         fflush(stdout);
336 }
337
338 void print_res(struct bench *prefs)
339 {
340         struct timer *tm;
341         struct tm_result res, res_rec;
342         double sum, sum_rec;
343
344         /*  */
345         tm = prefs->total_tm;
346         sum = __timespec2double(tm->sum);
347         res = __separate_by_order(sum);
348
349         fprintf(stdout, "\n");
350         fprintf(stdout, "              Benchmark results\n");
351         fprintf(stdout, "           ========================\n");
352         fprintf(stdout, "             |-s-||-ms-|-us-|-ns-|\n");
353         fprintf(stdout, "Total time:   %3u. %03u  %03u  %03u\n",
354                         res.s, res.ms, res.us, res.ns);
355
356         if (!prefs->status->received) {
357                 fflush(stdout);
358                 return;
359         }
360
361         tm = prefs->rec_tm;
362         if (GET_FLAG(INSANITY, prefs->flags) < tm->insanity)
363                 goto flush;
364
365         sum_rec = __timespec2double(tm->sum);
366         res_rec = __separate_by_order(sum_rec / prefs->status->received);
367
368         fprintf(stdout, "Avg. latency: %3u. %03u  %03u  %03u\n",
369                         res_rec.s, res_rec.ms, res_rec.us, res_rec.ns);
370
371 flush:
372         print_io_stats(prefs, sum);
373         fflush(stdout);
374 }
375
376 void print_progress(struct bench *prefs)
377 {
378         int lines = 6;
379
380         if ((prefs->op == X_READ) && (GET_FLAG(VERIFY, prefs->flags) != VERIFY_NO))
381                 lines++;
382
383         fprintf(stdout, "\033[%dA\033[J", lines);
384         print_stats(prefs);
385 }
386
387 /**************************\
388  * Benchmarking functions *
389 \**************************/
390
391 void create_id(unsigned long seed)
392 {
393         if (seed >= pow(10, 9))
394                 XSEGLOG2(&lc, W, "Seed larger than 10^9, only its first 9 digits will "
395                                 "be used\n");
396
397         //nanoseconds can't be more than 9 digits
398         snprintf(global_id, IDLEN + 1, "bench-%09lu", seed);
399 }
400
401 void create_target(struct bench *prefs, struct xseg_request *req,
402                 uint64_t new)
403 {
404         struct xseg *xseg = prefs->peer->xseg;
405         char *req_target;
406         char buf[TARGETLEN + 1];
407
408         req_target = xseg_get_target(xseg, req);
409
410         //For read/write, the target object may not correspond to `new`, which is
411         //actually the chunk number.
412         new = __get_object(prefs, new);
413         snprintf(buf, TARGETLEN + 1, "%s-%016lu", global_id, new);
414         strncpy(req_target, buf, TARGETLEN);
415         XSEGLOG2(&lc, D, "Target name of request is %s\n", buf);
416 }
417
418
419 uint64_t determine_next(struct bench *prefs)
420 {
421         if (GET_FLAG(PATTERN, prefs->flags) == PATTERN_SEQ)
422                 return prefs->status->submitted;
423         else
424                 return lfsr_next(prefs->lfsr);
425 }
426
427 uint64_t calculate_offset(struct bench *prefs, uint64_t new)
428 {
429         if (prefs->ts > 0)
430                 return (new * prefs->bs) % prefs->os;
431         else
432                 return 0;
433 }
434
435 uint64_t calculate_prog_quantum(struct bench *prefs)
436 {
437         return round((double)prefs->status->max / 20.0);
438 }
439
440
441 /*
442  * ***********************************************
443  * `create_chunk` handles 3 identifiers:
444  * 1. The benchmark's global_id
445  * 2. The object's number
446  * 3. The chunk offset in the object
447  *
448  * ************************************************
449  * `readwrite_chunk_full` takes the above 3 identifiers and feeds them as seeds
450  * in 63-bit LFSRs. The numbers generated are written consecutively in chunk's
451  * memory range. For example, for a 72-byte chunk:
452  *
453  * || 1 | 2 | 3 | 1 | 2 | 3 | 1 | 2 | 3 ||
454  *  ^   8  16  24  32  40  48  56  64   ^
455  *  |                                   |
456  *  |                                   |
457  * start                               end
458  *
459  * 1,2,3 differ between each iteration
460  *
461  * **************************************************
462  * `_create_chunk_meta` simply writes the above 3 ids in the start and end of
463  * the chunk's memory range, so it should be much faster (but less safe)
464  *
465  * **************************************************
466  * In both cases, special care is taken not to exceed the chunk's memory range.
467  * Also, the bare minimum chunk to verify should be 48 bytes. This limit is set
468  * by reeadwrite_chunk_meta, which expects to write in a memory at least this
469  * big.
470  *
471  * **************************************************
472  * Note: The diagram above also represents the x86_64's endianness.
473  * Endianness must be taken into careful consideration when examining a memory
474  * chunk.
475  */
476 static int readwrite_chunk_full(struct xseg *xseg, struct xseg_request *req,
477                 uint64_t id, uint64_t object)
478 {
479         struct bench_lfsr id_lfsr;
480         struct bench_lfsr obj_lfsr;
481         struct bench_lfsr off_lfsr;
482         uint64_t *d = (uint64_t *)xseg_get_data(xseg, req);
483         uint64_t s = req->size;
484
485         /* Create 63-bit LFSRs */
486         lfsr_init(&id_lfsr, 0x7FFFFFFFFFFFFFFF, id, 0);
487         lfsr_init(&obj_lfsr, 0x7FFFFFFFFFFFFFFF, object, 0);
488         lfsr_init(&off_lfsr, 0x7FFFFFFFFFFFFFFF, req->offset, 0);
489
490         if (s < sizeof(struct signature)) {
491                 XSEGLOG2(&lc, E, "Too small chunk size (%lu butes). Leaving.", s);
492                 return 1;
493         }
494
495         /*
496          * Every write operation has its read counterpart which, if it finds any
497          * corruption, returns 1
498          */
499
500         if (req->op == X_WRITE) {
501                 __write_sig(&id_lfsr, d, s, 0);
502                 __write_sig(&obj_lfsr, d, s, 1);
503                 __write_sig(&off_lfsr, d, s, 2);
504         } else {
505                 if (__read_sig(&id_lfsr, d, s, 0))
506                         return 1;
507                 if (__read_sig(&obj_lfsr, d, s, 1))
508                         return 1;
509                 if(__read_sig(&off_lfsr, d, s, 2))
510                         return 1;
511         }
512
513         return 0;
514 }
515
516 static int readwrite_chunk_meta(struct xseg *xseg, struct xseg_request *req,
517                 uint64_t id, uint64_t object)
518 {
519         char *d = xseg_get_data(xseg, req);
520         uint64_t s = req->size;
521         struct signature sig;
522         int sig_s = sizeof(struct signature);
523         int r = 0;
524
525         sig.id = id;
526         sig.object = object;
527         sig.offset = req->offset;
528
529         if (s < sig_s) {
530                 XSEGLOG2(&lc, E, "Too small chunk size (%lu butes). Leaving.", s);
531                 return 1;
532         }
533
534         //PRINT_SIG(expected, (&sig));
535         /* Read/Write chunk signature both at its start and at its end */
536         if (req->op == X_WRITE) {
537                 memcpy(d, &sig, sig_s);
538                 memcpy(d + s - sig_s, &sig, sig_s);
539         } else {
540                 if (memcmp(d, &sig, sig_s))
541                         r = 1;
542                 else if (memcmp(d + s - sig_s, &sig, sig_s))
543                         r = 1;
544         }
545         //PRINT_SIG(start, d);
546         //PRINT_SIG(end, (d + s - sig_s));
547         return r;
548 }
549
550 /*
551  * We want these functions to be as fast as possible in case we haven't asked
552  * for verification
553  * TODO: Make them prettier but keep the speed of this implementation
554  */
555 void create_chunk(struct bench *prefs, struct xseg_request *req, uint64_t new)
556 {
557         struct xseg *xseg = prefs->peer->xseg;
558         uint64_t id;
559         uint64_t object;
560         int verify;
561
562         verify = GET_FLAG(VERIFY, prefs->flags);
563         switch (verify) {
564                 case VERIFY_NO:
565                         break;
566                 case VERIFY_META:
567                         id = __get_id();
568                         object = __get_object(prefs, new);
569                         readwrite_chunk_meta(xseg, req, id, object);
570                         break;
571                 case VERIFY_FULL:
572                         id = __get_id();
573                         object = __get_object(prefs, new);
574                         readwrite_chunk_full(xseg, req, id, object);
575                         break;
576                 default:
577                         XSEGLOG2(&lc, W, "Unexpected verification mode: %d\n", verify);
578         }
579 }
580
581 int read_chunk(struct bench *prefs, struct xseg_request *req)
582 {
583         struct xseg *xseg = prefs->peer->xseg;
584         uint64_t id;
585         uint64_t object;
586         char *target;
587         int verify;
588         int r = 0;
589
590         verify = GET_FLAG(VERIFY, prefs->flags);
591         switch (verify) {
592                 case VERIFY_NO:
593                         break;
594                 case VERIFY_META:
595                         id = __get_id();
596                         target = xseg_get_target(xseg, req);
597                         object = __get_object_from_name(target);
598                         r = readwrite_chunk_meta(xseg, req, id, object);
599                         break;
600                 case VERIFY_FULL:
601                         id = __get_id();
602                         target = xseg_get_target(xseg, req);
603                         object = __get_object_from_name(target);
604                         r = readwrite_chunk_full(xseg, req, id, object);
605                         break;
606                 default:
607                         XSEGLOG2(&lc, W, "Unexpected verification mode: %d\n", verify);
608         }
609         return r;
610 }