blob: 0e93e52b6eaa89a88b2a9eac4338b9a98223653d [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 void flush_read_cache(struct device *dev)
{
off_t cache_size = 1024 * 1024; /* FIXME: use detected size */
time_read(dev, 2 * cache_size, dev->size - 2 * cache_size);
}
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 int try_read_cache(struct device *dev)
{
const int rounds = 18;
const int tries = 8;
ns_t times[rounds];
int i;
for (i = 0; i < rounds; i++) {
long blocksize = 512l << i;
char min[8];
int j;
times[i] = LLONG_MAX;
for (j = 0; j < tries; j++) {
ns_t ns;
ns = time_read(dev, 1024 * 1024 * 1024, blocksize);
returnif (ns);
if (ns < times[i])
times[i] = ns;
}
format_ns(min, times[i]);
printf("%ld bytes: %s, %g MB/s\n", blocksize, min,
blocksize / (times[i] / 1000.0));
}
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;
}
static int try_align(struct device *dev)
{
const int rounds = 11;
const int count = 16;
int i;
ns_t aligned[rounds];
ns_t unaligned[rounds];
for (i=0; i<rounds; i++) {
off_t blocksize = 512l << (i + 1);
ns_t times[count];
char buf_a[8], buf_u[8];
ns_t avg_a, avg_u;
int ret;
flush_read_cache(dev);
memset(times, 0, sizeof(times));
ret = time_read_interval(dev, count, times, blocksize, 0, blocksize * 8);
returnif (ret);
aligned[i] = ns_min(count, times);
avg_a = ns_avg(count, times);
memset(times, 0, sizeof(times));
ret = time_read_interval(dev, count, times, blocksize, blocksize / 2, blocksize * 8);
returnif (ret);
unaligned[i] = ns_min(count, times);
avg_u = ns_avg(count, times);
format_ns(buf_a, aligned[i]);
format_ns(buf_u, unaligned[i]);
printf("%lld bytes: aligned %s unaligned %s diff %lld, %02g%% min %02g%% avg\n",
(long long)blocksize, buf_a, buf_u, unaligned[i] - aligned[i],
100.0 * (unaligned[i] - aligned[i]) / aligned[i],
100.0 * (avg_u - avg_a) / avg_a);
}
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, "%d %lld\n", j * blocksize, min[j]);
}
return 0;
}
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 = 32;
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 0
/* 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
#if 1
/* find maximum number of open AUs */
struct operation program[] = {
/* loop through power of two multiple of one sector */
// {O_LEN_POW2, 7, -(64 * 1024)},
{O_LEN_POW2, 9, -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_FORMAT},
{O_SEQUENCE, 3},
/* linear write 0x5a */
{O_REDUCE, .aggregate = A_MAXIMUM}, {O_REPEAT, 3},
{O_REDUCE, .aggregate = A_AVERAGE},
{O_OFF_LIN, 8192, -1},
{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS},
{O_OFF_RAND, 1, 4 * 1024 * 1024}, {O_WRITE_RAND},
{O_REDUCE, .aggregate = A_MAXIMUM}, {O_REPEAT, 3},
{O_REDUCE, .aggregate = A_AVERAGE},
{O_OFF_LIN, 8192, -1},
{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS},
{O_OFF_RAND, 2, 4 * 1024 * 1024}, {O_WRITE_RAND},
{O_REDUCE, .aggregate = A_MAXIMUM}, {O_REPEAT, 3},
{O_REDUCE, .aggregate = A_AVERAGE},
{O_OFF_LIN, 8192, -1},
{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS},
{O_OFF_RAND, 3, 4 * 1024 * 1024}, {O_WRITE_RAND},
#if 0
{O_REDUCE, .aggregate = A_MAXIMUM}, {O_REPEAT, 1},
{O_REDUCE, .aggregate = A_AVERAGE},
{O_OFF_LIN, 8192, -1},
{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS},
{O_OFF_RAND, 4, 4 * 1024 * 1024}, {O_WRITE_RAND},
{O_REDUCE, .aggregate = A_MAXIMUM}, {O_REPEAT, 1},
{O_REDUCE, .aggregate = A_AVERAGE},
{O_OFF_LIN, 8192, -1},
{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS},
{O_OFF_RAND, 5, 4 * 1024 * 1024}, {O_WRITE_RAND},
{O_REDUCE, .aggregate = A_MAXIMUM}, {O_REPEAT, 1},
{O_REDUCE, .aggregate = A_AVERAGE},
{O_OFF_LIN, 8192, -1},
{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS},
{O_OFF_RAND, 8, 4 * 1024 * 1024}, {O_WRITE_RAND},
#endif
{O_END},
{O_NEWLINE},
{O_END},
{O_END},
};
call(program, dev, 0, 4 * 1024 * 1024, 0);
#endif
#if 0
/* Find FAT Units */
struct operation program[] = {
/* loop through power of two multiple of one sector */
// {O_LEN_POW2, 7, -(64 * 1024)},
{O_LEN_POW2, 14, -512},
{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, 3, 4 * 1024 * 1024},
/* linear write 0x5a */
{O_REDUCE, .aggregate = A_MAXIMUM}, {O_REPEAT, 1},
{O_REDUCE, .aggregate = A_AVERAGE},
{O_OFF_LIN, 8192, -1},
{O_BPS},{O_WRITE_RAND},
{O_NEWLINE},
{O_END},
{O_END},
};
call(program, dev, 0, 4 * 1024 * 1024, 0);
#endif
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("-r, --rcache determine read cache size\n");
printf("-v, --verbose increase verbosity of output\n");
printf("-c, --count=N run each test N times (default: 8\n");
}
struct arguments {
const char *dev;
const char *out;
bool scatter, rcache, align, interval, program;
int verbosity;
int count;
int blocksize;
int scatter_order;
int scatter_span;
int interval_order;
};
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, '$' },
{ "rcache", 0, NULL, 'r' },
{ "align", 0, NULL, 'a' },
{ "interval", 0, NULL, 'i' },
{ "interval-order", 1, NULL, 'I' },
{ "verbose", 0, NULL, 'v' },
{ "count", 1, NULL, 'c' },
{ "blocksize", 1, NULL, 'b' },
{ NULL, 0, NULL, 0 },
};
memset(args, 0, sizeof(*args));
args->count = 8;
args->scatter_order = 12;
args->scatter_span = 8;
args->blocksize = 8192;
while (1) {
int c;
c = getopt_long(argc, argv, "o:sraivc:b: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 'r':
args->rcache = 1;
break;
case 'a':
args->align = 1;
break;
case 'i':
args->interval = 1;
break;
case 'I':
args->interval_order = atoi(optarg);
break;
case 'p':
args->program = 1;
break;
case 'v':
args->verbosity++;
break;
case 'c':
args->count = atoi(optarg);
break;
case 'b':
args->blocksize = atoi(optarg);
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->rcache ||
args->align || args->interval ||
args->program)) {
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 (args.verbosity) {
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.align) {
ret = try_read_alignments(&dev, args.count, args.blocksize);
if (ret < 0) {
errno = -ret;
perror("try_align");
return ret;
}
}
if (args.rcache) {
ret = try_read_cache(&dev);
if (ret < 0) {
errno = -ret;
perror("try_read_cache");
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;
}