#define _GNU_SOURCE #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include #include #include #include #include #include #include #include "dev.h" #include "vm.h" typedef long long ns_t; #define returnif(x) do { typeof(x) __x = (x); if (__x < 0) return (__x); } while (0) static ns_t ns_min(int count, ns_t data[]) { int i; ns_t min = LLONG_MAX; for (i=0; i max) max = data[i]; } return max; } static ns_t ns_avg(int count, ns_t data[]) { int i; ns_t sum = 0; for (i=0; i ret) results[i] = ret; } return 0; } static void print_one_blocksize(int count, ns_t *times, off_t blocksize) { char min[8], avg[8], max[8]; format_ns(min, ns_min(count, times)); format_ns(avg, ns_avg(count, times)); format_ns(max, ns_max(count, times)); printf("%lld bytes: min %s avg %s max %s: %g MB/s\n", (long long)blocksize, min, avg, max, blocksize / (ns_min(count, times) / 1000.0)); } static int try_interval(struct device *dev, long blocksize, ns_t *min_time, int count) { int ret; ns_t times[count]; memset(times, 0, sizeof(times)); ret = time_read_interval(dev, count, times, blocksize, 0, blocksize * 9); returnif (ret); print_one_blocksize(count, times, blocksize); *min_time = ns_min(count, times); return 0; } static int try_intervals(struct device *dev, int count, int rounds) { const int ignore = 3; ns_t min[rounds]; off_t bytes[rounds]; ns_t atime; float throughput; int i; for (i=0; i= (1 << bits)) { fprintf(stderr, "flashbench: internal error\n"); exit(-EINVAL); } if (v == 0) v = ((1 << bits) - 1) & 0xace1; switch (bits) { case 8: /* x^8 + x^6 + x^5 + x^4 + 1 */ bit = ((v >> 0) ^ (v >> 2) ^ (v >> 3) ^ (v >> 4)) & 1; break; case 9: /* x9 + x5 + 1 */ bit = ((v >> 0) ^ (v >> 4)) & 1; break; case 10: /* x10 + x7 + 1 */ bit = ((v >> 0) ^ (v >> 3)) & 1; break; case 11: /* x11 + x9 + 1 */ bit = ((v >> 0) ^ (v >> 2)) & 1; break; case 12: bit = ((v >> 0) ^ (v >> 1) ^ (v >> 2) ^ (v >> 8)) & 1; break; case 13: /* x^13 + x^12 + x^11 + x^8 + 1 */ bit = ((v >> 0) ^ (v >> 1) ^ (v >> 2) ^ (v >> 5)) & 1; break; case 14: /* x^14 + x^13 + x^12 + x^2 + 1 */ bit = ((v >> 0) ^ (v >> 1) ^ (v >> 2) ^ (v >> 12)) & 1; break; case 15: /* x^15 + x^14 + 1 */ bit = ((v >> 0) ^ (v >> 1) ) & 1; break; case 16: /* x^16 + x^14 + x^13 + x^11 + 1 */ bit = ((v >> 0) ^ (v >> 2) ^ (v >> 3) ^ (v >> 5) ) & 1; break; default: fprintf(stderr, "flashbench: internal error\n"); exit(-EINVAL); } return v >> 1 | bit << (bits - 1); } static int try_scatter_io(struct device *dev, int tries, int scatter_order, int scatter_span, int blocksize, FILE *out) { int i, j; const int count = 1 << scatter_order; ns_t time; ns_t min[count]; unsigned long pos; memset(min, 0, sizeof(min)); for (i = 0; i < tries; i++) { pos = 0; for (j = 0; j < count; j++) { time = time_read(dev, (pos * blocksize), scatter_span * blocksize); returnif (time); if (i == 0 || time < min[pos]) min[pos] = time; pos = lfsr(pos, scatter_order); } } for (j = 0; j < count; j++) { fprintf(out, "%f %f\n", j * blocksize / (1024 * 1024.0), min[j] / 1000000.0); } return 0; } static unsigned int find_order(unsigned int large, unsigned int small) { unsigned int o; for (o=1; small < large; small <<= 1) o++; return o; } static int try_read_alignment(struct device *dev, int tries, int count, off_t maxalign, off_t align, size_t blocksize) { ns_t pre[count], on[count], post[count]; char pre_s[8], on_s[8], post_s[8], diff_s[8]; int i, ret; memset(pre, 0, sizeof(pre)); memset(on, 0, sizeof(on)); memset(post, 0, sizeof(post)); for (i = 0; i < tries; i++) { ret = time_read_interval(dev, count, pre, blocksize, align - blocksize, maxalign); returnif(ret); ret = time_read_interval(dev, count, on, blocksize, align - blocksize / 2, maxalign); returnif(ret); ret = time_read_interval(dev, count, post, blocksize, align, maxalign); returnif(ret); } format_ns(pre_s, ns_avg(count, pre)); format_ns(on_s, ns_avg(count, on)); format_ns(post_s, ns_avg(count, post)); format_ns(diff_s, ns_avg(count, on) - (ns_avg(count, pre) + ns_avg(count, post)) / 2); printf("align %lld\tpre %s\ton %s\tpost %s\tdiff %s\n", (long long)align, pre_s, on_s, post_s, diff_s); return 0; } static int try_read_alignments(struct device *dev, int tries, int blocksize) { const int count = 7; int ret; off_t align, maxalign; /* make sure we can fit eight power-of-two blocks in the device */ for (maxalign = blocksize * 2; maxalign < dev->size / count; maxalign *= 2) ; for (align = maxalign; align >= blocksize * 2; align /= 2) { ret = try_read_alignment(dev, tries, count, maxalign, align, blocksize); returnif (ret); } return 0; } static int try_program(struct device *dev) { #if 0 struct operation program[] = { {O_REPEAT, 4}, {O_SEQUENCE, 3}, {O_PRINT, .string = "Hello, World!\n"}, {O_DROP}, {O_PRINTF}, {O_FORMAT}, {O_REDUCE, 8, .aggregate = A_AVERAGE}, {O_LEN_POW2, 8, 512}, {O_OFF_LIN, 8, 4096 }, {O_SEQUENCE, 3}, {O_PRINTF}, {O_READ}, {O_NEWLINE}, {O_DROP}, {O_READ}, {O_END}, {O_NEWLINE}, {O_END}, {O_END}, }; #endif #if 0 struct operation program[] = { {O_SEQUENCE, 3}, {O_PRINT, .string="read by size\n"}, {O_LEN_POW2, 12, 512}, {O_DROP}, {O_SEQUENCE, 4}, {O_PRINTF}, {O_FORMAT}, {O_LENGTH}, {O_PRINT, .string = ": \t"}, {O_PRINTF}, {O_FORMAT}, {O_REDUCE, .aggregate = A_MINIMUM}, {O_OFF_LIN, 8, 4096 * 1024}, {O_READ}, {O_NEWLINE}, {O_END}, {O_NEWLINE}, {O_END}, {O_END}, }; #endif #if 1 /* show effect of type of access within AU */ struct operation program[] = { /* loop through power of two multiple of one sector */ {O_LEN_POW2, 13, -512}, {O_SEQUENCE, 3}, /* print block size */ {O_DROP}, {O_PRINTF}, {O_FORMAT}, {O_LENGTH}, /* start four units into the device, to skip FAT */ {O_OFF_FIXED, .val = 1024 * 4096 * 4}, {O_DROP}, /* print one line of aggregated per second results */ {O_PRINTF}, {O_SEQUENCE, 8}, /* write one block to clear state of block, ignore result */ // {O_DROP}, {O_LEN_FIXED, .val = 1024 * 4096}, // {O_WRITE_RAND}, #if 1 /* linear write zeroes */ {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, {O_OFF_LIN, 8192, -1}, {O_WRITE_ZERO}, /* linear write ones */ {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, {O_OFF_LIN, 8192, -1}, {O_WRITE_ONE}, #endif #if 0 /* Erase */ {O_FORMAT},{O_REDUCE, .aggregate = A_TOTAL},// {O_BPS}, {O_OFF_LIN, 8192, -1}, {O_ERASE}, {O_FORMAT},{O_REDUCE, .aggregate = A_TOTAL},// {O_BPS}, {O_OFF_LIN, 8192, -1}, {O_ERASE}, /* linear write 0x5a */ {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, {O_OFF_LIN, 8192, -1}, {O_WRITE_RAND}, #endif /* linear write 0x5a again */ {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, {O_OFF_LIN, 8192, -1}, {O_WRITE_RAND}, /* linear read */ {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, {O_OFF_LIN, 8192, -1}, {O_READ}, #if 1 /* random write zeroes */ {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, {O_OFF_RAND, 8192, -1}, {O_WRITE_ZERO}, /* random write ones */ {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, {O_OFF_RAND, 8192, -1}, {O_WRITE_ONE}, #endif #if 0 /* random erase */ {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE},// {O_BPS}, {O_OFF_RAND, 8192, -1}, {O_ERASE}, {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE},// {O_BPS}, {O_OFF_RAND, 8192, -1}, {O_ERASE}, /* random write 0x5a */ {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, {O_OFF_RAND, 8192, -1}, {O_WRITE_RAND}, #endif /* random write 0x5a again */ {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, {O_OFF_RAND, 8192, -1}, {O_WRITE_RAND}, /* random read */ {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, {O_OFF_RAND, 8192, -1}, {O_READ}, {O_END}, {O_NEWLINE}, {O_END}, {O_END}, }; call(program, dev, 0, 1 * 4096 * 1024, 0); #endif return 0; } #if 0 static int try_open_au_oob(struct device *dev, unsigned int erasesize, unsigned int blocksize, unsigned int count, bool random) { /* find maximum number of open AUs */ struct operation program[] = { /* loop through power of two multiple of one sector */ {O_LEN_POW2, find_order(erasesize, blocksize), -(long long)blocksize}, {O_SEQUENCE, 4}, /* print block size */ {O_DROP}, {O_PRINTF}, {O_FORMAT}, {O_LENGTH}, /* start 16 MB into the device, to skip FAT */ {O_OFF_FIXED, .val = 4 * 1024 * 4128}, {O_DROP}, /* print one line of aggregated per second results */ {O_PRINTF}, {O_FORMAT}, {O_BPS}, /* linear write 0x5a */ {O_REDUCE, .aggregate = A_MAXIMUM}, {O_REPEAT, 1}, {O_REDUCE, .aggregate = A_AVERAGE}, { (random ? O_OFF_RAND : O_OFF_LIN), erasesize / blocksize, -1}, {O_REDUCE, .aggregate = A_AVERAGE}, // {O_BPS}, {O_OFF_RAND, count, /* 2 * erasesize */ 2 * 4128 * 1024}, {O_WRITE_RAND}, {O_DROP}, {O_OFF_FIXED, .val = 4 * 1024 * 4128 + 4 * 1024 * 1024}, {O_DROP}, {O_LEN_FIXED, .val = 32 * 1024}, {O_OFF_RAND, count, 2 * 4128 * 1024}, {O_WRITE_RAND}, {O_NEWLINE}, {O_END}, {O_END}, }; call(program, dev, 0, erasesize, 0); return 0; } #endif static int try_open_au(struct device *dev, unsigned int erasesize, unsigned int blocksize, unsigned int count, unsigned long long offset, bool random) { /* start 16 MB into the device, to skip FAT, round up to full erase blocks */ if (offset == -1ull) offset = (1024 * 1024 * 16 + erasesize - 1) / erasesize * erasesize; /* find maximum number of open AUs */ struct operation program[] = { /* loop through power of two multiple of one sector */ {O_LEN_POW2, find_order(erasesize, blocksize), -(long long)blocksize}, {O_SEQUENCE, 3}, /* print block size */ {O_DROP}, {O_PRINTF}, {O_FORMAT}, {O_LENGTH}, {O_OFF_FIXED, .val = offset}, {O_DROP}, /* print one line of aggregated per second results */ {O_PRINTF}, {O_FORMAT}, {O_BPS}, /* linear write 0x5a */ {O_REDUCE, .aggregate = A_MAXIMUM}, {O_REPEAT, 1}, {O_REDUCE, .aggregate = A_AVERAGE}, { (random ? O_OFF_RAND : O_OFF_LIN), erasesize / blocksize, -1}, {O_REDUCE, .aggregate = A_AVERAGE}, {O_OFF_RAND, count, 12 * erasesize}, {O_WRITE_RAND}, {O_NEWLINE}, {O_END}, {O_END}, }; call(program, dev, 0, erasesize, 0); return 0; } static int try_find_fat(struct device *dev, unsigned int erasesize, unsigned int blocksize, unsigned int count, bool random) { /* Find FAT Units */ struct operation program[] = { /* loop through power of two multiple of one sector */ {O_LEN_POW2, find_order(erasesize, blocksize), - (long long)blocksize}, {O_SEQUENCE, 3}, /* print block size */ {O_DROP}, {O_PRINTF}, {O_FORMAT}, {O_LENGTH}, /* print one line of aggregated per second results */ {O_PRINTF}, {O_FORMAT}, {O_OFF_LIN, count, erasesize}, /* linear write 0x5a */ {O_REDUCE, .aggregate = A_MAXIMUM}, {O_REPEAT, 1}, {O_BPS}, {O_REDUCE, .aggregate = A_AVERAGE}, {random ? O_OFF_RAND : O_OFF_LIN, erasesize / blocksize, -1}, {O_WRITE_RAND}, {O_NEWLINE}, {O_END}, {O_END}, }; call(program, dev, 0, erasesize, 0); return 0; } static void print_help(const char *name) { printf("%s [OPTION]... [DEVICE]\n", name); printf("run tests on DEVICE, pointing to a flash storage medium.\n\n"); printf("-o, --out=FILE write output to FILE instead of stdout\n"); printf("-s, --scatter run scatter read test\n"); printf(" --scatter-order=N scatter across 2^N blocks\n"); printf(" --scatter-span=N span each write across N blocks\n"); printf("-f, --find-fat analyse first few erase blocks\n"); printf(" --fat-nr=N look through first N erase blocks (default: 6)\n"); printf("-O, --open-au find number of open erase blocks\n"); printf(" --open-au-nr=N try N open erase blocks\n"); printf(" --offset=N start at position N\n"); printf("-r, --random use pseudorandom access with erase block\n"); printf("-v, --verbose increase verbosity of output\n"); printf("-c, --count=N run each test N times (default: 8\n"); printf("-b, --blocksize=N use a blocksize of N (default:16K)\n"); printf("-e, --erasesize=N use a eraseblock size of N (default:4M)\n"); } struct arguments { const char *dev; const char *out; bool scatter, interval, program, fat, open_au, align; bool random; int count; int blocksize; int erasesize; unsigned long long offset; int scatter_order; int scatter_span; int interval_order; int fat_nr; int open_au_nr; }; static int parse_arguments(int argc, char **argv, struct arguments *args) { static const struct option long_options[] = { { "out", 1, NULL, 'o' }, { "scatter", 0, NULL, 's' }, { "scatter-order", 1, NULL, 'S' }, { "scatter-span", 1, NULL, '$' }, { "align", 0, NULL, 'a' }, { "interval", 0, NULL, 'i' }, { "interval-order", 1, NULL, 'I' }, { "findfat", 0, NULL, 'f' }, { "fat-nr", 1, NULL, 'F' }, { "open-au", 0, NULL, 'O' }, { "open-au-nr", 1, NULL, '0' }, { "offset", 1, NULL, 't' }, { "random", 0, NULL, 'r' }, { "verbose", 0, NULL, 'v' }, { "count", 1, NULL, 'c' }, { "blocksize", 1, NULL, 'b' }, { "erasesize", 1, NULL, 'e' }, { NULL, 0, NULL, 0 }, }; memset(args, 0, sizeof(*args)); args->count = 8; args->scatter_order = 9; args->scatter_span = 1; args->blocksize = 16384; args->offset = -1ull; args->erasesize = 4 * 1024 * 1024; args->fat_nr = 6; args->open_au_nr = 2; while (1) { int c; c = getopt_long(argc, argv, "o:siafF:Ovrc:b:e:p", long_options, &optind); if (c == -1) break; switch (c) { case 'o': args->out = optarg; break; case 's': args->scatter = 1; break; case 'S': args->scatter_order = atoi(optarg); break; case '$': args->scatter_span = atoi(optarg); break; case 'a': args->align = 1; break; case 'i': args->interval = 1; break; case 'I': args->interval_order = atoi(optarg); break; case 'f': args->fat = 1; break; case 'F': args->fat_nr = atoi(optarg); break; case 'O': args->open_au = 1; break; case '0': args->open_au_nr = atoi(optarg); break; case 'r': args->random = 1; break; case 'p': args->program = 1; break; case 'v': verbose++; break; case 'c': args->count = atoi(optarg); break; case 'b': args->blocksize = atoi(optarg); break; case 'e': args->erasesize = atoi(optarg); break; case 't': args->offset = strtoull(optarg, NULL, 0); break; case '?': print_help(argv[0]); return -EINVAL; break; } } if (optind != (argc - 1)) { fprintf(stderr, "%s: invalid arguments\n", argv[0]); return -EINVAL; } args->dev = argv[optind]; if (!(args->scatter || args->interval || args->program || args->fat || args->open_au || args->align)) { fprintf(stderr, "%s: need at least one action\n", argv[0]); return -EINVAL; } if (args->scatter && (args->scatter_order > 16)) { fprintf(stderr, "%s: scatter_order must be at most 16\n", argv[0]); return -EINVAL; } return 0; } static FILE *open_output(const char *filename) { if (!filename || !strcmp(filename, "-")) return fdopen(0, "w"); /* write to stdout */ return fopen(filename, "w+"); } int main(int argc, char **argv) { struct device dev; struct arguments args; FILE *output; int ret; returnif(parse_arguments(argc, argv, &args)); returnif(setup_dev(&dev, args.dev)); output = open_output(args.out); if (!output) { perror(args.out); return -errno; } if (verbose > 1) { printf("filename: \"%s\"\n", argv[1]); printf("filesize: 0x%llx\n", (unsigned long long)dev.size); } if (args.scatter) { ret = try_scatter_io(&dev, args.count, args.scatter_order, args.scatter_span, args.blocksize, output); if (ret < 0) { errno = -ret; perror("try_scatter_io"); return ret; } } if (args.fat) { ret = try_find_fat(&dev, args.erasesize, args.blocksize, args.fat_nr, args.random); if (ret < 0) { errno = -ret; perror("find_fat"); } } if (args.align) { ret = try_read_alignments(&dev, args.count, args.blocksize); if (ret < 0) { errno = -ret; perror("try_align"); return ret; } } if (args.open_au) { ret = try_open_au(&dev, args.erasesize, args.blocksize, args.open_au_nr, args.offset, args.random); if (ret < 0) { errno = -ret; perror("find_fat"); return ret; } } if (args.interval) { ret = try_intervals(&dev, args.count, args.interval_order); if (ret < 0) { errno = -ret; perror("try_intervals"); return ret; } } if (args.program) { try_program(&dev); } return 0; }