Commit Graph

20 Commits

Author SHA1 Message Date
Christopher Haster
5e817be9cc scripts: maps: Cleaned up comments and junk
This took a bit of a messy route, but these scripts should be good to go
now.
2025-04-16 15:22:54 -05:00
Christopher Haster
50f652d44f scripts: maps: Cleaned up/moved header generation before rendering
Should've probably been two commits, but:

1. Cleaned up tracebd.py's header generation to be consistent with
   dbgbmap.py and other scripts.

   Percentage fields are now consistently floats in all scripts,
   allowing user-specified precision when punescaping.

2. Moved header generation up to where we still have the disk open (in
   dbgbmap[d3].py), to avoid issues with lazy Lfs attrs trying to access
   the disk after it's been closed.

   Found while testing with --title='cksum %(cksum)08x'. Lfs tries to
   validate the gcksum last minute and things break.
2025-04-16 15:22:53 -05:00
Christopher Haster
f0b8d34230 scripts: maps: Fixed divide-by-zero when packing blocks into small maps
This can be hit when dealing with very small maps, which is common since
we're rendering to the terminal. Not crashing here at least allows the
header/usage string to be shown.
2025-04-16 15:22:52 -05:00
Christopher Haster
61ce23ce7e scripts: maps: Fixed some aspect ratio issues, limited scope
Replacing -R/--aspect-ratio, --to-ratio now calculates the width/height
_before_ adding decoration such as headers, stack info, etc.

I toying around with generalizing -R/--aspect-ratio to include
decorations, but when Wolfram Alpha spit this mess for the post-header
formula:

      header*r - sqrt(4*v*r + padding^2*r)
  w = ------------------------------------
                        2

I decided maybe a generalized -R/--aspect-ratio is a _bit_ too
complicated for what are supposed to be small standalone Python
scripts...

---

Also fixed the scaling formula, which should've taken the sqrt _after_
multiplying by the aspect ratio:

  w = sqrt(v*r)

I only noticed while trying to solve for the more complicated
post-decoration formula, the difference is pretty minor.
2025-04-16 15:22:48 -05:00
Christopher Haster
8e3760c5b8 scripts: Tweaked punescape to expect dict-like attrs
This simplifies attrs a bit, and scripts can always override
__getitem__ if they want to provide lazy attr generation.

The original intention of accepting functions was to make lazy attr
generation easier, but while tinkering around with the idea I realized
the actual attr mapping/generation would be complicated enough that
you'd probably want a full class anyways.

All of our scripts are only using dict attrs anyways. And lazy attr
generation is probably a premature optimization for the same reason
everyone's ok with Python's slices being O(n).
2025-04-16 15:22:45 -05:00
Christopher Haster
06bb34fd99 scripts: Adopted Attr class changes in all scripts
Mainly the addition of Attr.getall, Attr.get, and changing
Attr.__getitem__ to raise KeyError (just like a normal dict).
2025-04-16 15:22:44 -05:00
Christopher Haster
edc6c7ec99 scripts: dbgbmap[d3].py: Reverted percentages to entire bmap
So percentages now include unused blocks, instead of being derived from
only blocks in use.

This is a bit inconsistent with tracebd.py, where we show ops as
percentages of all ops, but it's more useful:

- mdir+btree+data gives you the total usage, which is useful if you want
  to know how full disk is. You can't get this info from in-use
  percentages.

  Note that total field is sticking around, so you can show the total
  usage directly if you provide your own title string:

    $ ./scripts/dbgbmap.py disk \
        --title="bd %(block_size)sx%(block_count)s, %(total_percent)s"

- You can derive the in-use percentages from total percentages if you
  need them: in-use-mdir = mdir/(mdir+btree+data).

  Maybe this should be added to the --title fields, but I can't think of
  a good name at the moment...

Attempting to make tracebd.py consistent with dbgbmap.py doesn't really
make sense either: Showing op percentages of total bmap will usually be
an extremely small number.

