Commit Graph

10 Commits

Author SHA1 Message Date
Christopher Haster 5d03416c82 Added LFSR_BTREE/SHRUB_NULL, dropped lfsr_btree_alloc
Our B-trees lazily allocate their root blocks, so it makes more sense
for this to be a macro. Added/adopted a similar LFSR_SHRUB_NULL for
consistency.

Unfortunately this added a bit of code. I think because GCC struggles to
optimize compound literals, which both LFSR_BTREE_NULL and
LFSR_SHRUB_NULL expand into:

           code          stack
  before: 33538           2624
  after:  33550 (+0.0%)   2624 (+0.0%)
2024-05-27 15:30:44 -05:00
Christopher Haster 15da817af5 Replace fuzz DENSITY with explicit OPS in tests
This sort of inverts the previous logic. Tests can still define
OPS='2*N' to scale the number of ops roughly with the number of entries,
but this fits better into the test framework, allows overriding, scaling
can be more easily tweaked, can be swapped out with a constant (like in
test_wl), etc.

Also tweaked some of the related N constants/filter conditions in tests
since these are now being effectively doubled... This should leave the
resulting number of ops unchanged.
2024-05-27 15:30:23 -05:00
Christopher Haster fe11a33416 (Re)implemented bad prog/erase recovery
This (re)implements the heavy-hitting tests in test_badblocks that rakes
filesystem operations over various types of prog/erase failures:

- test_badblocks_[one|region|alternating]_btree - force tall B-trees
- test_badblocks_[one|region|alternating]_dirs - large mtree
- test_badblocks_[one|region|alternating]_files - mixed mtree + files
- test_badblocks_[one|region|alternating]_fwrite_fuzz - complex files
- test_badblocks_[one|region|alternating]_orphanzombiedir_fuzz - complex
- test_badblocks_mrootanchor - uh, format fails, cheap test though

Where:

- test_badblocks_one_* - runs with every possible bad block
- test_badblocks_region_* - runs with a large region of bad blocks
- test_badblocks_alternating_* - runs with alternating bad blocks, this
  one is rough for block pair allocations

This required quite a bit of rewiring of internal block allocations. I
knew this would eventually need to be (re)implemented, but the jump from
infallible to fallible progs everywhere was still quite involved:

- lfs_alloc no longer returns LFS_ERR_CORRUPT if erase fails, instead it
  will keep searching for a block where an erase "sticks" or return
  LFS_ERR_NOENT. This simplifies above layers.

  This actually turned out to be required since the lookahead traversal
  can also return LFS_ERR_CORRUPT... which needs to be treated as a hard
  error and bail.

- In lfsr_btree_commit_ all inner-node compactions needed alloc loops.
  This really complements B-tree's copy-on-write behavior, but does make
  lfsr_btree_commit_ a bit of a goto soup...

- Same for lfsr_btree_commit/lfsr_bshrub_commit, but fortunately there
  are nice and self-contained.

- lfsr_mdir_alloc__/lfsr_mdir_swap__ needed a bit of an overhaul to be
  able to handle bad progs. lfsr_mdir_alloc__ now takes a bool `all`
  parameter to know if it should allocate one or two of the mdir blocks.

  You could argue it's simpler/cheaper to always allocate two blocks at
  a time, but this could lead to premature filesystem death on
  unfortunate bad block patterns. test_badblocks_alternating_*
  specifically tests for this. Note we still allocate both on
  relocation, but only on the first commit attempt.

  This also rearranges things to move the overcompacting logic out of
  lfsr_mdir_swap__ and into lfsr_mdir_commit_, since we only want to
  overcompact after trying to program all possible free blocks.

- lfsr_file_flush_ now needs to rewrite the entire block of data if a
  prog fails, even if appending an existing data block.

  Humorously, this was really easy, since we already align everything to
  any existing blocks as a part of our crystallization algorithm. Almost
  too easy... (no new code! only a couple gotos! scary!)

Note some of these may be transformable into simpler while loops, but I
decided to avoid this and prefer explicit `relocate` gotos because: 1.
in some functions these end up deeply nested in existing loops and I was
already bitten by a shadowed continue, 2. the "good" path does not loop,
with a loop you need an easy to miss break and the intention is less
clear, and 3. consistency is good.

