e521e2176458ee8df3d173fea20d1966169cba73
789 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
49e3b22907 |
scripts: csv.py: Fixed bottleneck from overlapping by/from fields
Found from some confusing behavior when by/from fields overlap. It turns out when this happens (-bhi -Fhi, for example), the generated getattr for the by field would trigger the __getattribute__ for the overlapping field field, resulting in a fold on _every add operation_. Hopefully you can see where this is a bit of a problem when summing a large number of results (O(n^2)?). --- Fixed by switching getattr to object.__getattribute__ and reconsidering csv.py's entire design. |
||
|
|
04b536eb79 |
runners: bench: Dropped cumulative results
Now that we have csv.py's accumulate(), this information is strictly
redundant!
$ ./scripts/csv.py test.csv \
-bspecific_permutation_here \
-fbench_creaded='accumulate(bench_readed)'
The point of adding accumulate() was to drop these. We really shouldn't
be doubling the size of the csvs with redundant/derivable data.
|
||
|
|
68de9efd17 |
scripts: csv.py: Added --list-computed to expose expr deps/types/etc
Less useful than --list-fields, but fun.
This shows more of the internal expr eval info: input fields + types,
output fields + types + folds, and a small dependency graph showing what
goes where:
$ ./scripts/csv.py --list-computed test.csv \
-bcase='%(case)s+%(m)s' \
-fsimtime='float(bench_simtime)/1.0e9' \
-fsimthroughput='float(n)/max(float(bench_simtime)/1.0e9,1.0e-9)'
i ? .--> case ? ?
suite ? |.-> simtime int sum
case ? -+|.> simthroughput int sum
SKIP_WARMUP ? |||
FILE_SIZE ? |||
SEED ? |||
...
m ? -'||
n int ---+
bench_reads ? ||
bench_progs ? ||
bench_erases ? ||
bench_readed ? ||
bench_progged ? ||
bench_erased ? ||
bench_simtime int ---'
Maybe I was just itching to write another ascii-art renderer.
|
||
|
|
187f35df61 |
scripts: csv.py: Added --list-fields for quick field access
One issue I keep running into with csv.py is that it's difficult to get started with a new/unfamiliar csv file. csv.py itself doesn't know what to do until you start specifying fields, but you can't start specifying fields until you know what fields there are. Add to this the fact that our csv files have so much info shoved in them that their "human readability" is mostly theoretical. The --list-fields flag provides a quick solution to this: $ ./scripts/csv.py --list-fields test.csv i int suite ? case ? SKIP_WARMUP int FILE_SIZE int SEED int ... csv.py doesn't have much info at this stage, but we can at least include the best-effort type guessing we use for field exprs. |
||
|
|
6c458b321c |
scripts: csv.py: Simplified -i/--enumerate to alias -bi -Fi=enumerate()
Now that we have the enumerate expr, -i/--enumerate can be implemented entirely during expr eval: - -i/--enumerate => -bi -Fi=enumerate() - -I/--hidden-enumerate => -Bi -Fi=enumerate() Instead of internally reimplementing the same behavior. This is what our help text implies, so might as well put our money where our mouth is. And the less special internals we have, the better. I considered removing -i/-I completely, but it's quite a convenient flag when debugging csv.py expressions. |
||
|
|
ac338e66f0 |
scripts: csv.py: Added explicit z field, reusing -Z/--children
In an effort to move away from magic usage of -i/--enumerate, this adds an explicit z field for differentiating -r/--hot results (and for normal recursive results). Instead of trying to think of a new flag to control this, this just piggybacks on -Z/--children, which now accepts a tuple: - ./scripts/csv.py -z3 -Z - ./scripts/csv.py -z3 -Zchildren - ./scripts/csv.py -z3 -Zz,children The only tricky bit was needing to insert z in front of the by fields, otherwise it was mostly a simplification from the enumerate mess. Another positive side-effect: -r/--hot (and -z/--depth) now implies -Zz,children, removing the annoying/confusing behavior of hotify folding results by default. |
||
|
|
078a1fb4c6 |
scripts: Adopted explicit underscore in Result._prefix
For consistency with the --prefix flag. I confused myself while debugging some script behavior, and that's no good. |
||
|
|
98279a0b36 |
scripts: csv.py: Simplified --prefix, moved to collect_csv
The current... attempt at an approach was broken and becoming horribly unmaintainable. Two issues found without even looking: 1. Field inference didn't understand prefixes, leading to duplicate by/field fields when attempting to infer by fields with --prefix. 2. Sort wasn't working for some reason, probably because they behavior of sort, defines, etc are really weird since they apply to both by fields and field fields. I considered just dropping support for --prefix completely, this really isn't worth the time, but instead found a simple solution of moving prefix handling to one of the first steps in collect_csv. This has the downside of creating conflicts when a prefixed/non-prefixed field has the same name, but I don't care. --prefix is a niche flag that shouldn't mess with the rest of the code like this, and none of the other scripts really handle field conflicts correctly anyways. |
||
|
|
695b1e94df |
scripts: csv.py: Fixed issues with non-default children/notes fields
- Fixed the initial filter using explicit 'children'/'notes' literals Whoops, how did this happen? - Fixed fold using default children/notes result attributes This one is a bit more excusable, self.children is easy to overlook. But not actual string literals, that's silly. |
||
|
|
84b2e73a30 |
scripts: csv.py: Added enumerate/accumulate exprs
This adds two new exprs to csv.py, useful for sequential data:
enumerate() A number incremented each result
accumulate(a) A running sum across results
To make these work required adding support for cross-row state, thus the
new state field in CsvExpr.Expr.eval.
Once you have that cross-row state, implementing enumerate/accumulate is
pretty straightforward. The only complication being that we need to hash
state by the unique Python id (`id(self)`), otherwise multiple exprs
would share state, which would be pretty weird.
Note that csv.py's pipeline is now quite complex, and stage order is
important!
input --> define --> expr --> folding --> sorting --> output
filtering eval
As a result, it's unfortunately not possible to organize enumerate/
accumulate by by fields. I poked around with the idea but decided it was
too complex (aren't I supposed be building a filesystem?). The guiding
principle behind csv.py is most problems can be solved with more process
substitution.
---
This is a bit clunky since we can't use the existing fold system, but
csv.py is already a pile of hacks, so what's one more?
The reason for the clunkiness is that the original idea behind csv.py
was to treat each folded row independently and order-agnostic. Not the
greatest idea in hindsight, cross-row operations are useful!
|
||
|
|
3978a32156 |
scripts: csv.py: Started adding -g/--accumulate
The idea here is to add some sort of accumulate operation to csv.py, so we can stop cumulative-result clunkiness. It would also be immensely useful as a general function, and -i/--enumerate already sets a precedent for this sort of cross-row behavior. But I'm starting to think using flags here is not the best way, maybe this would be better as a field expr? |
||
|
|
45f1850028 |
scripts: csv.py: Fixed missing -F/--hidden-field parsing
For some reason -F/--hidden-field fields weren't being parsed as a CsvExpr, breaking any attempt to use exprs with hidden fields. Probably just broken during a refactor. Fortunately an easy fix. |
||
|
|
cfafd07414 |
make: Leaned into simtime in Makefile
The value of simtime isn't actually the simtime value, but the simulated throughput, which is easy enough for our csv.py script to calculate (with a daintily placed max to avoid divide-by-zero). Throughput has the benefit of being somewhat size-agnostic, making cross-benchmark comparisons easier. I guess it's technically possible to do something similar with readed/progged/erased numbers, but conceptually that would be really confusing... --- Also renamed test/bench_time -> test/bench_runtime to hopefully prevent confusion between the two time spaces. |
||
|
|
d3dd927de3 |
runners: emubd/kiwibd: Adopted emulated simtime API
This is based on some work in external benchmarks. What's worked well
there is emulating a global simtime based on per-byte estimates.
This moves the emulated simtime into emubd/kiwibd, and extends the idea
with both per-byte and per-op timing estimates for hopefully more
realistic results.
---
The problem is how NAND flash reads work.
Per-byte timing estimates are surprisingly accurate for NOR flash. There
is some overhead for sending the address, but it's mostly dominated by
bus cost (~20ns/B [1]).
NAND flash, on the otherhand, technically does support byte-level reads,
but first needs to read into 2KiB buffer. Surprisingly, these are pretty
close in cost (~19ns/B bus [2] vs ~12ns/B buffer [2]).
This close-ness makes modeling NAND flash difficult. If we set
read_size=1, we risk hiding the cost of small reads, which littlefs3 is
full of (rbyd lookups). If we set read_size=2048, we unfairly penalize
littlefs3 for the same reason.
---
The solution here is to expose both per-byte and per-op timing
estimates. This lets you model NAND reads using two data points:
^
| realtime --> ...............o
| : .....'''' :
| ...............:'''' ^ :
| :....''''' | :
| ..........:::::: simtime :
| .....:'''' :
|o....:::::.....: :
|: :
|: :
+:-----------------------------------------------------------:>
min read max read
Where:
bus_timing = 19ns
buffer_timing = 25us
buffer_size = 2KiB
erase_size = 128KiB
min_read = buffer_timing
max_read = (erase_size/buffer_size)*buffer_timing - buffer_timing
read_timing = min_read
readed_timing = ((max_read - min_read)/erase_size) + bus_timing
simtime = reads*read_timing + readed*readed_timing
(per-op) (per-byte)
This should correctly penalize small reads without complicating
emubd/kiwibd too much.
That's the idea anyways! It will take some use to understand if this is
a reasonable approach.
As a plus, this is a superset of the per-byte model, so both can be used
for realistic vs idealistic simulations (and to test the bus+buffer
model itself).
1: https://www.winbond.com/resource-files/W25Q256JV%20SPI%20RevQ%2002072025%20Plus.pdf
2: https://www.winbond.com/resource-files/W25N01GV%20Rev%20R%20070323.pdf
|
||
|
|
35c09db971 |
scripts: test.py/bench.py: Some small tweaks
- Delayed defines/permutations assignment until after generation. Just a bit of code smell. - Expanded all __eq__, __ne__, __lt__, __gt__, etc magic methods, just to minimize surprises in the future. |
||
|
|
0c6e455961 |
scripts: test.py/bench.py: Allowed expressions in ifdefs/ifndefs
This extends our ifdef/ifndef test attributes to support more complicated logic expressions. So far we haven't really needed this (ifdef/ifndef accepts an implicitly anded list, which has covered everything so far), but I realized there's a simple trick to make this work. For example, in test.toml: ifdef = 'A && !(B || C)' Generated ifdef: #if (defined(A) && !(defined(B) || defined(C))) This doesn't require complex parsing or anything, just a simple regex: s/[a-zA-Z_0-9]\+/defined(&)/g Is using #if defined(A) everywhere instead of #ifdef A more expensive for the compiler? Not sure. But it seems like we're heavily dominated by the single-threaded link time, so I'm not sure we care. |
||
|
|
5511c100ed |
scripts: dbgflags.py: Added -d/--diff, lineno, better find reuse
This started with adding -d/--diff support to dbgflags.py, which is very
useful for comparing flags during test failure.
The flag asserts in our tests generally look like this:
tests/test_mount.toml:171:assert: assert failed with 33570064,
expected eq 33570576
assert(fsinfo.flags == (
Which can now be quickly compared with dbgflags.py:
$ ./scripts/dbgflags.py +i 33570064 -d 33570576
LFS3_I_GBMAP 0x02000000 Global on-disk block-map in use
LFS3_I_REVPERTURB 0x00000010 Mounted with LFS3_M_REVPERTURB
LFS3_I_MKCONSISTENT 0x00000100 Filesystem needs mkconsistent to write
-LFS3_I_LOOKAHEAD 0x00000200 Lookahead buffer is not full
LFS3_I_PREERASE 0x00000400 Blocks can be pre-erased
LFS3_I_COMPACT 0x00000800 Filesystem may have uncompacted metadata
LFS3_I_CKMETA 0x00001000 Metadata checksums not checked recently
LFS3_I_CKDATA 0x00002000 Data checksums not checked recently
The assert print is a bit more annoying than it needs to be, as it only
prints in decimal. But, since our prettyasserts.py only works at the
syntax layer, it's not possible to make it any smarter.
---
To make this diffing work required a couple more features in our
self-parsing Flag class:
- Keep track of lineno, mainly for ordering things
- Moved find logic into a staticmethod on all classes
- Added _sentinel based defaults to find functions
- Allowed self to be non-class in line functions to deduplicate "Unknown
flag" messages
I went ahead and extended these to the other self-parsing classes (Err
and Tag) in case they're useful in the future.
|
||
|
|
b3ab83d5b5 |
Added REVPERTURB, reworked how we handle revision counts
The main change is adding LFS3_M_REVPERTURB, which will be necessary for
preerase allocations, but I got distracted and ended up giving the
revision count subsystem a bit of a refactor.
Main changes:
- Added LFS3_M_REVPERTURB, which ensures the leading bit in the
revision count changes after each allocation/relocation/compaction.
This is generally optional, but will be required for preerase
allocations. Our ecksum system is only reliable if we ensure at least
one bit changes, otherwise the chance of ecksum collision is very
high.
The downside of LFS3_M_REVPERTURB is that we need to read the contents
of the new block to figure out what the bit should change to. Probably
a minimal cost in the system, but still a good reason to make the
behavior optional.
Does LFS3_M_REVPERTURB have any use outside of preerased allocation?
I'm not sure. Maybe it has some niche use reducing the chance of bd
ECC collisions?
- Dropped LFS3_M_REVDBG, but adding low-effort debug bits that are
always enabled.
Making LFS3_M_REVDBG conditional was probably overkill. The flag
checks probably cost more than the actual debug bits when enabled.
Instead, replaced with a simpler, low-effort debug bit system, where
we only set the debug bits during mdir allocation/relocation. These
bits shouldn't change during normal compaction, but we _don't_
introduce debug bits if mounting a filesystem from a driver without
these debug bits.
- Restricted recycle counter to at most 20-bits to make space for
things. This ensures perturb/debug bits don't get overwritten (though
we really only care about perturb bits).
2^20 (~1M) recycles is probably enough for any device littlefs will
run on, especially considering the recycle_count should probably be
several orders of magnitude smaller than the device's expected erase
cycles.
Worst case this can always be increased in the future without
backwards incompatible changes. The only hard requirement for revision
counts is that the full 32-bits are comparable.
- Simplified lfs3_rev_inc and friends, and moved most of the
disk-dependent revision count stuff down into lfs3_rbyd appendrev.
This deduplicates the messy revision count handling in
lfs3_btree_commit_.
Though note the implicit lfs3_rbyd_appendrev now defaults to writing
the btree debug bits ('b'). A bit of a hack, but works for littlefs.
Here's the resulting encoding:
vvvv---- -------- -------- -ddddddd
vvvvrrrr rrrrrr-- -------- -ddddddd
vvvvrrrr rrrrrrnn nnnnnnnn pddddddd
'-.''----.----''----.----' ^'--.--'
'------|----------|------|---|---- 4-bit relocation revision
'----------|------|---|---- recycle-bits recycle counter
'------|---|---- pseudorandom noise (if revnoise)
'---|---- perturb bit (if revperturb)
'---- low-effort debug bits
11-1--- - h = mroot anchor
11-11-1 - m = mdir
11---1- - b = btree node
Note we store revision counts as le32s, so the perturb bit should end up
as the leading bit in the first byte.
Costs a bit more code (mostly because the debug bits are now
unconditional, even if low-effort), but simplifies the codebase:
code stack ctx
before: 35124 2136 660
after: 35144 (+0.1%) 2136 (+0.0%) 660 (+0.0%)
after+yesrevperturb: 35192 (+0.2%) 2136 (+0.0%) 660 (+0.0%)
code stack ctx
gbmap+np before: 38252 2144 776
gbmap+np after: 38272 (+0.1%) 2144 (+0.0%) 776 (+0.0%)
gbmap+np after+yrp: 38328 (+0.2%) 2144 (+0.0%) 776 (+0.0%)
code stack ctx
gbmap+yp before: 38832 2168 796
gbmap+yp after: 38852 (+0.1%) 2168 (+0.0%) 796 (+0.0%)
gbmap+yp after+yrp: 38908 (+0.2%) 2168 (+0.0%) 796 (+0.0%)
|
||
|
|
843412cc79 |
preerase: Implemented the gc side of preerase
Allocating pre-erased blocks gets quite complicated due to our
restricted flash model, but at least the actual pre-erasing is
relatively straightforward:
- We keep track of known preerased state in lfs3->gbmap.preeraser.
- If LFS3_GC_PREERASE is provided during gc work, we increment the
preeraser's known window by scanning the gbmap.
- Any BMFREE ranges we find, we erase a block at a time, and store the
resulting ecksum in a BMERASED range in the gbmap.
- We keep track of how many blocks we erased, and stop early if this
exceeds cfg.gc_preerase_count. This just lets users tune how many
blocks to preerase in case something (?) prevents preerased blocks
from being used.
Some notes:
- We don't really do anything with ranges in lfs3_alloc_preerase. In
theory we could bulk in erase to minimize the number of commits to the
gbmap, but we expect erase to dominate, so this probably isn't worth
it.
And if erase doesn't dominate, why would you bother pre-erasing
blocks?
- Preerasing isn't really a traversal operation, and is managed by a
sort of secondary state machine in lfs3_fs_gc_.
This also means lfs3_trv_read with LFS3_T_PREERASE does nothing, but I
guess that is ok? It's tempting to try to make lfs3_trv_read also
preerase, but it's unclear what block it should return -- it's
probably the wrong API.
- Introducing ecksums actually went quite a bit smoother than I
expected. Though it helps ecksums are the only optional payload, no
type punning or anything.
Ecksums do muddy the gbmap's design a bit, unfortunately. The main
issue being that we can only merge BMERASED ranges with equal ecksums.
This makes BMERASED ranges less compressable than the others, and may
be one reason to limit cfg.gc_preerase_count.
However:
1. This is where I think it's useful to emphasize that the gbmap's
responsibility is to track _free_ blocks, in-use blocks are
secondary.
When allocating, we're going to stop at the first BMFREE/BMERASED,
but may need to skip over an unbounded number of BMINUSE/BMBAD
blocks. So the compressability of BMFREE/BMERASED ranges should
have less of an impact on block allocation.
2. In practice, most flash uses consistent erase values, so the
resulting ecksums will probably be compressable. The exceptions are
noop-erases (SD/eMMC, RAM, NVRAM, etc), and encryption with block
address permutation?
Though noop-erases are a pretty big exception.
Code changes:
code stack ctx
before: 35116 2136 660
after: 35116 (+0.0%) 2136 (+0.0%) 660 (+0.0%)
code stack ctx
gbmap+np before: 38040 2136 776
gbmap+np after: 38188 (+0.4%) 2144 (+0.4%) 776 (+0.0%)
code stack ctx
gbmap+yp before: 38040 2136 776
gbmap+yp after: 38608 (+1.5%) 2144 (+0.4%) 796 (+2.6%)
|
||
|
|
4f22d8c591 |
scripts: dbgflags.py: Added support for flag aliases
Also tweaked related flag comments a bit.
---
This adds another internal flag modifier ('a') to dbgflags.py to
indicate a flag is an alias for multiple other flags.
These still show up in -l/--list and name searches:
$ ./scripts/dbgflags.py -l +ck
LFS3_CK_MKCONSISTENT 0x00000100 Make the filesystem consistent
LFS3_CK_LOOKAHEAD 0x00000200 Repopulate lookahead buffer
LFS3_CK_COMPACT 0x00000800 Compact metadata logs
LFS3_CK_CKMETA 0x00001000 Check metadata checksums
LFS3_CK_CKDATA 0x00002000 Check metadata + data checksums
LFS3_CK_CK 0x00003000 Alias for all check work
LFS3_CK_GC 0x00003b00 Alias for all gc work
But are hidden from value searches, as they would be redundant and the
specific low-level flags are probably more useful:
$ ./scripts/dbgflags.py +ck 0x00003000
LFS3_CK_CKMETA 0x00001000 Check metadata checksums
LFS3_CK_CKDATA 0x00002000 Check metadata + data checksums
|
||
|
|
d54fef8099 |
Reorganized traversal flags again
One nice thing about merging LOOKAHEAD + LOOKGBMAP, is now our core
traversal flags fit in a single byte. This is useful for organizing
things, especially so as the traversal flags seem to permeate into
basically every flag set.
The main change was to actually group these flags into a byte, which
helps readability and in theory could make some bulk accesses cheaper
(in practice I don't think we currently leverage this):
T_MODE 0x00000001 ---- ---- ---- ---- ---- ---- ---- ---1
T_RDONLY 0x00000000 ---- ---- ---- ---- ---- ---- ---- ----
T_RDWR 0x00000001 ---- ---- ---- ---- ---- ---- ---- ---1
T_MTREEONLY 0x00000002 ---- ---- ---- ---- ---- ---- ---- --1-
T_EXCL 0x00000008 ---- ---- ---- ---- ---- ---- ---- 1---
T_MKCONSISTENT 0x00000100 ---- ---- ---- ---- ---- ---1 ---- ----
T_LOOKAHEAD 0x00000200 ---- ---- ---- ---- ---- --1- ---- ----
T_PREERASE* 0x00000400 ---- ---- ---- ---- ---- -1-- ---- ----
T_COMPACT 0x00000800 ---- ---- ---- ---- ---- 1--- ---- ----
T_CKMETA 0x00001000 ---- ---- ---- ---- ---1 ---- ---- ----
T_CKDATA 0x00002000 ---- ---- ---- ---- --1- ---- ---- ----
T_REPAIRMETA* 0x00004000 ---- ---- ---- ---- -1-- ---- ---- ----
T_REPAIRDATA* 0x00008000 ---- ---- ---- ---- 1--- ---- ---- ----
t_EVICT* 0x00000010 ---- ---- ---- ---- ---- ---- ---1 ----
t_TYPE 0xf0000000 1111 ---- ---- ---- ---- ---- ---- ----
t_ZOMBIE 0x08000000 ---- 1--- ---- ---- ---- ---- ---- ----
t_CKPOINTED 0x04000000 ---- -1-- ---- ---- ---- ---- ---- ----
t_DIRTY 0x02000000 ---- --1- ---- ---- ---- ---- ---- ----
t_STALE 0x01000000 ---- ---1 ---- ---- ---- ---- ---- ----
t_BTYPE 0x00ff0000 ---- ---- 1111 1111 ---- ---- ---- ----
* Planned
This gives btype a full byte as well, which is a bit overkill, but can
be reduced in the future if we run into traversal flag pressure.
This also pushes some future planned flags (DEDUP, COMPR, etc) into
higher-order bits, but that's not the end of the world.
Code changes basically nothing:
code stack ctx
before: 35152 2136 660
after: 35152 (+0.0%) 2136 (+0.0%) 660 (+0.0%)
code stack ctx
gbmap before: 38080 2136 776
gbmap after: 38076 (-0.0%) 2136 (+0.0%) 776 (+0.0%)
|
||
|
|
7a57b1e2bd |
Renamed LFS3_T_COMPACTMETA -> LFS3_T_COMPACT (and gc_compact_thresh)
This effectively reverts
|
||
|
|
347c7b7290 |
scripts: gdb: Forward +flags to dbg scripts
dbgflags.py now uses +flags to indicate flag namespaces, but our gdb script only forwarded -f/--flags, which made dbgflags a bit of a pain to use in the debugger! Fortunately an easy fix. Now this works: (gdb) dbgflags +t trv.gc.t.h.flags LFS3_T_RDWR 0x00000000 Open traversal as read and write LFS3_T_MKCONSISTENT 0x00000100 Make the filesystem consistent LFS3_T_LOOKAHEAD 0x00000200 Repopulate lookahead buffer LFS3_t_TRAVERSAL 0x60000000 Type = traversal LFS3_t_MDIR 0x00010000 Btype = mdir |
||
|
|
ffc565508a |
alloc: Merged LOOKAHEAD+LOOKGBMAP -> single LOOKAHEAD flag
Our flag space is already really packed, and I'm not sure having these
as separate flags is meaningful or useful for users. They both indicate
to repopulate allocators, and most users probably won't care that there
are two subtly different allocators operating under the hood.
There's an argument that LOOKAHEAD not touching disk is a useful
distinction, but in practice you really only need LOOKAHEAD work when
mounted RDWR.
So, merged the behaviors of LOOKAHEAD + LOOKGBMAP such that
LFS3_*_LOOKAHEAD requests repopulation of all allocators based on
gc_lookahead_thresh and gc_lookgbmap_thresh.
In priority order (some notes below):
1. If max(lookahead, gbmap) < gc_lookahead_thresh => repop lookahead
2. If gbmap < gc_lookgbmap_thresh => repop gbmap
As a plus, this makes it easier to avoid LFS3_IFDEF_GBMAP mess.
---
It's interesting to note LFS3_*_LOOKAHEAD will still repopulate the
lookahead buffer when the gbmap is present, but only if this would gain
more knowledge than was is currently in the gbmap.
I considered disabling lookahead scans completely when we have a gbmap,
but repopulating the lookahead buffer is still useful if the gbmap is at
risk of exhaustion. This is what gc_lookahead_thresh is for anyways, and
users can set gc_lookahead_thresh=0 if they want to disable this
behavior.
Relatedly, lookahead scans are actually prioritized over gbmap scans
(when they would gain knowledge). In theory this minimizes gc latency,
as gbmap scans risk triggering a full lookahead scan when building the
new gbmap.
---
Code changes minimal:
code stack ctx
before: 35152 2136 660
after: 35152 (+0.0%) 2136 (+0.0%) 660 (+0.0%)
code stack ctx
gbmap before: 38076 2136 776
gbmap after: 38080 (+0.0%) 2136 (+0.0%) 776 (+0.0%)
|
||
|
|
cd7dd37888 |
scripts: dbglfs3.py: Adopted % as littlefs root dir character
The idea is this is similar to ~ for the home directory, hopefully simplifying littlefs path parsing in scripts: - /hi -> hi in root dir - ./hi -> hi in current dir - ../hi -> hi in parent dir - ~/hi -> hi in home dir - %/hi -> hi in littlefs root dir Note this only works when standalone: - ~hi != ~/hi - %hi != %/hi And files named % can still be referenced with a ./ prefix: - ./% -> % in current dir - %/% -> % in littlefs root dir --- This is probably overkill for dbglfs3.py, as the arg ordering was already enough to disambiguate disk path vs mroot address vs littlefs path, but eventually I think the idea will be useful for more powerful scripts. A hypothetical: $ mklfs3 cp disk -b4096 -r image_files %/image_files |
||
|
|
0d5cdeaeb8 |
scripts: dbgflags.py: Make SEEK_MODE non-internal
This is still a hack to make the seek _enum_ appear somewhat readable in our dbg _flags_ script. But the previously internal SEEK_MODE was causing all seek flags to be hidden from -l/--list confusingly. |
||
|
|
192206b66d |
scripts: dbgflags.py: Renamed --o -> +o for prefix namespaces
Just a bit less typing than --o, and lowers risk of conflicts with actual flags we may care about. To be honest I was procrastinating because I thought this would be a lot more work! I was prepared to write a hacky secondary parser, but argparse already supports this natively with prefix_chars='-+'. Yay! |
||
|
|
9728cda682 |
runners: Renamed -a/--all -> --force
Test/bench filters have proven to be mostly non-optional, protecting against bad configuration that doesn't make any sense. It's still valid to want to override test filters sometimes, but using a more, uh, forceful verb probably makes sense here. The shortform would conflict with -f/--fail, so no shortform flag for this, but some argue --force should never have a shortform flag anyways. |
||
|
|
efdcb912f5 |
scripts: Renamed -w/--wait -> -t/--wait
I'm trying to avoid the inevitable conflict with -w/--word, which will probably become important when exploring non-32-bit filesystem configurations. Renaming this to -t/--wait still conflicts with -t/--tree and -t/--tiny, but as a debug-only flag, I think these are less important. Oh, and -t/--trace, but test.py/bench.py are already quite different in their flag naming (see -d/--disk vs -d/--diff). --- Renamed a few other flags while tweaking things: - -t/--tiny -> --tiny (dropped shortform) - -w/--word-bits -> -w/--word/--word-bits - -t/--tree -> -R/--tree/--rbyd/--tree-rbyd - -R/--tree-rbyd -> -Y/--rbyd-all/--tree-rbyd-all - -B/--tree-btree -> -B/--btree/--tree-btree After tinkering with it a bit, I think the -R/-Y/-B set of flags are a decent way to organize the tree renderers. At least --tree-rbyd-all does a better job of describing the difference between --tree-rbyd and --tree-rbyd-all. |
||
|
|
9bc41099f0 |
scripts: Changed -~/--sleep -> -w/--wait to sleep after -k/--keep-open
This changes -w/--wait to sleep _after_ -k/--keep-open, instead of including the time spent waiting on inotifywait in the sleep time. 1. It's easier, no need to keep track of when we started waiting. 2. It's simpler to reason about. 3. It trivially avoids the multiple wakeup noise that plagued watch.py + vim (vim likes to do a bunch of renaming and stuff when saving files, including the file 4913 randomly?) Avoiding this was previously impossible because -~/--sleep was effectively a noop when combined with -k/--keep-open. --- Also renamed from -~/--sleep -> -w/--wait, which is a bit more intuitive and avoids possible shell issues with -~. To make this work, dropped the -w/--block-cycles shortform flag in dbgtrace.py. It's not like this flag is ever used anyways. Though at the moment this is ignoring the possible conflict with -w/--word-bits... |
||
|
|
7da44f12ae |
Added redund hints to more tags
Well, kinda. At the moment we don't have any reund support (it's a TODO), so arguably redund=0 and this is just a comment tweak. Though our mdirs _are_ already redund=1... so maybe these should actually set redund=1? It's unclear, so for now I've just tweaked the comment, and we should probably revisit when _actually_ implementing meta/data redundancy. --- Note this only really affects struct tags: LFS3_TAG_STRUCT 0x04tt v--- -1-- +ttt tttt LFS3_TAG_BRANCH 0x040r v--- -1-- +--- --rr LFS3_TAG_DATA 0x0404 v--- -1-- +--- -1rr LFS3_TAG_BLOCK 0x0408 v--- -1-- +--- 1err LFS3_TAG_DDKEY* 0x0410 v--- -1-- +--1 --rr LFS3_TAG_DID 0x0420 v--- -1-- +-1- ---- LFS3_TAG_BSHRUB 0x0428 v--- -1-- +-1- 1-rr LFS3_TAG_BTREE 0x042c v--- -1-- +-1- 11rr LFS3_TAG_MROOT 0x0431 v--- -1-- +-11 --rr LFS3_TAG_MDIR 0x0435 v--- -1-- +-11 -1rr LFS3_TAG_MSHRUB+ 0x0438 v--- -1-- +-11 1-rr LFS3_TAG_MTREE 0x043c v--- -1-- +-11 11rr LFS3_TAG_BMRANGE 0x044u v--- -1-- +1-- ++uu LFS3_TAG_BMFREE 0x0440 v--- -1-- +1-- ---- LFS3_TAG_BMINUSE 0x0441 v--- -1-- +1-- ---1 LFS3_TAG_BMERASED 0x0442 v--- -1-- +1-- --1- LFS3_TAG_BMBAD 0x0443 v--- -1-- +1-- --11 LFS3_TAG_DDRC* 0x0450 v--- -1-- +1-1 ---- LFS3_TAG_DDPCOEFF* 0x0451 v--- -1-- +1-1 ---1 LFs3_TAG_PCOEFFMAP* 0x0460 v--- -1-- +11- ---- This redund hint may be useful for debugging and the theoretical CKMETAREDUND feature. |
||
|
|
cf34ba9aca |
Rearranged tag encodings, reserved suptype=0 for internal tags
This was motivated by a discussion with a gh user, in which it was noted
that not having a reserved suptype for internal tags risks potential
issues with long-term future tag compatibility.
I think the risk is low, but, without a reserved suptype, it _is_
possible for a future tag to conflict with an internal tag in an older
driver version, potentially and unintentionally breaking compatibility.
Note this is especially concerning during mdir compactions, where we
copy tags we may not understand otherwise.
In littlefs2 we reserved suptype=0x100, though this was mostly an
accident due to saturating the 3-bit suptype space. With the larger tag
space in littlefs3, the reserved suptype=0x100 was dropped.
---
Long story short, this reserves suptype=0 for internal flags (well, and
null, which is _mostly_ internal only, but does get written to disk as
unreachable tags).
Unfortunately, adding a new suptype _did_ require moving a bunch of
stuff around:
LFS3_TAG_NULL 0x0000 v--- ---- +--- ----
LFS3_TAG_INTERNAL 0x00tt v--- ---- +ttt tttt
LFS3_TAG_CONFIG 0x01tt v--- ---1 +ttt tttt
LFS3_TAG_MAGIC 0x0131 v--- ---1 +-11 --rr
LFS3_TAG_VERSION 0x0134 v--- ---1 +-11 -1--
LFS3_TAG_RCOMPAT 0x0135 v--- ---1 +-11 -1-1
LFS3_TAG_WCOMPAT 0x0136 v--- ---1 +-11 -11-
LFS3_TAG_OCOMPAT 0x0137 v--- ---1 +-11 -111
LFS3_TAG_GEOMETRY 0x0138 v--- ---1 +-11 1---
LFS3_TAG_NAMELIMIT 0x0139 v--- ---1 +-11 1--1
LFS3_TAG_FILELIMIT 0x013a v--- ---1 +-11 1-1-
LFS3_TAG_ATTRLIMIT? 0x013b v--- ---1 +-11 1-11
LFS3_TAG_GDELTA 0x02tt v--- --1- +ttt tttt
LFS3_TAG_GRMDELTA 0x0230 v--- --1- +-11 ----
LFS3_TAG_GBMAPDELTA 0x0234 v--- --1- +-11 -1rr
LFS3_TAG_GDDTREEDELTA* 0x0238 v--- --1- +-11 1-rr
LFS3_TAG_GPTREEDELTA* 0x023c v--- --1- +-11 11rr
LFS3_TAG_NAME 0x03tt v--- --11 +ttt tttt
LFS3_TAG_BNAME 0x0300 v--- --11 +--- ----
LFS3_TAG_REG 0x0301 v--- --11 +--- ---1
LFS3_TAG_DIR 0x0302 v--- --11 +--- --1-
LFS3_TAG_STICKYNOTE 0x0303 v--- --11 +--- --11
LFS3_TAG_BOOKMARK 0x0304 v--- --11 +--- -1--
LFS3_TAG_SYMLINK? 0x0305 v--- --11 +--- -1-1
LFS3_TAG_SNAPSHOT? 0x0306 v--- --11 +--- -11-
LFS3_TAG_MNAME 0x0330 v--- --11 +-11 ----
LFS3_TAG_DDNAME* 0x0350 v--- --11 +1-1 ----
LFS3_TAG_DDTOMB* 0x0351 v--- --11 +1-1 ---1
LFS3_TAG_STRUCT 0x04tt v--- -1-- +ttt tttt
LFS3_TAG_BRANCH 0x040r v--- -1-- +--- --rr
LFS3_TAG_DATA 0x0404 v--- -1-- +--- -1--
LFS3_TAG_BLOCK 0x0408 v--- -1-- +--- 1err
LFS3_TAG_DDKEY* 0x0410 v--- -1-- +--1 ----
LFS3_TAG_DID 0x0420 v--- -1-- +-1- ----
LFS3_TAG_BSHRUB 0x0428 v--- -1-- +-1- 1---
LFS3_TAG_BTREE 0x042c v--- -1-- +-1- 11rr
LFS3_TAG_MROOT 0x0431 v--- -1-- +-11 --rr
LFS3_TAG_MDIR 0x0435 v--- -1-- +-11 -1rr
LFS3_TAG_MSHRUB+ 0x0438 v--- -1-- +-11 1---
LFS3_TAG_MTREE 0x043c v--- -1-- +-11 11rr
LFS3_TAG_BMRANGE 0x044u v--- -1-- +1-- ++uu
LFS3_TAG_BMFREE 0x0440 v--- -1-- +1-- ----
LFS3_TAG_BMINUSE 0x0441 v--- -1-- +1-- ---1
LFS3_TAG_BMERASED 0x0442 v--- -1-- +1-- --1-
LFS3_TAG_BMBAD 0x0443 v--- -1-- +1-- --11
LFS3_TAG_DDRC* 0x0450 v--- -1-- +1-1 ----
LFS3_TAG_DDPCOEFF* 0x0451 v--- -1-- +1-1 ---1
LFs3_TAG_PCOEFFMAP* 0x0460 v--- -1-- +11- ----
LFS3_TAG_ATTR 0x06aa v--- -11a +aaa aaaa
LFS3_TAG_UATTR 0x06aa v--- -11- +aaa aaaa
LFS3_TAG_SATTR 0x07aa v--- -111 +aaa aaaa
LFS3_TAG_SHRUB 0x1kkk v--1 kkkk +kkk kkkk
LFS3_TAG_ALT 0x4kkk v1cd kkkk +kkk kkkk
LFS3_TAG_CKSUM 0x300p v-11 ---- ++++ +pqq
LFS3_TAG_NOTE 0x3100 v-11 ---1 ++++ ++++
LFS3_TAG_ECKSUM 0x3200 v-11 --1- ++++ ++++
LFS3_TAG_GCKSUMDELTA 0x3300 v-11 --11 ++++ ++++
* Planned
+ Reserved
? Hypothetical
Some additional notes:
- I was on the fence on keeping the 0x30 prefix on config tags now that
it is not longer needed to differentiate from null, but ultimately
decided to keep it because: 1. it's fun, 2. it decreases the chance
of false positives, 3. it keeps the redund bits readable in hexdumps,
and 4. it reserves some tags < config, which is useful since order
matters.
Instead, I pushed the 0x30 prefix to _more_ tags, mainly gstate.
As a coincidence, meta related tags (MNAME, MROOT, MRTREE) all shifted
to also have the 0x30 prefix, which is a nice bit of unexpected
consistency.
- I also considered reserving the redund bits across the config tags
similarly to what we've done in struct/gstate tags, but decided
against it as 1. it significantly reduces the config tag space
available, and 2. makes alignment with VERSION + R/W/OCOMPAT a bit
awkward.
Instead I think would should relax the redund bit alignment in other
suptypes, though in practice the intermixing of non-redund and redund
tags makes this a bit difficult.
Maybe we should consider including redund bits as a hint for things
like DATA? DDKEY? BSHRUB? etc?
- I created a bit more space for file btree struct tags, allowing for
both the future planned DDKEY, and BLOCK with optional erased-bit. We
don't currently use this, but it may be useful for the future planned
gddtree, which in-theory can track erased-state in partially written
file blocks.
Currently tracking erased-state in file blocks is difficult due to
the potential of multiple references, and inability to prevent ecksum
conflicts in raw data blocks.
- UATTR/SATTR bumped up to 0x600/0x700 to keep the 1-bit alignment,
leaving the suptype 0x500 unused. Though this may be useful if we ever
run out of struct tags (suptype=0x400), which is likely where most new
tags will go.
---
Code changes were minimal, but with a bunch of noise:
code stack ctx
before: 35912 2280 660
after: 35920 (+0.0%) 2280 (+0.0%) 660 (+0.0%)
code stack ctx
gbmap before: 38800 2296 772
gbmap after: 38812 (+0.0%) 2296 (+0.0%) 772 (+0.0%)
|
||
|
|
c16c4a00d3 |
ck: Merged FSCK+CK -> CK flag namespace
Unintentionally arriving at the infamous "fsck" name is a bit funny. But it's probably something we don't want to conflict with if we can help it, on the off chance we want a sort of lfs3_fsck function in the future. (This is all hypothetical, but lfs3_fsck may expect an unmounted filesystem, and have a much larger scope than lfs3_fs_ck. Though typing this out now I'm realizing how confusing that might be...) Since lfs3_file_ck and lfs3_fs_ck share a subset of flags, it's not _entirely_ unreasonable for lfs3_file_ck and lfs3_fs_ck to share the same namespace. There's a risk of confusing users around what flags lfs3_file_ck accepts, but we have asserts, and said flags (LFS3_CK_MKCONSISTENT, LFS3_CK_LOOKAHEAD, etc) just don't really make sense in lfs3_file_ck: fs file y LFS3_CK_MKCONSISTENT 0x00000800 Make the filesystem consistent y LFS3_CK_LOOKAHEAD 0x00001000 Repopulate lookahead buffer y LFS3_CK_LOOKGBMAP 0x00002000 Repopulate the gbmap y LFS3_CK_PREERASE* 0x00004000 Pre-erase unused blocks y LFS3_CK_COMPACTMETA 0x00008000 Compact metadata logs y y LFS3_CK_CKMETA 0x00010000 Check metadata checksums y y LFS3_CK_CKDATA 0x00020000 Check metadata + data checksums y y LFS3_CK_REPAIRMETA* 0x00040000 Repair data blocks y y LFS3_CK_REPAIRDATA* 0x00080000 Repair metadata + data blocks * Planned Another option would be to document that lfs3_fs_ck accepts both LFS3_CK_* _and_ LFS3_GC_* flags, but I worry that would be more confusing. It would also lock us into supporting all LFs3_GC_* flags in lfs3_fs_ck, which may not always be the case. Though this is an argument for doing away with the whole LFS3_M/F/CK/GC/I_* duplication... (tbh another reason for this is to reduce the number of namespaces by at least one). No code changes. |
||
|
|
5c0cebb00b |
ck: Traded ckmeta/ckdata for flag-based ck functions
TLDR: Replaced lfs3_file_ckmeta/ckdata and lfs3_fs_ckmeta/ckdata with
flag based ck functions:
- lfs3_file_ckmeta -> lfs3_file_ck + LFS3_CK_CKMETA
- lfs3_file_ckdata -> lfs3_file_ck + LFS3_CK_CKDATA
- lfs3_fs_ckmeta -> lfs3_fs_ck + LFS3_FSCK_CKMETA
- lfs3_fs_ckdata -> lfs3_fs_ck + LFS3_FSCK_CKDATA
Note lfs3_fs_ck is equivalent to lfs3_fs_gc, but:
1. Performs the work in one call (equivalent to littlefs2's lfs2_fs_gc)
2. Takes flags at call time (like lfs3_mount) instead of cfg time (like
lfs3_fs_gc)
3. Avoids the constant RAM necessary to track incremental GC state
---
Motivation:
I've been thinking: It's a bit weird that users are able to one-shot
janitorial work in lfs3_mount, but there's no equivalent function after
the filesystem is mounted.
Originally this is what lfs3_fs_gc was for, but after adding support for
incremental GC, it made sense to hide lfs3_fs_gc behind the opt-in
LFS3_GC ifdef due to the extra (ironically non-gc-able) state.
In theory lfs3_trv_t fills a bit of the gap, but, without the internal
i_flag handling and traversal restarts, it's a bit hard to use. And
basically requires duplicating said log, which we need anyways for
lfs3_mount!
So ideally we'd add an explicit one-shot GC function, but now lfs3_fs_gc
is taken.
While thinking about alternative names, I realized we can just call this
lfs3_fs_ck and completely replace lfs3_fs_ckmeta/ckdata.
This has some extra benefits:
- Avoids an explosion of ckmeta/ckdata/repairmeta/repairdata functions
- Discourages redundant traversals that could accomplish more work
- Makes it less confusing that ckdata implies ckmeta
---
I also tweaked lfs3_file_ck to match, but note that lfs3_file_ck is
internally very different from lfs3_fs_ck. For one, lfs3_file_ck only
supports "actual" check flags (LFS3_CK_*) vs all gc flags (LFS3_FSCK_*):
lfs3_file_ck:
LFS3_CK_CKMETA 0x00010000 Check metadata checksums
LFS3_CK_CKDATA 0x00020000 Check metadata + data checksums
LFS3_CK_REPAIRMETA* 0x00040000 Repair metadata blocks
LFS3_CK_REPAIRDATA* 0x00080000 Repair metadata + data blocks
* Planned
lfs3_fs_ck:
LFS3_FSCK_MKCONSISTENT 0x00000800 Make the filesystem consistent
LFS3_FSCK_LOOKAHEAD 0x00001000 Repopulate lookahead buffer
LFS3_FSCK_LOOKGBMAP 0x00002000 Repopulate the gbmap
LFS3_FSCK_PREERASE* 0x00004000 Pre-erase unused blocks
LFS3_FSCK_COMPACTMETA 0x00008000 Compact metadata logs
LFS3_FSCK_CKMETA 0x00010000 Check metadata checksums
LFS3_FSCK_CKDATA 0x00020000 Check metadata + data checksums
LFS3_FSCK_REPAIRMETA* 0x00040000 Repair metadata blocks
LFS3_FSCK_REPAIRDATA* 0x00080000 Repair metadata + data blocks
* Planned
As a plus, this also saves a bit of code:
code stack ctx
before: 35968 2280 660
after: 35924 (-0.1%) 2280 (+0.0%) 660 (+0.0%)
code stack ctx
gbmap before: 38828 2296 772
gbmap after: 38812 (-0.0%) 2296 (+0.0%) 772 (+0.0%)
|
||
|
|
2d68db965b |
Rearranged on-disk compat flags
Other than moving things around to make space for planned features, this
also adopts the idea of allowing compat flags to be ored into a single
32-bit integer, at least in the short-term.
Note though that these are still stored in separate wcompat/rcompat
tags, to make compat tests easier, and we may introduce conflicting
flags in the future if we run out of 32-bits. This is just an indulgence
to potentially make tooling/debugging easier until that happens.
Rcompat flags:
RCOMPAT_NONSTANDARD+
0x00000001 ---- ---- ---- ---- ---- ---- ---- ---1
RCOMPAT_WRONLY+ 0x00000004 ---- ---- ---- ---- ---- ---- ---- -1--
RCOMPAT_MMOSS 0x00000010 ---- ---- ---- ---- ---- ---- ---1 ----
RCOMPAT_MSPROUT+ 0x00000020 ---- ---- ---- ---- ---- ---- --1- ----
RCOMPAT_MSHRUB+ 0x00000040 ---- ---- ---- ---- ---- ---- -1-- ----
RCOMPAT_MTREE 0x00000080 ---- ---- ---- ---- ---- ---- 1--- ----
RCOMPAT_BMOSS+ 0x00000100 ---- ---- ---- ---- ---- ---1 ---- ----
RCOMPAT_BSPROUT+ 0x00000200 ---- ---- ---- ---- ---- --1- ---- ----
RCOMPAT_BSHRUB 0x00000400 ---- ---- ---- ---- ---- -1-- ---- ----
RCOMPAT_BTREE 0x00000800 ---- ---- ---- ---- ---- 1--- ---- ----
RCOMPAT_MDIRR1* 0x00001000 ---- ---- ---- ---- ---1 ---- ---- ----
RCOMPAT_MDIRR2* 0x00002000 ---- ---- ---- ---- --1- ---- ---- ----
RCOMPAT_MDIRR3* 0x00003000 ---- ---- ---- ---- --11 ---- ---- ----
RCOMPAT_BTREER1* 0x00004000 ---- ---- ---- ---- -1-- ---- ---- ----
RCOMPAT_BTREER2* 0x00008000 ---- ---- ---- ---- 1--- ---- ---- ----
RCOMPAT_BTREER3* 0x0000c000 ---- ---- ---- ---- 11-- ---- ---- ----
RCOMPAT_GRM 0x00010000 ---- ---- ---- ---1 ---- ---- ---- ----
RCOMPAT_GMV? 0x00020000 ---- ---- ---- --1- ---- ---- ---- ----
RCOMPAT_GDDTREE* 0x00100000 ---- ---- ---1 ---- ---- ---- ---- ----
RCOMPAT_GPTREE* 0x00200000 ---- ---- --1- ---- ---- ---- ---- ----
RCOMPAT_DATAR1* 0x00400000 ---- ---- -1-- ---- ---- ---- ---- ----
RCOMPAT_DATAR2* 0x00800000 ---- ---- 1--- ---- ---- ---- ---- ----
RCOMPAT_DATAR3* 0x00c00000 ---- ---- 11-- ---- ---- ---- ---- ----
rcompat_OVERFLOW+ 0x80000000 1--- ---- ---- ---- ---- ---- ---- ----
* Planned
+ Reserved
? Hypothetical
Wcompat flags:
WCOMPAT_NONSTANDARD+
0x00000001 ---- ---- ---- ---- ---- ---- ---- ---1
WCOMPAT_RDONLY+ 0x00000002 ---- ---- ---- ---- ---- ---- ---- --1-
WCOMPAT_GCKSUM 0x00040000 ---- ---- ---- -1-- ---- ---- ---- ----
WCOMPAT_GBMAP 0x00080000 ---- ---- ---- 1--- ---- ---- ---- ----
WCOMPAT_DIR 0x01000000 ---- ---1 ---- ---- ---- ---- ---- ----
WCOMPAT_SYMLINK? 0x02000000 ---- --1- ---- ---- ---- ---- ---- ----
WCOMPAT_SNAPSHOT? 0x04000000 ---- -1-- ---- ---- ---- ---- ---- ----
wcompat_OVERFLOW+ 0x80000000 1--- ---- ---- ---- ---- ---- ---- ----
+ Reserved
? Hypothetical
Ocompat flags:
OCOMPAT_NONSTANDARD+
0x00000001 ---- ---- ---- ---- ---- ---- ---- ---1
ocompat_OVERFLOW+ 0x80000000 1--- ---- ---- ---- ---- ---- ---- ----
+ Reserved
Other notes:
- M* and B* struct flags were reordered to match META -> DATA order
elsewhere. This no longer matches the tag ordering, but there's an
argument the B* tags apply more generally (all btrees) than the B*
compat flag (only file btrees).
- MDIR/BTREE/DATA redund flags were moved near relevant flags, rather
than sticking them in the higher-order bits as we are planning to do
in the M_*/F_* flags. The compat flags already won't match because of
the mdir/btree split (which is IMO too much detail to include in
M_*/F_* flags, but hard to argue against in the compat flags), and
this keeps the highest bit free for OVERFLOW, which is useful
internally.
- Moving DIR to the current-highest bit makes it easy to add 6 more file
types (7 if you ignore OVERFLOW), before things start getting cramped.
No code changes.
|
||
|
|
8233ac9dfe |
Renamed RELOOKAHEAD -> LOOKAHEAD, REGBMAP -> LOOKGBMAP
Yeah, after using these for a bit, the RE* names were not great. Trying LOOK* now, as an alternative that hopefully still implies the similar behavior without needing an additional prefix for LOOKAHEAD: - LFS3_*_RELOOKAHEAD -> LFS3_*_LOOKAHEAD - LFS3_*_REGBMAP -> LFS3_*_LOOKGBMAP - cfg.regbmap_thresh -> cfg.lookgbmap_thresh - cfg.gc_relookahead_thresh -> cfg.gc_lookahead_thresh - cfg.gc_regbmap_thresh -> cfg.gc_lookgbmap_thresh |
||
|
|
4ccc8dc120 |
Added support for all mount-traversal flags in lfs3_format
I mean, why not? These redirect to the same internal lfs3_fs_gc_ function anyways. Might as well keep things consistent. Added: LFS3_F_MKCONSISTENT 0x00000800 Make the filesystem consistent LFS3_F_RELOOKAHEAD 0x00001000 Repopulate lookahead buffer LFS3_F_MKCONSISTENT is guaranteed to be a noop, but LFS3_F_RELOOKAHEAD forces a filesystem traversal, which may have some niche use case. No code changes. |
||
|
|
b01a385bc9 |
Added LFS3_F_REGBMAP and LFS3_F_COMPACTMETA
These are unlikely to make much progress, but that doesn't seem like a great reason to disallow these flags in lfs3_format: LFS3_F_REGBMAP 0x00002000 Repopulate the gbmap LFS3_F_COMPACTMETA 0x00008000 Compact metadata logs These are actually guaranteed to do _no_ work when formatting _without_ the gbmap, but with the gbmap it's less clear. Looking forward to the planned ckfactory feature, these may be useful for cleaning up any rbyd commits created as a part of building the initial gbmap. --- Also tweaked the formatting for LFS3_F_* flags a bit, including making all ifdefs explicit (mainly ifdef LFS3_RDONLY). Mixed ifdefs are a real pain to read. No code changes. |
||
|
|
9e75138f7a |
Rearranged O/M/F/GC/I flags
Now that we don't need to encode tstate info in our traversal flags, we
can move things around to be a bit more comfortable.
This is also after some tweaking to make space for planned features:
O flags:
O_MODE 0x00000003 ---- ---- ---- ---- ---- ---- ---- --11
O_RDONLY 0x00000000 ---- ---- ---- ---- ---- ---- ---- ----
O_WRONLY 0x00000001 ---- ---- ---- ---- ---- ---- ---- ---1
O_RDWR 0x00000002 ---- ---- ---- ---- ---- ---- ---- --1-
O_CREAT 0x00000004 ---- ---- ---- ---- ---- ---- ---- -1--
O_EXCL 0x00000008 ---- ---- ---- ---- ---- ---- ---- 1---
O_TRUNC 0x00000010 ---- ---- ---- ---- ---- ---- ---1 ----
O_APPEND 0x00000020 ---- ---- ---- ---- ---- ---- --1- ----
O_FLUSH 0x00000040 ---- ---- ---- ---- ---- ---- -1-- ----
O_SYNC 0x00000080 ---- ---- ---- ---- ---- ---- 1--- ----
O_DESYNC 0x00100000 ---- ---- ---1 ---- ---- ---- ---- ----
O_DEDAG* 0x00000100 ---- ---- ---- ---- ---- ---1 ---- ----
O_DEDUP* 0x00000200 ---- ---- ---- ---- ---- --1- ---- ----
O_COMPR? 0x00000400 ---- ---- ---- ---- ---- -1-- ---- ----
O_CKMETA 0x00010000 ---- ---- ---- ---1 ---- ---- ---- ----
O_CKDATA 0x00020000 ---- ---- ---- --1- ---- ---- ---- ----
O_REPAIRMETA* 0x00040000 ---- ---- ---- -1-- ---- ---- ---- ----
O_REPAIRDATA* 0x00080000 ---- ---- ---- 1--- ---- ---- ---- ----
o_WRSET 0x00000003 ---- ---- ---- ---- ---- ---- ---- --11
o_TYPE 0xf0000000 1111 ---- ---- ---- ---- ---- ---- ----
o_ZOMBIE 0x08000000 ---- 1--- ---- ---- ---- ---- ---- ----
o_UNCREAT 0x04000000 ---- -1-- ---- ---- ---- ---- ---- ----
o_UNSYNC 0x02000000 ---- --1- ---- ---- ---- ---- ---- ----
o_UNCRYST 0x01000000 ---- ---1 ---- ---- ---- ---- ---- ----
o_UNGRAFT 0x00800000 ---- ---- 1--- ---- ---- ---- ---- ----
o_UNFLUSH 0x00400000 ---- ---- -1-- ---- ---- ---- ---- ----
* Planned
? Hypothetical
T flags:
T_MODE 0x00000001 ---- ---- ---- ---- ---- ---- ---- ---1
T_RDONLY 0x00000000 ---- ---- ---- ---- ---- ---- ---- ----
T_RDWR 0x00000001 ---- ---- ---- ---- ---- ---- ---- ---1
T_MTREEONLY 0x00000002 ---- ---- ---- ---- ---- ---- ---- --1-
T_EXCL 0x00000008 ---- ---- ---- ---- ---- ---- ---- 1---
T_MKCONSISTENT 0x00000800 ---- ---- ---- ---- ---- 1--- ---- ----
T_RELOOKAHEAD 0x00001000 ---- ---- ---- ---- ---1 ---- ---- ----
T_REGBMAP 0x00002000 ---- ---- ---- ---- --1- ---- ---- ----
T_PREERASE* 0x00004000 ---- ---- ---- ---- -1-- ---- ---- ----
T_COMPACTMETA 0x00008000 ---- ---- ---- ---- 1--- ---- ---- ----
T_CKMETA 0x00010000 ---- ---- ---- ---1 ---- ---- ---- ----
T_CKDATA 0x00020000 ---- ---- ---- --1- ---- ---- ---- ----
T_REPAIRMETA* 0x00040000 ---- ---- ---- -1-- ---- ---- ---- ----
T_REPAIRDATA* 0x00080000 ---- ---- ---- 1--- ---- ---- ---- ----
t_EVICT* 0x00000010 ---- ---- ---- ---- ---- ---- ---1 ----
t_TYPE 0xf0000000 1111 ---- ---- ---- ---- ---- ---- ----
t_ZOMBIE 0x08000000 ---- 1--- ---- ---- ---- ---- ---- ----
t_CKPOINTED 0x04000000 ---- -1-- ---- ---- ---- ---- ---- ----
t_DIRTY 0x02000000 ---- --1- ---- ---- ---- ---- ---- ----
t_STALE 0x01000000 ---- ---1 ---- ---- ---- ---- ---- ----
t_BTYPE 0x00f00000 ---- ---- 1111 ---- ---- ---- ---- ----
* Planned
M/F flags:
M_MODE 0x00000001 ---- ---- ---- ---- ---- ---- ---- ---1
M_RDWR 0x00000000 ---- ---- ---- ---- ---- ---- ---- ----
M_RDONLY 0x00000001 ---- ---- ---- ---- ---- ---- ---- ---1
M_STRICT? 0x00000002 ---- ---- ---- ---- ---- ---- ---- --1-
M_FORCE? 0x00000004 ---- ---- ---- ---- ---- ---- ---- -1--
M_FORCEWITHRECKLESSABANDON?
0x00000008 ---- ---- ---- ---- ---- ---- ---- 1---
M_FLUSH 0x00000040 ---- ---- ---- ---- ---- ---- -1-- ----
M_SYNC 0x00000080 ---- ---- ---- ---- ---- ---- 1--- ----
M_DEDAG* 0x00000100 ---- ---- ---- ---- ---- ---1 ---- ----
M_DEDUP* 0x00000200 ---- ---- ---- ---- ---- --1- ---- ----
M_COMPR? 0x00000400 ---- ---- ---- ---- ---- -1-- ---- ----
M_REVDBG 0x00000010 ---- ---- ---- ---- ---- ---- ---1 ----
M_REVNOISE 0x00000020 ---- ---- ---- ---- ---- ---- --1- ----
M_CKPROGS 0x00100000 ---- ---- ---1 ---- ---- ---- ---- ----
M_CKFETCHES 0x00200000 ---- ---- --1- ---- ---- ---- ---- ----
M_CKMETAPARITY 0x00400000 ---- ---- -1-- ---- ---- ---- ---- ----
M_CKMETAREDUND* 0x00800000 ---- ---- 1--- ---- ---- ---- ---- ----
M_CKDATACKSUMS 0x01000000 ---- ---1 ---- ---- ---- ---- ---- ----
M_CKREADS* 0x01800000 ---- ---1 1--- ---- ---- ---- ---- ----
M_MKCONSISTENT 0x00000800 ---- ---- ---- ---- ---- 1--- ---- ----
M_RELOOKAHEAD 0x00001000 ---- ---- ---- ---- ---1 ---- ---- ----
M_REGBMAP 0x00002000 ---- ---- ---- ---- --1- ---- ---- ----
M_PREERASE* 0x00004000 ---- ---- ---- ---- -1-- ---- ---- ----
M_COMPACTMETA 0x00008000 ---- ---- ---- ---- 1--- ---- ---- ----
M_CKMETA 0x00010000 ---- ---- ---- ---1 ---- ---- ---- ----
M_CKDATA 0x00020000 ---- ---- ---- --1- ---- ---- ---- ----
M_REPAIRMETA* 0x00040000 ---- ---- ---- -1-- ---- ---- ---- ----
M_REPAIRDATA* 0x00080000 ---- ---- ---- 1--- ---- ---- ---- ----
F_CKFACTORY* 0x00000002 ---- ---- ---- ---- ---- ---- ---- --1-
F_GBMAP 0x02000000 ---- --1- ---- ---- ---- ---- ---- ----
F_GDDTREE* 0x04000000 ---- -1-- ---- ---- ---- ---- ---- ----
F_GPTREE* 0x08000000 ---- 1--- ---- ---- ---- ---- ---- ----
F_METAR1* 0x10000000 ---1 ---- ---- ---- ---- ---- ---- ----
F_METAR2* 0x20000000 --1- ---- ---- ---- ---- ---- ---- ----
F_METAR3* 0x30000000 --11 ---- ---- ---- ---- ---- ---- ----
F_DATAR1* 0x40000000 -1-- ---- ---- ---- ---- ---- ---- ----
F_DATAR2* 0x80000000 1--- ---- ---- ---- ---- ---- ---- ----
F_DATAR3* 0xc0000000 11-- ---- ---- ---- ---- ---- ---- ----
* Planned
? Hypothetical
It's a bit concerning that _all_ 32-bit mount flags end up used, but
what can you do...
Code changes minimal:
code stack ctx
before: 35964 2280 660
after: 35968 (+0.0%) 2280 (+0.0%) 660 (+0.0%)
code stack ctx
gbmap before: 38828 2296 772
gbmap after: 38828 (+0.0%) 2296 (+0.0%) 772 (+0.0%)
|
||
|
|
673fa7876f |
Reduced the scope of LFS3_REVDBG/REVNOISE
LFS3_REVDBG introduced a lot of overhead for something I'm not sure
anyone will actually use (I have enough tooling that the state of an
rbyd is rarely a mystery, see dbgbmap.py). That, and we're running out
of flags!
So this reduces LFS3_REVDBG to just store one of "himb" in the first
(lowest) byte of the revision count; information that is easily
available:
vvvv---- -------- -------- --------
vvvvrrrr rrrrrr-- -------- --------
vvvvrrrr rrrrrrnn nnnnnnnn nnnnnnnn
vvvvrrrr rrrrrrnn nnnnnnnn dddddddd
'-.''----.----''----.- - - '---.--'
'------|----------|----------|---- 4-bit relocation revision
'----------|----------|---- recycle-bits recycle counter
'----------|---- pseudorandom noise (if revnoise)
'---- h, i, m, or b (if revdbg)
-11-1--- - h = mroot anchor
-11-1--1 - i = mroot
-11-11-1 - m = mdir
-11---1- - b = btree node
Some other notes:
- Enabled LFS3_REVDBG and LFS3_REVNOISE to work together, now that
LFS3_REVDBG doesn't consume all unused rev bits.
Note that LFS3_REVDBG has priority over LFS3_REVNOISE, but _not_
recycle-bits, etc. Otherwise problems would happen for recycle-bits
>2^20 (though do we care?).
- Fixed an issue where using the gcksum as a noise source results in
noise=0 when there is only an mroot. This is due to how we xor out
the current mdir cksum during an mdir commit.
Fixed by using gcksum_p instead of gcksum.
- Added missing LFS3_I_REVDBG/REVNOISE flags in the tests, so now you
can actually run the tests with LFS3_REVDBG/REVNOISE (this probably
just fell out-of-date at some point).
---
Curiously, despite LFS3_REVDBG/REVNOISE being disabled by default, this
did save some code. I'm guessing the non-tail-call mtree/gbmap commit
functions prevented some level of inlining?:
code stack ctx
before: 35964 2280 660
after: 35964 (+0.0%) 2280 (+0.0%) 660 (+0.0%)
code stack ctx
gbmap before: 38940 2296 772
gbmap after: 38828 (-0.3%) 2296 (+0.0%) 772 (+0.0%)
|
||
|
|
4010afeafd |
trv: Reintroduced LFS3_T_EXCL
With the relaxation of traversal behavior under mutation, I think it
makes sense to bring back LFS3_T_EXCL. If only to allow traversals to
gaurantee termination under mutation. Now that traversals no longer
guarantee forward progress, it's possible to get stuck looping
indefinitely if the filesystem is constantly being mutated.
Non-excl traversals are probably still useful for GC work and debugging
threads, but LFS3_T_EXCL now allows traversals to terminate immediately
with LFS3_ERR_BUSY at the first sign of unrelated filesystem mutation:
LFS3_T_EXCL 0x00000008 Error if filesystem modified
Internally, we already track unrelated mutation to avoid corrupt state
(LFS3_t_DIRTY), so this is a very low-cost feature:
code stack ctx
before: 35944 2280 660
after: 35964 (+0.1%) 2280 (+0.0%) 660 (+0.0%)
code stack ctx
gbmap before: 38916 2296 772
gbmap after: 38940 (+0.1%) 2296 (+0.0%) 772 (+0.0%)
code stack ctx
gc before: 36016 2280 768
gc after: 36036 (+0.1%) 2280 (+0.0%) 768 (+0.0%)
|
||
|
|
14c369af93 |
trv: Adopted LFS3_t_STALE for marking block queue as stale
This solves the previous gc-needs-block-queue-so-we-can-clobber-block-
queue issue by adding an additional LFS3_t_STALE flag to indicate when
any block queues would be invalid.
So instead of clearing block queues in lfs3_alloc_ckpoint, we just set
LFS3_t_STALE, and any lfs3_trv_ts can clear their block queues in
lfs3_trv_read. This allows lfs3_mgc_ts to be allocated without a block
queue when doing any LFS3_M_*/LFS3_F_*/LFS3_GC_* work.
LFS3_t_STALE is set at the same time as LFS3_t_CKPOINT and LFS3_t_DIRTY,
but we need a separate bit so lfs3_trv_read can clear the flag after
flushing without losing ckpoint/dirty information.
---
Unfortunately, none of the stack-allocated lfs3_mgc_ts are on the stack
hot-path, so we don't immediate savings. But note the 2-words saved in
ctx when compiling in LFS3_GC mode:
code stack ctx
before: 35940 2280 660
after: 35944 (+0.0%) 2280 (+0.0%) 660 (+0.0%)
code stack ctx
gbmap before: 38916 2296 772
gbmap after: 38916 (+0.0%) 2296 (+0.0%) 772 (+0.0%)
code stack ctx
gc before: 36012 2280 776
gc after: 36016 (+0.0%) 2280 (+0.0%) 768 (-1.0%)
|
||
|
|
d1d69c0a52 |
trv: Greatly simplified filesystem traversal
The main idea here is to drop the flag-encoded tstate state machine, and
replace it with a matrix controlled by special mid + bid values:
-- mid ->
-5 -4 -3 -2 >=-1
bid -2 x x x --> mdir
v >=-1 x gbm gbm x --> bshrub/btree
'----|----|----|----|----> mroot anchor
'----|----|----|----> mroot chain + mtree
'----|----|----> gbmap (in-ram gbmap)
'----|----> gbmap_p (on-disk gbmap)
'----> file bshrubs/btrees
This was motivated by the observation that everything in our filesystem
can be modeled as mdir + bshrub/btree tuples, as long as some states are
noops. And we can cleanly encode these tuples in the unused negative
mid + bid ranges without needing an explicit state machine.
Well, that and the previous tstate state machine approach being an ugly
pile of switch cases and messy logic.
Note though that some mids may need to traverse multiple mdirs/bshrub/
btrees:
- The mroot chain + mtree (mid=-4) needs to traverse all mroots in the
mroot chain, and detect any cycles.
- File mdirs (mid>=-1) need to traverse both the on-disk bshrub/btree
and any opened file handles' bshrubs/btrees before moving onto the
next mid.
This grows O(n^2) because all file handles are in one big unsorted
linked-list, but as usual we don't care.
In addition to the greatly simplified traversal logic, the new state
matrix simplifies traversal clobbering: Setting bid=-2 always forces a
bshrub/btree refetch.
This comes at the cost of traversal _precision_, i.e. we can now revisit
previously visited bshrub/btree nodes. But I think this is well worth it
for more robust traversal clobbering. Traversal clobbering is delicate
and difficult to get right.
Besides, we can already revisit blocks due to CoW references, so what's
the harm in revisiting blocks when under mutation?
---
The simpler traversal logic leads to a nice amount of code savings
across the board:
code stack ctx
before: 36476 2304 660
after: 35940 (-1.5%) 2280 (-1.0%) 660 (+0.0%)
code stack ctx
gbmap before: 39524 2320 772
gbmap after: 38916 (-1.5%) 2296 (-1.0%) 772 (+0.0%)
code stack ctx
gc before: 36548 2304 804
gc after: 36012 (-1.5%) 2280 (-1.0%) 776 (-3.5%)
Note the ctx savings in LFS3_GC mode. Most of the stack/ctx savings
comes from the smaller lfs3_mtrv_t struct, which no longer needs to
stage bshrubs (we no longer care about bshrubs across mdir commit as a
part of the above clobbering simplifications):
before after
lfs3_mtrv_t: 128 100 (-21.9%)
lfs3_mgc_t: 128 100 (-21.9%)
lfs3_trv_t: 136 108 (-20.6%)
Unfortunately, the simpler clobbering means now any gc work needs the
block queue (i.e. lfs3_trv_t), solely so clobbering the block queue
doesn't clobber unallocated memory. Not great but hopefully fixable.
---
Some other notes:
- As a part of simplifying traversal clobbering, everything is triggered
by lfs3_alloc_ckpoint (via lfs3_trv_ckpoint_).
This may clobber traversals more than is strictly necessary, but
that's kinda the idea. Better safe than sorry.
And no more need to explicit lfs3_handle_clobber calls is nice.
- Opened file handle iteration is now tracked by the traversal handle's
position in the handle linked-list, instead of a separate handle
pointer. This means one less thing to disentangle and makes traversals
no longer a special case for things like lfs3_handle_close.
You may think this bumps traversals up to O(n^3) in-ram, but because
we only ever visit each unique handle + mid once, we can keep the
total O(n^2) if we're smart about linked-list updates!
- lfs3_mdir_commit needed to be tweaked to accept mids<=-1, instead of
just mid=-1 for the mroot. Unfortunately I don't know how much this
costs on its own.
- The reorganization of lfs3_mtrv_t means lfs3_mtortoise_t gets its own
struct again!
- No more tstate state machine also frees up a big chunk of the
traversal flag space, which was getting pretty cramped.
|
||
|
|
ee519f43b5 |
scripts: Renamed lookupleaf -> lookupnext_ to match lfs3.c
- lookupleaf -> lookupnext_ - namelookupleaf -> namelookup_ I want to move away from lookupleaf usage in general in the dbg scripts, like we have in lfs3.c, but I also just really don't want to touch these scripts again unless I need to. They've been useful, but also a big time sink. Maybe I should actually learn Python's new type system. That would probably help here... |
||
|
|
4dced81abc |
scripts: dbgflags.py: Better indented *COMPAT flags
Just to avoid the awkward escaped newlines when possible. Note this has no effect on the output of dbgflags.py. |
||
|
|
b49d9e9ece |
Renamed REPOP* -> RE*
So: - cfg.gc_repoplookahead_thresh -> cfg.gc_relookahead_thresh - cfg.gc_repopgbmap_thresh -> cfg.gc_regbmap_thresh - cfg.gbmap_repop_thresh -> cfg.gbmap_re_thresh - LFS3_*_REPOPLOOKAHEAD -> LFS3_*_RELOOKAHEAD - LFS3_*_REPOPGBMAP -> LFS3_*_REGBMAP Mainly trying to reduce the mouthful that is REPOPLOOKAHEAD and REPOPGBMAP. As a plus this also avoids potential confusion of "repop" as a push/pop related operation. |
||
|
|
ffc40da878 |
scripts: Reworked tagrepr -> Tag.repr to rely more on self-parsing
This should make tag editing less tedious/error-prone. We already used
self-parsing to generate -l/--list in dbgtag.py, but this extends the
idea to tagrepr (now Tag.repr), which is used in quite a few more
scripts.
To make this work the little tag encoding spec had to become a bit more
rigorous, fortunately the only real change was the addition of '+'
characters to mark reserved-but-expected-zero bits.
Example:
TAG_CKSUM = 0x3000 ## v-11 ---- ++++ +pqq
^--^----^----^--^-^-- valid bit, unmatched
'----|----|--|-|-- matches 1
'----|--|-|-- matches 0
'--|-|-- reserved 0, unmatched
'-|-- perturb bit, unmatched
'-- phase bits, unmatched
dbgtag.py 0x3000 => cksumq0
dbgtag.py 0x3007 => cksumq3p
dbgtag.py 0x3017 => cksumq3p 0x10
dbgtag.py 0x3417 => 0x3417
Though Tag.repr still does a bit of manual formatting for the
differences between shrub/normal/null/alt tags.
Still, this should reduce the number of things that need to be changed
from 2 -> 1 when adding/editing most new tags.
|
||
|
|
3f15b61c72 |
scripts: dbgflags.py: Added LFS3_SEEK_* flags for completeness
This required a bit of a hack: LFS3_seek_MODE, which is marked internal to try to minimize confusion, but really doesn't exist in the code at all. But a hack is probably good enough for now. |
||
|
|
0c0643d5d7 |
scripts: Adopted self-parsing script for dgbflags/err.py encoding
This has just proven much easier to tweak in dbgtag.py, so adopting the same self-parsing pattern in dbgflags.py/dbgerr.py. This makes editing easier by (1) not needing to worry about parens/quotes/commas, and (2) allowing for non-python expressions, such as the mode flags in dbgflags.py. The only concern is script startup may be slightly slower, but we really don't care. |
||
|
|
8a58954828 |
trv: Reduced LFS3_t_CKPOINTED + LFS3_t_MUTATED -> LFS3_t_CKPOINTED
This drops LFS3_t_MUTATED in favor of just using LFS3_t_CKPOINTED
everywhere:
1. These meant roughly the same thing, with LFS3_t_MUTATED being a bit
tighter at the cost of needing to be explicitly set.
2. The implicit setting of LFS3_t_CKPOINTED by lfs3_alloc_ckpoint -- a
function that already needs to be called before mutation -- means we
have one less thing to worry about.
Implicit properties like LFS3_t_CKPOINTED are great for building a
reliable system. Manual flags like LFS3_t_MUTATED, not so much.
3. Why use two flags when we can get away with one?
The only downside is we may unnecessarily clobber gc/traversal work when
we don't actually mutate the filesystem. Failed file open calls are a
good example.
However this tradeoff seems well worth it for an overall simpler +
more reliable system.
---
Saves a bit of code:
code stack ctx
before: 37220 2352 688
after: 37160 (-0.2%) 2352 (+0.0%) 688 (+0.0%)
code stack ctx
gbmap before: 40184 2368 856
gbmap after: 40132 (-0.1%) 2368 (+0.0%) 856 (+0.0%)
|