Added dbgbmap.py, tweaked tracebd.py to match

dbgbmap.py parses littlefs's mtree/btrees and displays that status of
every block in use:

  $ ./scripts/dbgbmap.py disk -B4096x256 -Z -H8 -W64
  bd 4096x256,   7.8% mdir,  10.2% btree,  78.1% data
  mmddbbddddddmmddddmmdd--bbbbddddddddddddddbbdddd--ddddddmmdddddd
  mmddddbbddbbddddddddddddddddbbddddbbddddddmmddbbdddddddddddddddd
  bbdddddddddddd--ddddddddddddddddbbddddmmmmddddddddddddmmmmdddddd
  ddddddddddbbdddddddddd--ddddddddddddddmmddddddddddddddddddddmmdd
  ddddddbbddddddddbb--ddddddddddddddddddddbb--mmmmddbbdddddddddddd
  ddddddddddddddddddddbbddbbdddddddddddddddddddddddddddddddddddddd
  dddddddddd--ddddbbddddddddmmbbdd--ddddddddddddddbbmmddddbbdddddd
  ddmmddddddddddmmddddddddmmddddbbbbdddddddd--ddbbddddddmmdd--ddbb

  (ok, it looks a bit better with colors)

dbgbmap.py matches the layout and has the same options as tracebd.py,
allowing the combination of both to provide valuable insight into what
exactly littlefs is doing.