At least dbgbmap.py is consistent with tracebd.py's --wear percentage,
which is out of all erase state in the bmap.
2025-04-16 15:22:40 -05:00
Christopher Haster
465fdd1fca scripts: dbgbmap.py tweaks
- Create a grid with dashes even in -%/--usage mode.

  This was surprisingly annoying since it breaks the existing
  1 block = 1 char assumption.

- Derive percentages from in-use blocks, not all blocks. This matches
  behavior of tracebd.py's percentages (% read/prog/erase).

  Though not tracebd.py's percent wear...

- Added mdir/btree/data counts/percentages to dbgbmapd3.py, for use in
  custom --title strings and the newly added --title-usage.

  Because why not. Unlike dbgbmap.py, performance is not a concern at
  all, and the consistency between these two scripts helps
  maintainability.

  Case in point: also fixed a typo from copying the block_count
  inference between scripts.
2025-04-16 15:22:39 -05:00
Christopher Haster
d5c0e142f0 scripts: Reworked tracebd.py, needs cleanup
It's a mess but it's working. Still a number of TODOs to cleanup...

This adopts all of the changes in dbgbmap.py/dbgbmapd3.py, block
grouping, nested curves, Canvas, Attrs, etc:

- Like dbgbmap.py, we now group by block first before applying space
  filling curves, using nested space filling curves to render byte-level
  operations.

  Python's ft.lru_cache really shines here.

  The previous behavior is still available via -u/--contiguous

- Adopted most features in dbgbmap.py, so --to-scale, -t/--tiny, custom
  --title strings, etc.

- Adopted Attrs so now chars/coloring can be customized with
  -./--add-char, -,/--add-wear-char, -C/--add-color,
  -G/--add-wear-color.

- Renamed -R/--reset -> --volatile, which is a much better name.

- Wear is now colored cyan -> white -> read, which is a bit more
  visually interesting. And we're not using cyan in any scripts yet.

In addition to the new stuff, there were a few simplifications:

- We no longer support sub-char -n/--lines with -:/--dots or
  -⣿/--braille. Too complicated, required Canvas state hacks to get
  working, and wasn't super useful.

  We probably want to avoid doing too much cleverness with -:/--dots and
  -⣿/--braille since we can't color sub-chars.

- Dropped -@/--blocks byte-level range stuff. This was just not worth
  the amount of complexity it added. -@/--blocks is now limited to
  simple block ranges. High-level scripts should stick to high-level
  options.

- No fancy/complicated Bmap class. The bmap object is just a dict of
  TraceBlocks which contain RangeSets for relevant operations.

  Actually the new RangeSet class deserves a mention but this commit
  message is probably already too long.

  RangeSet is a decently efficient set of, well, ranges, that can be
  merged and queried. In a lower-level language it should be implemented
  as a binary tree, but in Python we're just using a sorted list because
  we're probably not going to be able to beat O(n) list operations.

- Wear is tracked at the block level, no reason to overcomplicate this.

- We no longer resize based on new info. Instead we either expect a
  -b/--block-size argument or wait until first bd init call.

  We can probably drop the block size in BD_TRACE statements now, but
  that's a TODO item.

- Instead of one amalgamated regex, we use string searches to figure out
  the bd op and then smaller regexes to parse. Lesson learned here:
  Python's string search is very fast (compared to regex).

- We do _not_ support labels on blocks like we do in treemap.py/
  codemap.py. It's less useful here and would just be more hassle.

I also tried to reorganize main a bit to mirror the simple two-main
approach in dbgbmap.py and other ascii-rendering scripts, but it's a bit
difficult here since trace info is very stateful. Building up main
functions in the main main function seemed to work well enough:

  main -+-> main_ -> trace__ (main thread)
        '-> draw_ -> draw__ (daemon thread)

---

You may note some weirdness going on with flags. That's me trying to
avoid upcoming flag conflicts.

I think we want -n/--lines in more scripts, now that it's relatively
self-contained, but this conflicts with -n/--namespace-depth in
codemap[d3].py, and risks conflict with -N/--notes in csv.py which may
end up with namespace-related functionality in the future.

