4fe0738ff4
These are really just different flavors of test.py and test_runner.c
without support for power-loss testing, but with support for measuring
the cumulative number of bytes read, programmed, and erased.
Note that the existing define parameterization should work perfectly
fine for running benchmarks across various dimensions:
./scripts/bench.py \
runners/bench_runner \
bench_file_read \
-gnor \
-DSIZE='range(0,131072,1024)'
Also added a couple basic benchmarks as a starting point.
506 lines
16 KiB
TOML
506 lines
16 KiB
TOML
# test running a filesystem to exhaustion
|
|
[cases.test_exhaustion_normal]
|
|
defines.ERASE_CYCLES = 10
|
|
defines.BLOCK_COUNT = 256 # small bd so test runs faster
|
|
defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2'
|
|
defines.BADBLOCK_BEHAVIOR = [
|
|
'LFS_EMUBD_BADBLOCK_PROGERROR',
|
|
'LFS_EMUBD_BADBLOCK_ERASEERROR',
|
|
'LFS_EMUBD_BADBLOCK_READERROR',
|
|
'LFS_EMUBD_BADBLOCK_PROGNOOP',
|
|
'LFS_EMUBD_BADBLOCK_ERASENOOP',
|
|
]
|
|
defines.FILES = 10
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfs_format(&lfs, cfg) => 0;
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
lfs_mkdir(&lfs, "roadrunner") => 0;
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
uint32_t cycle = 0;
|
|
while (true) {
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
for (uint32_t i = 0; i < FILES; i++) {
|
|
// chose name, roughly random seed, and random 2^n size
|
|
char path[1024];
|
|
sprintf(path, "roadrunner/test%d", i);
|
|
srand(cycle * i);
|
|
lfs_size_t size = 1 << ((rand() % 10)+2);
|
|
|
|
lfs_file_t file;
|
|
lfs_file_open(&lfs, &file, path,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
|
|
|
|
for (lfs_size_t j = 0; j < size; j++) {
|
|
char c = 'a' + (rand() % 26);
|
|
lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
|
|
assert(res == 1 || res == LFS_ERR_NOSPC);
|
|
if (res == LFS_ERR_NOSPC) {
|
|
int err = lfs_file_close(&lfs, &file);
|
|
assert(err == 0 || err == LFS_ERR_NOSPC);
|
|
lfs_unmount(&lfs) => 0;
|
|
goto exhausted;
|
|
}
|
|
}
|
|
|
|
int err = lfs_file_close(&lfs, &file);
|
|
assert(err == 0 || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
lfs_unmount(&lfs) => 0;
|
|
goto exhausted;
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < FILES; i++) {
|
|
// check for errors
|
|
char path[1024];
|
|
sprintf(path, "roadrunner/test%d", i);
|
|
srand(cycle * i);
|
|
lfs_size_t size = 1 << ((rand() % 10)+2);
|
|
|
|
lfs_file_t file;
|
|
lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
|
|
for (lfs_size_t j = 0; j < size; j++) {
|
|
char c = 'a' + (rand() % 26);
|
|
char r;
|
|
lfs_file_read(&lfs, &file, &r, 1) => 1;
|
|
assert(r == c);
|
|
}
|
|
|
|
lfs_file_close(&lfs, &file) => 0;
|
|
}
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
cycle += 1;
|
|
}
|
|
|
|
exhausted:
|
|
// should still be readable
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
for (uint32_t i = 0; i < FILES; i++) {
|
|
// check for errors
|
|
char path[1024];
|
|
sprintf(path, "roadrunner/test%d", i);
|
|
struct lfs_info info;
|
|
lfs_stat(&lfs, path, &info) => 0;
|
|
}
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
LFS_WARN("completed %d cycles", cycle);
|
|
'''
|
|
|
|
# test running a filesystem to exhaustion
|
|
# which also requires expanding superblocks
|
|
[cases.test_exhaustion_superblocks]
|
|
defines.ERASE_CYCLES = 10
|
|
defines.BLOCK_COUNT = 256 # small bd so test runs faster
|
|
defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2'
|
|
defines.BADBLOCK_BEHAVIOR = [
|
|
'LFS_EMUBD_BADBLOCK_PROGERROR',
|
|
'LFS_EMUBD_BADBLOCK_ERASEERROR',
|
|
'LFS_EMUBD_BADBLOCK_READERROR',
|
|
'LFS_EMUBD_BADBLOCK_PROGNOOP',
|
|
'LFS_EMUBD_BADBLOCK_ERASENOOP',
|
|
]
|
|
defines.FILES = 10
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfs_format(&lfs, cfg) => 0;
|
|
|
|
uint32_t cycle = 0;
|
|
while (true) {
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
for (uint32_t i = 0; i < FILES; i++) {
|
|
// chose name, roughly random seed, and random 2^n size
|
|
char path[1024];
|
|
sprintf(path, "test%d", i);
|
|
srand(cycle * i);
|
|
lfs_size_t size = 1 << ((rand() % 10)+2);
|
|
|
|
lfs_file_t file;
|
|
lfs_file_open(&lfs, &file, path,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
|
|
|
|
for (lfs_size_t j = 0; j < size; j++) {
|
|
char c = 'a' + (rand() % 26);
|
|
lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
|
|
assert(res == 1 || res == LFS_ERR_NOSPC);
|
|
if (res == LFS_ERR_NOSPC) {
|
|
int err = lfs_file_close(&lfs, &file);
|
|
assert(err == 0 || err == LFS_ERR_NOSPC);
|
|
lfs_unmount(&lfs) => 0;
|
|
goto exhausted;
|
|
}
|
|
}
|
|
|
|
int err = lfs_file_close(&lfs, &file);
|
|
assert(err == 0 || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
lfs_unmount(&lfs) => 0;
|
|
goto exhausted;
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < FILES; i++) {
|
|
// check for errors
|
|
char path[1024];
|
|
sprintf(path, "test%d", i);
|
|
srand(cycle * i);
|
|
lfs_size_t size = 1 << ((rand() % 10)+2);
|
|
|
|
lfs_file_t file;
|
|
lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
|
|
for (lfs_size_t j = 0; j < size; j++) {
|
|
char c = 'a' + (rand() % 26);
|
|
char r;
|
|
lfs_file_read(&lfs, &file, &r, 1) => 1;
|
|
assert(r == c);
|
|
}
|
|
|
|
lfs_file_close(&lfs, &file) => 0;
|
|
}
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
cycle += 1;
|
|
}
|
|
|
|
exhausted:
|
|
// should still be readable
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
for (uint32_t i = 0; i < FILES; i++) {
|
|
// check for errors
|
|
char path[1024];
|
|
struct lfs_info info;
|
|
sprintf(path, "test%d", i);
|
|
lfs_stat(&lfs, path, &info) => 0;
|
|
}
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
LFS_WARN("completed %d cycles", cycle);
|
|
'''
|
|
|
|
# These are a sort of high-level litmus test for wear-leveling. One definition
|
|
# of wear-leveling is that increasing a block device's space translates directly
|
|
# into increasing the block devices lifetime. This is something we can actually
|
|
# check for.
|
|
|
|
# wear-level test running a filesystem to exhaustion
|
|
[cases.test_exhuastion_wear_leveling]
|
|
defines.ERASE_CYCLES = 20
|
|
defines.BLOCK_COUNT = 256 # small bd so test runs faster
|
|
defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2'
|
|
defines.FILES = 10
|
|
code = '''
|
|
uint32_t run_cycles[2];
|
|
const uint32_t run_block_count[2] = {BLOCK_COUNT/2, BLOCK_COUNT};
|
|
|
|
for (int run = 0; run < 2; run++) {
|
|
for (lfs_block_t b = 0; b < BLOCK_COUNT; b++) {
|
|
lfs_emubd_setwear(cfg, b,
|
|
(b < run_block_count[run]) ? 0 : ERASE_CYCLES) => 0;
|
|
}
|
|
|
|
lfs_t lfs;
|
|
lfs_format(&lfs, cfg) => 0;
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
lfs_mkdir(&lfs, "roadrunner") => 0;
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
uint32_t cycle = 0;
|
|
while (true) {
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
for (uint32_t i = 0; i < FILES; i++) {
|
|
// chose name, roughly random seed, and random 2^n size
|
|
char path[1024];
|
|
sprintf(path, "roadrunner/test%d", i);
|
|
srand(cycle * i);
|
|
lfs_size_t size = 1 << ((rand() % 10)+2);
|
|
|
|
lfs_file_t file;
|
|
lfs_file_open(&lfs, &file, path,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
|
|
|
|
for (lfs_size_t j = 0; j < size; j++) {
|
|
char c = 'a' + (rand() % 26);
|
|
lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
|
|
assert(res == 1 || res == LFS_ERR_NOSPC);
|
|
if (res == LFS_ERR_NOSPC) {
|
|
int err = lfs_file_close(&lfs, &file);
|
|
assert(err == 0 || err == LFS_ERR_NOSPC);
|
|
lfs_unmount(&lfs) => 0;
|
|
goto exhausted;
|
|
}
|
|
}
|
|
|
|
int err = lfs_file_close(&lfs, &file);
|
|
assert(err == 0 || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
lfs_unmount(&lfs) => 0;
|
|
goto exhausted;
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < FILES; i++) {
|
|
// check for errors
|
|
char path[1024];
|
|
sprintf(path, "roadrunner/test%d", i);
|
|
srand(cycle * i);
|
|
lfs_size_t size = 1 << ((rand() % 10)+2);
|
|
|
|
lfs_file_t file;
|
|
lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
|
|
for (lfs_size_t j = 0; j < size; j++) {
|
|
char c = 'a' + (rand() % 26);
|
|
char r;
|
|
lfs_file_read(&lfs, &file, &r, 1) => 1;
|
|
assert(r == c);
|
|
}
|
|
|
|
lfs_file_close(&lfs, &file) => 0;
|
|
}
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
cycle += 1;
|
|
}
|
|
|
|
exhausted:
|
|
// should still be readable
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
for (uint32_t i = 0; i < FILES; i++) {
|
|
// check for errors
|
|
char path[1024];
|
|
struct lfs_info info;
|
|
sprintf(path, "roadrunner/test%d", i);
|
|
lfs_stat(&lfs, path, &info) => 0;
|
|
}
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
run_cycles[run] = cycle;
|
|
LFS_WARN("completed %d blocks %d cycles",
|
|
run_block_count[run], run_cycles[run]);
|
|
}
|
|
|
|
// check we increased the lifetime by 2x with ~10% error
|
|
LFS_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]);
|
|
'''
|
|
|
|
# wear-level test + expanding superblock
|
|
[cases.test_exhaustion_wear_leveling_superblocks]
|
|
defines.ERASE_CYCLES = 20
|
|
defines.BLOCK_COUNT = 256 # small bd so test runs faster
|
|
defines.BLOCK_CYCLES = 'ERASE_CYCLES / 2'
|
|
defines.FILES = 10
|
|
code = '''
|
|
uint32_t run_cycles[2];
|
|
const uint32_t run_block_count[2] = {BLOCK_COUNT/2, BLOCK_COUNT};
|
|
|
|
for (int run = 0; run < 2; run++) {
|
|
for (lfs_block_t b = 0; b < BLOCK_COUNT; b++) {
|
|
lfs_emubd_setwear(cfg, b,
|
|
(b < run_block_count[run]) ? 0 : ERASE_CYCLES) => 0;
|
|
}
|
|
|
|
lfs_t lfs;
|
|
lfs_format(&lfs, cfg) => 0;
|
|
|
|
uint32_t cycle = 0;
|
|
while (true) {
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
for (uint32_t i = 0; i < FILES; i++) {
|
|
// chose name, roughly random seed, and random 2^n size
|
|
char path[1024];
|
|
sprintf(path, "test%d", i);
|
|
srand(cycle * i);
|
|
lfs_size_t size = 1 << ((rand() % 10)+2);
|
|
|
|
lfs_file_t file;
|
|
lfs_file_open(&lfs, &file, path,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
|
|
|
|
for (lfs_size_t j = 0; j < size; j++) {
|
|
char c = 'a' + (rand() % 26);
|
|
lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
|
|
assert(res == 1 || res == LFS_ERR_NOSPC);
|
|
if (res == LFS_ERR_NOSPC) {
|
|
int err = lfs_file_close(&lfs, &file);
|
|
assert(err == 0 || err == LFS_ERR_NOSPC);
|
|
lfs_unmount(&lfs) => 0;
|
|
goto exhausted;
|
|
}
|
|
}
|
|
|
|
int err = lfs_file_close(&lfs, &file);
|
|
assert(err == 0 || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
lfs_unmount(&lfs) => 0;
|
|
goto exhausted;
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < FILES; i++) {
|
|
// check for errors
|
|
char path[1024];
|
|
sprintf(path, "test%d", i);
|
|
srand(cycle * i);
|
|
lfs_size_t size = 1 << ((rand() % 10)+2);
|
|
|
|
lfs_file_t file;
|
|
lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
|
|
for (lfs_size_t j = 0; j < size; j++) {
|
|
char c = 'a' + (rand() % 26);
|
|
char r;
|
|
lfs_file_read(&lfs, &file, &r, 1) => 1;
|
|
assert(r == c);
|
|
}
|
|
|
|
lfs_file_close(&lfs, &file) => 0;
|
|
}
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
cycle += 1;
|
|
}
|
|
|
|
exhausted:
|
|
// should still be readable
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
for (uint32_t i = 0; i < FILES; i++) {
|
|
// check for errors
|
|
char path[1024];
|
|
struct lfs_info info;
|
|
sprintf(path, "test%d", i);
|
|
lfs_stat(&lfs, path, &info) => 0;
|
|
}
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
run_cycles[run] = cycle;
|
|
LFS_WARN("completed %d blocks %d cycles",
|
|
run_block_count[run], run_cycles[run]);
|
|
}
|
|
|
|
// check we increased the lifetime by 2x with ~10% error
|
|
LFS_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]);
|
|
'''
|
|
|
|
# test that we wear blocks roughly evenly
|
|
[cases.test_exhaustion_wear_distribution]
|
|
defines.ERASE_CYCLES = 0xffffffff
|
|
defines.BLOCK_COUNT = 256 # small bd so test runs faster
|
|
defines.BLOCK_CYCLES = [5, 4, 3, 2, 1]
|
|
defines.CYCLES = 100
|
|
defines.FILES = 10
|
|
if = 'BLOCK_CYCLES < CYCLES/10'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfs_format(&lfs, cfg) => 0;
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
lfs_mkdir(&lfs, "roadrunner") => 0;
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
uint32_t cycle = 0;
|
|
while (cycle < CYCLES) {
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
for (uint32_t i = 0; i < FILES; i++) {
|
|
// chose name, roughly random seed, and random 2^n size
|
|
char path[1024];
|
|
sprintf(path, "roadrunner/test%d", i);
|
|
srand(cycle * i);
|
|
lfs_size_t size = 1 << 4; //((rand() % 10)+2);
|
|
|
|
lfs_file_t file;
|
|
lfs_file_open(&lfs, &file, path,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
|
|
|
|
for (lfs_size_t j = 0; j < size; j++) {
|
|
char c = 'a' + (rand() % 26);
|
|
lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
|
|
assert(res == 1 || res == LFS_ERR_NOSPC);
|
|
if (res == LFS_ERR_NOSPC) {
|
|
int err = lfs_file_close(&lfs, &file);
|
|
assert(err == 0 || err == LFS_ERR_NOSPC);
|
|
lfs_unmount(&lfs) => 0;
|
|
goto exhausted;
|
|
}
|
|
}
|
|
|
|
int err = lfs_file_close(&lfs, &file);
|
|
assert(err == 0 || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
lfs_unmount(&lfs) => 0;
|
|
goto exhausted;
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < FILES; i++) {
|
|
// check for errors
|
|
char path[1024];
|
|
sprintf(path, "roadrunner/test%d", i);
|
|
srand(cycle * i);
|
|
lfs_size_t size = 1 << 4; //((rand() % 10)+2);
|
|
|
|
lfs_file_t file;
|
|
lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
|
|
for (lfs_size_t j = 0; j < size; j++) {
|
|
char c = 'a' + (rand() % 26);
|
|
char r;
|
|
lfs_file_read(&lfs, &file, &r, 1) => 1;
|
|
assert(r == c);
|
|
}
|
|
|
|
lfs_file_close(&lfs, &file) => 0;
|
|
}
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
cycle += 1;
|
|
}
|
|
|
|
exhausted:
|
|
// should still be readable
|
|
lfs_mount(&lfs, cfg) => 0;
|
|
for (uint32_t i = 0; i < FILES; i++) {
|
|
// check for errors
|
|
char path[1024];
|
|
struct lfs_info info;
|
|
sprintf(path, "roadrunner/test%d", i);
|
|
lfs_stat(&lfs, path, &info) => 0;
|
|
}
|
|
lfs_unmount(&lfs) => 0;
|
|
|
|
LFS_WARN("completed %d cycles", cycle);
|
|
|
|
// check the wear on our block device
|
|
lfs_emubd_wear_t minwear = -1;
|
|
lfs_emubd_wear_t totalwear = 0;
|
|
lfs_emubd_wear_t maxwear = 0;
|
|
// skip 0 and 1 as superblock movement is intentionally avoided
|
|
for (lfs_block_t b = 2; b < BLOCK_COUNT; b++) {
|
|
lfs_emubd_wear_t wear = lfs_emubd_getwear(cfg, b);
|
|
printf("%08x: wear %d\n", b, wear);
|
|
assert(wear >= 0);
|
|
if (wear < minwear) {
|
|
minwear = wear;
|
|
}
|
|
if (wear > maxwear) {
|
|
maxwear = wear;
|
|
}
|
|
totalwear += wear;
|
|
}
|
|
lfs_emubd_wear_t avgwear = totalwear / BLOCK_COUNT;
|
|
LFS_WARN("max wear: %d cycles", maxwear);
|
|
LFS_WARN("avg wear: %d cycles", totalwear / (int)BLOCK_COUNT);
|
|
LFS_WARN("min wear: %d cycles", minwear);
|
|
|
|
// find standard deviation^2
|
|
lfs_emubd_wear_t dev2 = 0;
|
|
for (lfs_block_t b = 2; b < BLOCK_COUNT; b++) {
|
|
lfs_emubd_wear_t wear = lfs_emubd_getwear(cfg, b);
|
|
assert(wear >= 0);
|
|
lfs_emubd_swear_t diff = wear - avgwear;
|
|
dev2 += diff*diff;
|
|
}
|
|
dev2 /= totalwear;
|
|
LFS_WARN("std dev^2: %d", dev2);
|
|
assert(dev2 < 8);
|
|
'''
|
|
|