blob: 5fffe638459ccd7afe4fd8bab8acc4b3668d9f39 [file] [log] [blame]
#define _GNU_SOURCE
#define _FILE_OFFSET_BITS 64
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <getopt.h>
#include <stdbool.h>
#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<count; i++) {
if (data[i] < min)
min = data[i];
}
return min;
}
#if 0
static void ns_min_elements(int count, ns_t target[], ns_t new[])
{
int i;
for (i=0; i<count; i++) {
if (new[i] < target[i] || target[i] == 0)
target[i] = new[i];
}
}
#endif
static ns_t ns_max(int count, ns_t data[])
{
int i;
ns_t max = 0;
for (i=0; i<count; i++) {
if (data[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<count; i++) {
sum += data[i];
}
return sum / i;
}
static void format_ns(char *out, ns_t ns)
{
if (ns < 1000)
snprintf(out, 8, "%lldns", ns);
else if (ns < 1000 * 1000)
snprintf(out, 8, "%.3gµs", ns / 1000.0);
else if (ns < 1000 * 1000 * 1000)
snprintf(out, 8, "%.3gms", ns / 1000000.0);
else {
snprintf(out, 8, "%.4gs", ns / 1000000000.0);
}
}
static inline void print_ns(ns_t ns)
{
char buf[8];
format_ns(buf, ns);
puts(buf);
}
static void regression(ns_t ns[], off_t bytes[], int count, ns_t *atime, float *throughput)
{
int i;
float sum_x = 0, sum_xx = 0;
float sum_y = 0, sum_xy = 0;
float slope, intercept;
char buf[8];
for (i = 0; i < count; i++) {
sum_x += (float)bytes[i];
sum_xx += (float)bytes[i] * (float)bytes[i];
sum_y += (float)ns[i];
sum_xy += (float)ns[i] * (float)bytes[i];
}
/* standard linear regression method */
slope = (float)(count * sum_xy - sum_x * sum_y) /
(float)(count * sum_xx - sum_x * sum_x);
intercept = (sum_y - slope * sum_x) / count;
format_ns(buf, intercept);
printf("%g MB/s, %s access time\n", 1000.0 / slope, buf);
*atime = intercept;
*throughput = 1000.0 / slope;
}
static int time_read_interval(struct device *dev, int count, ns_t results[],
size_t size, off_t offset, off_t interval)
{
int i;
off_t pos;
ns_t ret;
for (i=0; i < count; i++) {
pos = offset + i * interval;
ret = time_read(dev, pos, size);
returnif (ret);
if (results[i] == 0 || results[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<rounds; i++) {
bytes[i] = 512l << i;
try_interval(dev, bytes[i], &min[i], count);
}
regression(min + ignore, bytes + ignore, rounds - ignore, &atime, &throughput);
for (i=0; i<rounds; i++) {
printf("bytes %lld, time %lld overhead %g\n", (long long)bytes[i], min[i],
min[i] - atime - bytes[i] * 1000 / throughput);
}
return 0;
}
/*
* Linear feedback shift register
*
* We use this to randomize the block positions for random-access
* tests. Unlike real random data, we know that within 2^bits
* accesses, every possible value up to 2^bits will be seen
* exactly once, with the exception of zero, for which we have
* a special treatment.
*/
static int lfsr(unsigned short v, unsigned int bits)
{
unsigned short bit;
if (v >= (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;
}