I ended up hijacking -_, but this conflicted with -_/--add-line-char in
plot.py, but that's ok because we also want a common "secondary char"
flag for wear in tracebd.py... Long story short I ended up moving a
bunch of flags around:

- added                   -n/--lines
- -n/--namespace-depth -> -_/--namespace-depth
- -N/--notes           -> -N/--notes
- -./--add-char        -> -./--add-char
- -_/--add-line-char   -> -,/--add-line-char
- added                   -,/--add-wear-char
- -C/--color           -> -C/--add-color
- added                -> -G/--add-wear-color

Worth it? Dunno.
2025-04-16 15:22:38 -05:00
Christopher Haster
3ff25a4fdf scripts: dbgbmap[d3].py: Disabled gcksum checking by default
By default, we don't actually do anything if we find an invalid gcksum,
so there's no reason to calculate it everytime.

Though this performance improvement may not be very noticeable:

  dbgbmap.py w/  crc32c lib w/  no_ck --no-ckdata: 0m0.221s
  dbgbmap.py w/  crc32c lib w/o no_ck --no-ckdata: 0m0.269s
  dbgbmap.py w/o crc32c lib w/  no_ck --no-ckdata: 0m0.388s
  dbgbmap.py w/o crc32c lib w/o no_ck --no-ckdata: 0m0.490s
  dbgbmap.old.py:                                  0m0.231s