We are _not_ testing read errors yet. This is because we no longer read
back progs and the relaxed rcache/pcache alignment requirements make
this a bit difficult to (re)implement. User feedback also suggests we
may want to make this optional... So need to think on how to address
this.

Some other notes:

- Our low-level bd wrappers, lfsr_bd_*__, now log bad ops via LFS_DEBUG.

- Overcompaction is now an LFS_WARN.

- The pcache is now correctly dropped if we error during flush.

- I noticed lfsr_btree_alloc double allocated for new B-trees, it
  doesn't now, maybe change this function?

- Our B-tree tests all stop on LFS_ERR_NOSPC, but this isn't guaranteed
  since our filesystem isn't in a valid state. We should make sure none
  of our B-tree tests actually rely on this...

Honestly, considering how much new logic was introduced, this really did
not impact code cost as much as I thought it would. Probably thanks to
the underlying data structures being built to easily discard blocks in
the first place:

           code          stack
  before: 33474           2640
  after:  33618 (+0.4%)   2648 (+0.3%)
2024-05-27 03:00:44 -05:00
Christopher Haster 4ff7c1f771 Commenting out outdated functions for now
This makes it easier to evaluate the code/stack/etc sizes and run tests
without bringing in all of the outdated code.

I guess this officially makes this branch more-or-less a full rewrite,
though the benefit of commenting vs deleting this code is that it can be
easily pulled back in when useful.
2023-06-16 01:51:29 -05:00
Christopher Haster 4fe0738ff4 Added bench.py and bench_runner.c for benchmarking
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.
2022-11-15 13:33:34 -06:00
Christopher Haster 11d6d1251e Dropped namespacing of test cases
The main benefit is small test ids everywhere, though this is with the
downside of needing longer names to properly prefix and avoid
collisions. But this fits into the rest of the scripts with globally
unique names a bit better. This is a C project after all.

The other small benefit is test generators may have an easier time since
per-case symbols can expect to be unique.
2022-09-17 03:03:39 -05:00
Christopher Haster 0781f50edb Ported tests to new framework
This mostly required names for each test case, declarations of
previously-implicit variables since the new test framework is more
conservative with what it declares (the small extra effort to add
declarations is well worth the simplicity and improved readability),
and tweaks to work with not-really-constant defines.

Also renamed test_ -> test, replacing the old ./scripts/test.py,
unfortunately git seems to have had a hard time with this.
2022-06-06 01:35:03 -05:00
Christopher Haster 9f546f154f Updated .travis.yml and added additional geometry constraints
Moved .travis.yml over to use the new test framework. A part of this
involved testing all of the configurations ran on the old framework
and deciding which to carry over. The new framework duplicates some of
the cases tested by the configurations so some configurations could be
dropped.

The .travis.yml includes some extreme ones, such as no inline files,
relocations every cycle, no intrinsics, power-loss every byte, unaligned
block_count and lookahead, and odd read_sizes.

There were several configurations were some tests failed because of
limitations in the tests themselves, so many conditions were added
to make sure the configurations can run on as many tests as possible.
2020-02-11 16:01:57 -06:00
Christopher Haster 77e3078b9f Added/fixed tests for noop writes (where bd error can't be trusted)
It's interesting how many ways block devices can show failed writes:
1. prog can error
2. erase can error
3. read can error after writing (ECC failure)
4. prog doesn't error but doesn't write the data correctly
5. erase doesn't error but doesn't erase correctly

Can read fail without an error? Yes, though this appears the same as
prog and erase failing.

These weren't all simulated by testbd since I unintentionally assumed
the block device could always error. Fixed by added additional bad-black
behaviors to testbd.

Note: This also includes a small fix where we can miss bad writes if the
underlying block device contains a valid commit with the exact same
size in the exact same offset.
2020-02-09 12:00:22 -06:00
Christopher Haster aab6aa0ed9 Cleaned up test script and directory naming
- Removed old tests and test scripts
- Reorganize the block devices to live under one directory
- Plugged new test framework into Makefile

renamed:
- scripts/test_.py -> scripts/test.py
- tests_ -> tests
- {file,ram,test}bd/* -> bd/*

It took a surprising amount of effort to make the Makefile behave since
it turns out the "test_%" rule could override "tests/test_%.toml.test"
which is generated as part of test.py.
2020-01-27 10:16:29 -06:00