This required a bit of tweaking of tracebd.py to get right, mostly
around conflicting order-based arguments. This also reworks the internal
Bmap class to be more resilient to out-of-window ops, and adds an
optional informative header.
This commit is contained in:
Christopher Haster
2023-10-30 15:15:58 -05:00
parent 3e46139a45
commit 4ecf4cc654
6 changed files with 2098 additions and 180 deletions
+1835
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -119,6 +119,9 @@ def crc32c(data, crc=0):
crc = (crc >> 1) ^ ((crc & 1) * 0x82f63b78) crc = (crc >> 1) ^ ((crc & 1) * 0x82f63b78)
return 0xffffffff ^ crc return 0xffffffff ^ crc
def popc(x):
return bin(x).count('1')
def fromle32(data): def fromle32(data):
return struct.unpack('<I', data[0:4].ljust(4, b'\0'))[0] return struct.unpack('<I', data[0:4].ljust(4, b'\0'))[0]
@@ -145,9 +148,6 @@ def frombranch(data):
cksum = fromle32(data[d:]); d += 4 cksum = fromle32(data[d:]); d += 4
return block, trunk, cksum return block, trunk, cksum
def popc(x):
return bin(x).count('1')
def xxd(data, width=16): def xxd(data, width=16):
for i in range(0, len(data), width): for i in range(0, len(data), width):
yield '%-*s %-*s' % ( yield '%-*s %-*s' % (
@@ -994,8 +994,8 @@ def main(disk, roots=None, *,
# show the branch # show the branch
dbg_branch(bid, w, rbyd, rid, tags, len(path)-1) dbg_branch(bid, w, rbyd, rid, tags, len(path)-1)
if args.get('error_on_corrupt') and corrupted: if args.get('error_on_corrupt') and corrupted:
sys.exit(2) sys.exit(2)
if __name__ == "__main__": if __name__ == "__main__":
+24 -29
View File
@@ -120,6 +120,9 @@ def crc32c(data, crc=0):
crc = (crc >> 1) ^ ((crc & 1) * 0x82f63b78) crc = (crc >> 1) ^ ((crc & 1) * 0x82f63b78)
return 0xffffffff ^ crc return 0xffffffff ^ crc
def popc(x):
return bin(x).count('1')
def fromle32(data): def fromle32(data):
return struct.unpack('<I', data[0:4].ljust(4, b'\0'))[0] return struct.unpack('<I', data[0:4].ljust(4, b'\0'))[0]
@@ -148,6 +151,12 @@ def frommdir(data):
d += d_ d += d_
return blocks return blocks
def fromshrub(data):
d = 0
weight, d_ = fromleb128(data[d:]); d += d_
trunk, d_ = fromleb128(data[d:]); d += d_
return weight, trunk
def frombranch(data): def frombranch(data):
d = 0 d = 0
block, d_ = fromleb128(data[d:]); d += d_ block, d_ = fromleb128(data[d:]); d += d_
@@ -161,8 +170,12 @@ def frombtree(data):
block, trunk, cksum = frombranch(data[d:]) block, trunk, cksum = frombranch(data[d:])
return w, block, trunk, cksum return w, block, trunk, cksum
def popc(x): def frombptr(data):
return bin(x).count('1') d = 0
size, d_ = fromleb128(data[d:]); d += d_
block, d_ = fromleb128(data[d:]); d += d_
off, d_ = fromleb128(data[d:]); d += d_
return size, block, off
def xxd(data, width=16): def xxd(data, width=16):
for i in range(0, len(data), width): for i in range(0, len(data), width):
@@ -1263,28 +1276,19 @@ def frepr(mdir, rid, tag):
# shrub? # shrub?
done, rid_, tag_, w_, j, d, data, _ = mdir.lookup(rid, TAG_TRUNK) done, rid_, tag_, w_, j, d, data, _ = mdir.lookup(rid, TAG_TRUNK)
if not done and rid_ == rid and tag_ == TAG_TRUNK: if not done and rid_ == rid and tag_ == TAG_TRUNK:
d = 0 weight, trunk = fromshrub(data)
weight, d_ = fromleb128(data[d:]); d += d_
trunk, d_ = fromleb128(data[d:]); d += d_
size = max(size, weight) size = max(size, weight)
structs.append('trunk 0x%x.%x %d' % (mdir.block, trunk, weight)) structs.append('trunk 0x%x.%x %d' % (mdir.block, trunk, weight))
# direct block? # direct block?
done, rid_, tag_, w_, j, d, data, _ = mdir.lookup(rid, TAG_BLOCK) done, rid_, tag_, w_, j, d, data, _ = mdir.lookup(rid, TAG_BLOCK)
if not done and rid_ == rid and tag_ == TAG_BLOCK: if not done and rid_ == rid and tag_ == TAG_BLOCK:
d = 0 size_, block, off = frombptr(data)
size_, d_ = fromleb128(data[d:]); d += d_
block, d_ = fromleb128(data[d:]); d += d_
off, d_ = fromleb128(data[d:]); d += d_
size = max(size, size_) size = max(size, size_)
structs.append('block 0x%x.%x %d' % (block, off, size_)) structs.append('block 0x%x.%x %d' % (block, off, size_))
# indirect btree? # indirect btree?
done, rid_, tag_, w_, j, d, data, _ = mdir.lookup(rid, TAG_BTREE) done, rid_, tag_, w_, j, d, data, _ = mdir.lookup(rid, TAG_BTREE)
if not done and rid_ == rid and tag_ == TAG_BTREE: if not done and rid_ == rid and tag_ == TAG_BTREE:
d = 0 weight, block, trunk, cksum = frombtree(data)
weight, d_ = fromleb128(data[d:]); d += d_
block, d_ = fromleb128(data[d:]); d += d_
trunk, d_ = fromleb128(data[d:]); d += d_
cksum = fromle32(data[d:]); d += 4
size = max(size, weight) size = max(size, weight)
structs.append('btree 0x%x.%x %d' % (block, trunk, weight)) structs.append('btree 0x%x.%x %d' % (block, trunk, weight))
return 'reg %s' % ', '.join(it.chain(['%d' % size], structs)) return 'reg %s' % ', '.join(it.chain(['%d' % size], structs))
@@ -1311,9 +1315,7 @@ def dbg_fstruct(f, block_size, mdir, rid, tag, j, d, data, *,
w = len(data) w = len(data)
# shrub? # shrub?
elif tag == TAG_TRUNK: elif tag == TAG_TRUNK:
d = 0 weight, trunk = fromshrub(data)
weight, d_ = fromleb128(data[d:]); d += d_
trunk, d_ = fromleb128(data[d:]); d += d_
btree = Rbyd.fetch(f, block_size, mdir.block, trunk) btree = Rbyd.fetch(f, block_size, mdir.block, trunk)
w = weight w = weight
# direct block? # direct block?
@@ -1325,18 +1327,11 @@ def dbg_fstruct(f, block_size, mdir, rid, tag, j, d, data, *,
mdir.eoff, mdir.eoff,
j, j,
0) 0)
d = 0 size, block, off = frombptr(data)
size, d_ = fromleb128(data[d:]); d += d_
block, d_ = fromleb128(data[d:]); d += d_
off, d_ = fromleb128(data[d:]); d += d_
w = size w = size
# indirect btree? # indirect btree?
elif tag == TAG_BTREE: elif tag == TAG_BTREE:
d = 0 weight, block, trunk, cksum = frombtree(data)
weight, d_ = fromleb128(data[d:]); d += d_
block, d_ = fromleb128(data[d:]); d += d_
trunk, d_ = fromleb128(data[d:]); d += d_
cksum = fromle32(data[d:]); d += 4
btree = Rbyd.fetch(f, block_size, block, trunk) btree = Rbyd.fetch(f, block_size, block, trunk)
w = weight w = weight
@@ -2202,8 +2197,8 @@ def main(disk, mroots=None, *,
rec_dir(0, args.get('depth') or m.inf) rec_dir(0, args.get('depth') or m.inf)
if args.get('error_on_corrupt') and corrupted: if args.get('error_on_corrupt') and corrupted:
sys.exit(2) sys.exit(2)
if __name__ == "__main__": if __name__ == "__main__":
@@ -2305,7 +2300,7 @@ if __name__ == "__main__":
parser.add_argument( parser.add_argument(
'-e', '--error-on-corrupt', '-e', '--error-on-corrupt',
action='store_true', action='store_true',
help="Error if B-tree is corrupt.") help="Error if the filesystem is corrupt.")
sys.exit(main(**{k: v sys.exit(main(**{k: v
for k, v in vars(parser.parse_intermixed_args()).items() for k, v in vars(parser.parse_intermixed_args()).items()
if v is not None})) if v is not None}))
+6 -6
View File
@@ -119,6 +119,9 @@ def crc32c(data, crc=0):
crc = (crc >> 1) ^ ((crc & 1) * 0x82f63b78) crc = (crc >> 1) ^ ((crc & 1) * 0x82f63b78)
return 0xffffffff ^ crc return 0xffffffff ^ crc
def popc(x):
return bin(x).count('1')
def fromle32(data): def fromle32(data):
return struct.unpack('<I', data[0:4].ljust(4, b'\0'))[0] return struct.unpack('<I', data[0:4].ljust(4, b'\0'))[0]
@@ -160,9 +163,6 @@ def frombtree(data):
block, trunk, cksum = frombranch(data[d:]) block, trunk, cksum = frombranch(data[d:])
return w, block, trunk, cksum return w, block, trunk, cksum
def popc(x):
return bin(x).count('1')
def xxd(data, width=16): def xxd(data, width=16):
for i in range(0, len(data), width): for i in range(0, len(data), width):
yield '%-*s %-*s' % ( yield '%-*s %-*s' % (
@@ -1646,8 +1646,8 @@ def main(disk, mroots=None, *,
# force next btree entry to be shown # force next btree entry to be shown
prbyd = None prbyd = None
if args.get('error_on_corrupt') and corrupted: if args.get('error_on_corrupt') and corrupted:
sys.exit(2) sys.exit(2)
if __name__ == "__main__": if __name__ == "__main__":
@@ -1715,7 +1715,7 @@ if __name__ == "__main__":
parser.add_argument( parser.add_argument(
'-e', '--error-on-corrupt', '-e', '--error-on-corrupt',
action='store_true', action='store_true',
help="Error if B-tree is corrupt.") help="Error if the filesystem is corrupt.")
sys.exit(main(**{k: v sys.exit(main(**{k: v
for k, v in vars(parser.parse_intermixed_args()).items() for k, v in vars(parser.parse_intermixed_args()).items()
if v is not None})) if v is not None}))
+3 -3
View File
@@ -128,6 +128,9 @@ def crc32c(data, crc=0):
crc = (crc >> 1) ^ ((crc & 1) * 0x82f63b78) crc = (crc >> 1) ^ ((crc & 1) * 0x82f63b78)
return 0xffffffff ^ crc return 0xffffffff ^ crc
def popc(x):
return bin(x).count('1')
def fromle32(data): def fromle32(data):
return struct.unpack('<I', data[0:4].ljust(4, b'\0'))[0] return struct.unpack('<I', data[0:4].ljust(4, b'\0'))[0]
@@ -147,9 +150,6 @@ def fromtag(data):
size, d_ = fromleb128(data[2+d:]) size, d_ = fromleb128(data[2+d:])
return tag>>15, tag&0x7fff, weight, size, 2+d+d_ return tag>>15, tag&0x7fff, weight, size, 2+d+d_
def popc(x):
return bin(x).count('1')
def xxd(data, width=16): def xxd(data, width=16):
for i in range(0, len(data), width): for i in range(0, len(data), width):
yield '%-*s %-*s' % ( yield '%-*s %-*s' % (
+225 -137
View File
@@ -22,10 +22,10 @@ import time
CHARS = 'rpe-' CHARS = 'rpe-'
COLORS = ['32', '35', '34', ''] COLORS = ['32', '35', '34', '90']
WEAR_CHARS = '-123456789' WEAR_CHARS = '-123456789'
WEAR_COLORS = ['', '', '', '', '', '', '', '35', '35', '1;31'] WEAR_COLORS = ['90', '', '', '', '', '', '', '35', '35', '1;31']
CHARS_DOTS = " .':" CHARS_DOTS = " .':"
CHARS_BRAILLE = ( CHARS_BRAILLE = (
@@ -390,25 +390,52 @@ class Pixel(int):
return c return c
class Bd: class Bmap:
def __init__(self, *, def __init__(self, *,
block_size=1, block_size=1,
block_count=1, block_count=1,
block_window=None,
off_window=None,
width=None, width=None,
height=1, height=1,
pixels=None): pixels=None):
# default width to block_window or block_size
if width is None: if width is None:
width = block_count if block_window is not None:
width = len(block_window)
else:
width = block_count
# allocate pixels if not provided
if pixels is None: if pixels is None:
self.pixels = [Pixel() for _ in range(width*height)] pixels = [Pixel() for _ in range(width*height)]
else:
self.pixels = pixels self.pixels = pixels
self.block_size = block_size self.block_size = block_size
self.block_count = block_count self.block_count = block_count
self.block_window = block_window
self.off_window = off_window
self.width = width self.width = width
self.height = height self.height = height
@property
def _block_window(self):
if self.block_window is None:
return range(0, self.block_count)
else:
return self.block_window
@property
def _off_window(self):
if self.off_window is None:
return range(0, self.block_size)
else:
return self.off_window
@property
def _window(self):
return len(self._off_window)*len(self._block_window)
def _op(self, f, block=None, off=None, size=None): def _op(self, f, block=None, off=None, size=None):
if block is None: if block is None:
range_ = range(len(self.pixels)) range_ = range(len(self.pixels))
@@ -418,21 +445,31 @@ class Bd:
elif size is None: elif size is None:
off, size = 0, off off, size = 0, off
# update our geometry? # map into our window
if off+size > self.block_size or block >= self.block_count: if block not in self._block_window:
self.resize( return
block_size=max(self.block_size, off+size), block -= self._block_window.start
block_count=max(self.block_count, block+1))
size = (max(self._off_window.start,
min(self._off_window.stop, off+size))
- max(self._off_window.start,
min(self._off_window.stop, off)))
off = (max(self._off_window.start,
min(self._off_window.stop, off))
- self._off_window.start)
if size == 0:
return
# map to our block space # map to our block space
start = block*self.block_size + off range_ = range(
stop = block*self.block_size + off+size block*len(self._off_window) + off,
start = ((start*len(self.pixels)) block*len(self._off_window) + off+size)
// (self.block_size*self.block_count)) range_ = range(
stop = ((stop*len(self.pixels)) (range_.start*len(self.pixels)) // self._window,
// (self.block_size*self.block_count)) (range_.stop*len(self.pixels)) // self._window)
stop = max(stop, start+1) range_ = range(
range_ = range(start, stop) range_.start,
max(range_.stop, range_.start+1))
# apply the op # apply the op
for i in range_: for i in range_:
@@ -450,14 +487,6 @@ class Bd:
def clear(self, block=None, off=None, size=None): def clear(self, block=None, off=None, size=None):
self._op(Pixel.clear, block, off, size) self._op(Pixel.clear, block, off, size)
def copy(self):
return Bd(
pixels=self.pixels.copy(),
block_size=self.block_size,
block_count=self.block_count,
width=self.width,
height=self.height)
def resize(self, *, def resize(self, *,
block_size=None, block_size=None,
block_count=None, block_count=None,
@@ -477,21 +506,25 @@ class Bd:
return return
# transform our pixels # transform our pixels
self.block_size = block_size
self.block_count = block_count
pixels = [] pixels = []
for x in range(width*height): for x in range(width*height):
# map into our old bd space # map into our old bd space
start = (x*(block_size*block_count)) // (width*height) range_ = range(
stop = ((x+1)*(block_size*block_count)) // (width*height) (x*self._window) // (width*height),
stop = max(stop, start+1) ((x+1)*self._window) // (width*height))
range_ = range(
range_.start,
max(range_.stop, range_.start+1))
# aggregate state # aggregate state
pixels.append(ft.reduce( pixels.append(ft.reduce(
Pixel.__or__, Pixel.__or__,
self.pixels[start:stop], self.pixels[range_.start:range_.stop],
Pixel())) Pixel()))
self.block_size = block_size
self.block_count = block_count
self.width = width self.width = width
self.height = height self.height = height
self.pixels = pixels self.pixels = pixels
@@ -610,17 +643,19 @@ class Bd:
def main(path='-', block=None, *, def main(path='-', *,
off=None,
size=None,
block_size=None, block_size=None,
block_count=None, block_count=None,
block_cycles=None, block_cycles=None,
block=None,
off=None,
size=None,
read=False, read=False,
prog=False, prog=False,
erase=False, erase=False,
wear=False, wear=False,
reset=False, reset=False,
no_header=False,
color='auto', color='auto',
dots=False, dots=False,
braille=False, braille=False,
@@ -671,52 +706,64 @@ def main(path='-', block=None, *,
if block_count is None: if block_count is None:
block_count = block_count_ block_count = block_count_
# allow ranges for blocks/offs # try to simplify the block/off/size arguments a bit
if not isinstance(block, tuple): if not isinstance(block, tuple):
block = (block,) block = block,
if any(isinstance(block, list) and len(block) > 1 for block in block): if isinstance(off, tuple) and len(off) == 1:
off, = off
if isinstance(size, tuple) and len(size) == 1:
if off is None:
off, = size
size = None
if any(isinstance(b, list) and len(b) > 1 for b in block):
print("error: More than one block address?") print("error: More than one block address?")
sys.exit(-1) sys.exit(-1)
block = tuple( if isinstance(block[0], list):
block[0] if isinstance(block, list) else block block = (block[0][0], *block[1:])
for block in block) if len(block) > 1 and isinstance(block[1], list):
if not isinstance(off, tuple): block = (block[0], block[1][0])
off = (off,) if isinstance(block[0], tuple):
if not isinstance(size, tuple): block, off_ = (block[0][0], *block[1:]), block[0][1]
size = (size,) if off is None:
off = off_
if len(block) > 1 and isinstance(block[1], tuple):
block = (block[0], block[1][0])
if len(block) == 1:
block, = block
block_start = ( if isinstance(off, tuple):
block[0][0] if isinstance(block[0], tuple) off, size_ = off[0], off[1] - off[0]
else block[0] if block[0] is not None if size is None:
else 0) size = size_
block_stop = ( if isinstance(size, tuple):
block[1][0] if len(block) > 1 and isinstance(block[1], tuple) off_, size = off[0], off[1] - off[0]
else block[1] if len(block) > 1 and block[1] is not None if off is None:
else block_start+1 if len(block) == 1 and block[0] is not None off = off_
else block_count)
off_start = (
off[0] if off[0] is not None
else block[0][1] if isinstance(block[0], tuple)
else block[1][1] if len(block) > 1 and isinstance(block[1], tuple)
else size[0] if len(size) > 1 and size[0] is not None
else 0)
off_stop = (
off_start + size[0] if len(size) == 1 and size[0] is not None
else off[1] if len(off) > 1 and off[1] is not None
else size[1] if len(size) > 1 and size[1] is not None
else block_size)
# create a block device representation # is a block window specified?
bd = Bd() block_window = None
if block is not None:
if isinstance(block, tuple):
block_window = range(*block)
else:
block_window = range(block, block+1)
def resize(*, block_size=None, block_count=None): off_window = None
nonlocal bd if off is not None or size is not None:
off_ = off if off is not None else 0
size_ = size if size is not None else 1
off_window = range(off_, off_+size_)
# size may be overriden by cli args # create our block device representation
if off_stop is not None: bmap = Bmap(
block_size = off_stop-off_start block_size=block_size,
if block_stop is not None: block_count=block_count,
block_count = block_stop-block_start block_window=block_window,
off_window=off_window)
def resize():
nonlocal bmap
# figure out best width/height # figure out best width/height
if width is None: if width is None:
@@ -733,17 +780,22 @@ def main(path='-', block=None, *,
else: else:
height_ = shutil.get_terminal_size((80, 5))[1] height_ = shutil.get_terminal_size((80, 5))[1]
bd.resize( # terminal size changed?
block_size=block_size, if width_ != bmap.width or height_ != bmap.height:
block_count=block_count, bmap.resize(
# scale if we're printing with dots or braille # scale if we're printing with dots or braille
width=2*width_ if braille else width_, width=2*width_ if braille else width_,
height=max(1, height=max(1,
4*height_ if braille 4*height_ if braille
else 2*height_ if dots else 2*height_ if dots
else height_)) else height_))
resize() resize()
# keep track of some extra info
readed = 0
proged = 0
erased = 0
# parse a line of trace output # parse a line of trace output
pattern = re.compile( pattern = re.compile(
'^(?P<file>[^:]*):(?P<line>[0-9]+):trace:.*?bd_(?:' '^(?P<file>[^:]*):(?P<line>[0-9]+):trace:.*?bd_(?:'
@@ -771,7 +823,10 @@ def main(path='-', block=None, *,
'|' '(?P<sync>sync)\(' '|' '(?P<sync>sync)\('
'\s*(?P<sync_ctx>\w+)' '\s*\)' ')\s*$') '\s*(?P<sync_ctx>\w+)' '\s*\)' ')\s*$')
def parse(line): def parse(line):
nonlocal bd nonlocal bmap
nonlocal readed
nonlocal proged
nonlocal erased
# string searching is much faster than the regex here, and this # string searching is much faster than the regex here, and this
# actually has a big impact given how much trace output comes # actually has a big impact given how much trace output comes
@@ -787,13 +842,20 @@ def main(path='-', block=None, *,
block_size = int(m.group('block_size'), 0) block_size = int(m.group('block_size'), 0)
block_count = int(m.group('block_count'), 0) block_count = int(m.group('block_count'), 0)
resize(block_size=block_size, block_count=block_count)
if reset: if reset:
bd = Bd( bmap = Bmap(
block_size=bd.block_size, block_size=block_size,
block_count=bd.block_count, block_count=block_count,
width=bd.width, block_window=bmap.block_window,
height=bd.height) off_window=bmap.off_window,
width=bmap.width,
height=bmap.height)
else:
if (block_size != bmap.block_size
or block_count != bmap.block_count):
bmap.resize(
block_size=block_size,
block_count=block_count)
return True return True
elif m.group('read') and read: elif m.group('read') and read:
@@ -801,17 +863,13 @@ def main(path='-', block=None, *,
off = int(m.group('read_off'), 0) off = int(m.group('read_off'), 0)
size = int(m.group('read_size'), 0) size = int(m.group('read_size'), 0)
if ((block_stop is not None and block >= block_stop) if block >= bmap.block_count or off+size > bmap.block_size:
or block < block_start bmap.resize(
or (off_stop is not None and off >= off_stop) block_size=max(off+size, bmap.block_size),
or off+size <= off_start): block_count=max(block+1, bmap.block_count))
return False
block -= block_start
size = ((min(off+size, off_stop)
if off_stop is not None else off+size)
- max(off, off_start))
bd.read(block, off, size) bmap.read(block, off, size)
readed += size
return True return True
elif m.group('prog') and prog: elif m.group('prog') and prog:
@@ -819,35 +877,26 @@ def main(path='-', block=None, *,
off = int(m.group('prog_off'), 0) off = int(m.group('prog_off'), 0)
size = int(m.group('prog_size'), 0) size = int(m.group('prog_size'), 0)
if ((block_stop is not None and block >= block_stop) if block >= bmap.block_count or off+size > bmap.block_size:
or block < block_start bmap.resize(
or (off_stop is not None and off >= off_stop) block_size=max(off+size, bmap.block_size),
or off+size <= off_start): block_count=max(block+1, bmap.block_count))
return False
block -= block_start
size = ((min(off+size, off_stop)
if off_stop is not None else off+size)
- max(off, off_start))
bd.prog(block, off, size) bmap.prog(block, off, size)
proged += size
return True return True
elif m.group('erase') and (erase or wear): elif m.group('erase') and (erase or wear):
block = int(m.group('erase_block'), 0) block = int(m.group('erase_block'), 0)
off = 0
size = int(m.group('erase_size'), 0) size = int(m.group('erase_size'), 0)
if ((block_stop is not None and block >= block_stop) if block >= bmap.block_count or size > bmap.block_size:
or block < block_start bmap.resize(
or (off_stop is not None and off >= off_stop) block_size=max(off+size, bmap.block_size),
or off+size <= off_start): block_count=max(block+1, bmap.block_count))
return False
block -= block_start
size = ((min(off+size, off_stop)
if off_stop is not None else off+size)
- max(off, off_start))
bd.erase(block, off, size) bmap.erase(block, size)
erased += size
return True return True
else: else:
@@ -855,17 +904,49 @@ def main(path='-', block=None, *,
# print trace output # print trace output
def draw(f): def draw(f):
nonlocal readed
nonlocal proged
nonlocal erased
def writeln(s=''): def writeln(s=''):
f.write(s) f.write(s)
f.write('\n') f.write('\n')
f.writeln = writeln f.writeln = writeln
# print some information about read/prog/erases
if not no_header:
# compute total ops
total = readed+proged+erased
# compute stddev of wear using our bmap, this is a bit different
# from read/prog/erase which ignores any bmap window, but it's
# what we have
if wear:
mean = (sum(p.wear for p in bmap.pixels)
/ max(len(bmap.pixels), 1))
stddev = m.sqrt(sum((p.wear - mean)**2 for p in bmap.pixels)
/ max(len(bmap.pixels), 1))
worst = max((p.wear for p in bmap.pixels), default=0)
f.writeln('bd %dx%d%s%s%s%s' % (
block_size, block_count,
', %6s read' % ('%.1f%%' % (100*readed / max(total, 1)))
if read else '',
', %6s prog' % ('%.1f%%' % (100*proged / max(total, 1)))
if prog else '',
', %6s erase' % ('%.1f%%' % (100*erased / max(total, 1)))
if erase else '',
', %13s wear' % ('%.1fσ (%.1f%%)' % (
worst / max(stddev, 1),
100*stddev / max(worst, 1)))
if wear else ''))
# don't forget we've scaled this for braille/dots! # don't forget we've scaled this for braille/dots!
for row in range( for row in range(
m.ceil(bd.height/4) if braille m.ceil(bmap.height/4) if braille
else m.ceil(bd.height/2) if dots else m.ceil(bmap.height/2) if dots
else bd.height): else bmap.height):
line = bd.draw(row, line = bmap.draw(row,
read=read, read=read,
prog=prog, prog=prog,
erase=erase, erase=erase,
@@ -880,7 +961,10 @@ def main(path='-', block=None, *,
if line: if line:
f.writeln(line) f.writeln(line)
bd.clear() bmap.clear()
readed = 0
proged = 0
erased = 0
resize() resize()
@@ -888,7 +972,7 @@ def main(path='-', block=None, *,
if cat: if cat:
ring = sys.stdout ring = sys.stdout
else: else:
ring = LinesIO(lines) ring = LinesIO(lines + (1 if not no_header else 0))
# if sleep print in background thread to avoid getting stuck in a read call # if sleep print in background thread to avoid getting stuck in a read call
event = th.Event() event = th.Event()
@@ -945,20 +1029,13 @@ if __name__ == "__main__":
import sys import sys
import argparse import argparse
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Display operations on block devices based on " description="Render operations on block devices based on "
"trace output.", "trace output.",
allow_abbrev=False) allow_abbrev=False)
parser.add_argument( parser.add_argument(
'path', 'path',
nargs='?', nargs='?',
help="Path to read from.") help="Path to read from.")
parser.add_argument(
'block',
nargs='?',
type=lambda x: tuple(
rbydaddr(x) if x.strip() else None
for x in x.split(',')),
help="Optional block to show, may be a range.")
parser.add_argument( parser.add_argument(
'-B', '--block-size', '-B', '--block-size',
type=bdgeom, type=bdgeom,
@@ -971,6 +1048,13 @@ if __name__ == "__main__":
'-C', '--block-cycles', '-C', '--block-cycles',
type=lambda x: int(x, 0), type=lambda x: int(x, 0),
help="Assumed maximum number of erase cycles when measuring wear.") help="Assumed maximum number of erase cycles when measuring wear.")
parser.add_argument(
'-@', '--block',
nargs='?',
type=lambda x: tuple(
rbydaddr(x) if x.strip() else None
for x in x.split(',')),
help="Optional block to show, may be a range.")
parser.add_argument( parser.add_argument(
'--off', '--off',
type=lambda x: tuple( type=lambda x: tuple(
@@ -1003,6 +1087,10 @@ if __name__ == "__main__":
'-R', '--reset', '-R', '--reset',
action='store_true', action='store_true',
help="Reset wear on block device initialization.") help="Reset wear on block device initialization.")
parser.add_argument(
'-N', '--no-header',
action='store_true',
help="Don't show the header.")
parser.add_argument( parser.add_argument(
'--color', '--color',
choices=['never', 'always', 'auto'], choices=['never', 'always', 'auto'],