Note that there's no point in adopting this in dbgbmapd3.py: 1. svg
rendering dominates (probably, I haven't measured this), and 2. we
default to showing the littlefs mount string instead of mdir/btree/data
percentages.
2025-04-16 15:22:36 -05:00
Christopher Haster
3820be180d scripts: Adopted crc32c lib when available
Jumping from a simple Python implementation to the fully hardware
accelerated crc32c library basically deletes any crc32c related
bottlenecks:

  crc32c.py disk (1MiB) w/  crc32c lib: 0m0.027s
  crc32c.py disk (1MiB) w/o crc32c lib: 0m0.844s

This uses the same try-import trick we use for inotify_simple, so we get
the speed improvement without losing portability.

---

In dbgbmap.py:

  dbgbmap.py w/  crc32c lib:             0m0.273s
  dbgbmap.py w/o crc32c lib:             0m0.697s
  dbgbmap.py w/  crc32c lib --no-ckdata: 0m0.269s
  dbgbmap.py w/o crc32c lib --no-ckdata: 0m0.490s
  dbgbmap.old.py:                        0m0.231s

The bulk of the runtime is still in Rbyd.fetch, but this is now
dominated by leb128 decoding, which makes sense. We do ~twice as many
fetches in the new dbgbmap.py in order to calculate the gcksum (which
we then ignore...).
2025-04-16 15:22:34 -05:00
Christopher Haster
b5242d02ac scripts: dbgbmap[d3].py: Moved rbyd/bptr checks behind --no-ckmeta/ckdata
Checking every data block for errors really slows down dbgbmap.py, which
is unfortunate for realtime rendering.

To be fair, the real issue is our naive crc32c impl, but the mindset of
these scripts is if you want speed you really shouldn't be using Python
and should rewrite the script in Rust/C/something (see prettyasserts for
example). You _could_ speed things up with a table-based crc32c, but at
that point you should probably just find C-bindings for crc32c (maybe
optional like inotify?... actually that's not a bad idea...).

At least --no-ckmeta/--no-ckdata allow for the previous behavior of not
checking for relevant errors for a bit of speed.

---

Note that --no-ckmeta currently doesn't really do anything. I toyed with
adding a non-fetching Rbyd.fetchtrunk method, but this seems out of
scope for these scripts.
2025-04-16 15:22:34 -05:00
Christopher Haster
6ea18e6579 scripts: Tweaked bd.read to behave like an actual bd_read callback
This better matches what you would expect from a function called
bd.read, at least in the context of littlefs, while also decreasing the
state (seek) we have to worry about.

Note that bd.readblock already behaved mostly like this, and is
preferred by every class except for Bptr.
2025-04-16 15:22:32 -05:00
Christopher Haster
b2911fbbe7 scripts: Removed item/iter magic methods from fs object classes
So no more __getitem__, __contains__, or __iter__ for Rbyd, Btree, Mdir,
Mtree, Lfs.File, etc.

These were way too error-prone, especially when accidental unpacking
triggered unintended disk traversal and weird error states. We didn't
even use the implicit behavior because we preferred the full name for
heavy disk operations.

The motivation for this was Python not catching this bug, which is a bit
silly:

  rid, rattr, *path_ = rbyd
2025-04-16 15:22:28 -05:00
Christopher Haster
33120bf930 scripts: Reworked dbgbmap.py
This is a rework of dbgbmap.py to match dbgbmapd3.py, adopt the new
Rbyd/Lfs class abstractions, as well as Canvas, -k/--keep-open, etc.

Some of the main changes:

- dbgbmap.py now reports corrupt/conflict blocks, which can be useful
  for debugging.

  Note though that you will probably get false positives if running with
  -k/--keep-open while something is writing to the disk. littlefs is
  powerloss safe, not multi-write safe! Very different problem!

- dbgbmap.py now groups by blocks before mapping to the space filling
  curve. This matches dbgbmapd3.py and I think is more intuitive now
  that we have a bmap tiling algorithm.

  -%/--usage still works, but is rendered as a second space filling
  curve _inside_ the block tile. Different blocks can end up with
  slightly different sizes due to rounding, but it's not the end of the
  world.

  I wasn't originally going to keep it around, but ended up caving, so
  you can still get the original byte-level curve via -u/--contiguous.

- Like the other ascii rendering script, dbgbmap.py now supports
  -k/--keep-open and friends as a thin main wrapper. This just makes it
  a bit easier to watch a realtime bmap without needing to use watch.py.

- --mtree-only is supported, but filtering via --mdirs/--btrees/--data
  is _not_ supported. This was too much complexity for a minor feature,
  and doesn't cover other niche blocks like corrupted/conflict or parity
  in the future.

- Things are more customizable thanks to the Attr class. For an example
  you can now use the littlefs mount string as the title via
  --title-littlefs.

- Support for --to-scale and -t/--tiny mode, if you want to scale based
  on block_size.

One of the bigger differences dbgbmapd3.py -> dbgbmap.py is that
dbgbmap.py still supports -%/--usage. Should we backport -%/--usage to
dbgbmapd3.py? Uhhhh...

This ends up a funny example of raster graphics vs vector graphics. A
pixel-level space filling curve is easy with raster graphics, but with
an svg you'd need some sort of pixel -> path wrapping algorithm...

So no -%/--usage in dbgbmapd3.py for now.

Also just ripped out all of the -@/--blocks byte-level range stuff. Way
too complicated for what it was worth. -@/--blocks is limited to simple
block ranges now. High-level scripts should stick to high-level options.

One last thing to note is the adoption of "if '%' in label__" checks
before applying punescape. I wasn't sure if we should support punescape
in dbgbmap.py, since it's quite a bit less useful here, and may be
costly due to the lazy attr generation. Adding this simple check avoids
the cost and consistency question, so I adopted it in all scripts.
2025-04-16 15:22:24 -05:00
Christopher Haster
202636cccd scripts: Tweaked corrupt rbyd coloring to include addresses
This matches the coloring in dbglfs.py for other erroneous conditions,
and also matches how we color hidden items when shown.

Also fixed some minor bugs in grm printing.
2025-04-16 15:22:23 -05:00
Christopher Haster
5682fd6163 scripts: dbglfs.py: Added --ckmeta/--ckdata
For more aggressive checking of filesystem state. These should match the
behavior of LFS_M_CKMETA/CKDATA in lfs.c.

Also tweaked dbgbmapd3.py (and eventually dbgmap.py) to match, though we
don't need new flags there since we're already checking every block in
the filesystem.
2025-04-16 15:22:21 -05:00
Christopher Haster
cbd3fed8b8 scripts: *d3.py: Tried to make highlighted graphs more visible
These were hard to read, especially in light mode (which I use the
least). They're still hard to read, but hopefully a bit less so:

- Decreased opacity of unfocused tiles 0.7 -> 0.5
- Don't unfocus unused blocks in dbgbmapd3.py
- Softened arrow color in light mode #000000 -> #555555
2025-04-16 15:22:20 -05:00
Christopher Haster
97e2786545 scripts: Synced dbgbmapd3.py Lfs class changes
- Added Lfs.traverse for full filesystem traversal
- Added Rbyd.shrub flag so we can tell if an Rbyd is a shrub
- Removed redundant leaves from paths in leaf iters
2025-04-16 15:22:19 -05:00
Christopher Haster
5f06558cbe scripts: Added dbgbmapd3.py for bmap -> svg rendering
Like codemapd3.py this include an interactive UI for viewing the
underlying filesystem graph, including:

- mode-tree - Shows all reachable blocks from a given block
- mode-branches - Shows immediate children of a given block
- mode-references - Shows parents of a given block
- mode-redund - Shows sibling blocks in redund groups (This is
  currently just mdir pairs, but the plan is to add more)

This is _not_ a full filesystem explorer, so we don't embed all block
data/metadata in the svg. That's probably a project for another time.
However we do include interesting bits such as trunk addresses,
checksums, etc.

An example:

  # create an filesystem image
  $ make test-runner -j
  $ ./scripts/test.py -B test_files_many -a -ddisk -O- \
          -DBLOCK_SIZE=1024 \
          -DCHUNK=10 \
          -DSIZE=2050 \
          -DN=128 \
          -DBLOCK_RECYCLES=1
  ... snip ...
  done: 2/2 passed, 0/2 failed, 164pls!, in 0.16s

  # generate bmap svg
  $ ./scripts/dbgbmapd3.py disk -b1024 -otest.svg \
          -W1400 -H750 -Z --dark
  updated test.svg, littlefs v0.0 1024x1024 0x{26e,26f}.d8 w64.128, cksu
  m 41ea791e

And open test.svg in a browser of your choice.

Here's what the current colors mean:

- yellow => mdirs
- blue   => btree nodes
- green  => data blocks
- red    => corrupt/conflict issue
- gray   => unused blocks

But like codemapd3.py the output is decently customizable. See -h/--help
for more info.

And, just like codemapd3.py, this is based on ideas from d3 and
brendangregg's flamegraphs:

- d3 - https://d3js.org
- brendangregg's flamegraphs - https://github.com/brendangregg/FlameGraph

Note we don't actually use d3... the name might be a bit confusing...

---

One interesting change from the previous dbgbmap.py is the addition of
"corrupt" (bad checksum) and "conflict" (multiple parents) blocks, which
can help find bugs.

You may find the "conflict" block reporting a bit strange. Yes it's
useful for finding block allocation failures, but won't naturally formed
dags in file btrees also be reported as "conflicts"?

Yes, but the long-term plan is to move away from dags and make littlefs
a pure tree (for block allocator and error correction reasons). This
hasn't been implemented yet, so for now dags will result in false
positives.

---

Implementation wise, this script was pretty straightforward given prior
dbglfs.py and codemapd3.py work.

However there was an interesting case of https://xkcd.com/1425:

- Traverse the filesystem and build a graph - easy
- Tile a rectangle with n nice looking rectangles - uhhh

I toyed around with an analytical approach (something like block width =
sqrt(canvas_width*canvas_height/n) * block_aspect_ratio), but ended up
settling on an algorithm that divides the number of columns by 2 until
we hit our target aspect ratio.

This algorithm seems to work quite well, runs in only O(log n), and
perfectly tiles the grid for powers-of-two. Honestly the result is
better than I was expecting.
2025-04-16 15:22:17 -05:00