From 9b803f962504813d30ca0227149d6018ff0cf78f Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sat, 20 May 2023 13:12:45 -0500 Subject: [PATCH] Reimplemented tree rendering in dbg*.py scripts The goal here was to add the option to show the combined rbyd trees in dbgbtree.py/dbgmtree.py. This was quite tricky, (and not really helped by the hackiness of these scripts), but was made a bit easier by adding a general purpose tree renderer that can render a precomputed set of branches into the tag output. For example, a 2-deep rendering of a simple btree with a block size of 1KiB, where you can see a bit of the emergent data-structure: $ ./scripts/dbgbtree.py disk -B1024 0x223 -t -Z2 -i btree 0x223.90, rev 46, weight 1024 rbyd ids tag ... 0223.0090: .-+ 0-199 btree w200 9 ... 00cb.0048: | | .-> 0-39 btree w40 7 ... | | .---+-> 40-79 btree w40 7 ... | | | .---> 80-119 btree w40 7 ... | | | | .-> 120-159 btree w40 7 ... | '-+-+-+-> 160-199 btree w40 7 ... 0223.0090: .---+-+ 200-399 btree w200 9 ... 013e.004b: | | .-> 200-239 btree w40 7 ... | | .---+-> 240-279 btree w40 8 ... | | | .---> 280-319 btree w40 8 ... | | | | .-> 320-359 btree w40 8 ... | '-+-+-+-> 360-399 btree w40 8 ... 0223.0090: | .---+ 400-599 btree w200 9 ... 01a7.004c: | | | .-> 400-439 btree w40 8 ... | | | .---+-> 440-479 btree w40 8 ... | | | | .---> 480-519 btree w40 8 ... | | | | | .-> 520-559 btree w40 8 ... | | '-+-+-+-> 560-599 btree w40 8 ... 0223.0090: | | .-+ 600-799 btree w200 9 ... 021e.004c: | | | | .-> 600-639 btree w40 8 ... | | | | .---+-> 640-679 btree w40 8 ... | | | | | .---> 680-719 btree w40 8 ... | | | | | | .-> 720-759 btree w40 8 ... | | | '-+-+-+-> 760-799 btree w40 8 ... 0223.0090: +-+-+-+ 800-1023 btree w224 10 ... 021f.0298: | .-> 800-839 btree w40 8 ... | .-+-> 840-879 btree w40 8 ... | | .-> 880-919 btree w40 8 ... '---+-+-> 920-1023 btree w104 9 ... This tree renderer also replaces the adhoc tree rendere in dbgrbyd.py for consistency. --- scripts/dbgbtree.py | 651 ++++++++++++++++++++++++++++++-------------- scripts/dbgrbyd.py | 176 ++++++------ 2 files changed, 536 insertions(+), 291 deletions(-) diff --git a/scripts/dbgbtree.py b/scripts/dbgbtree.py index 0fb5a40e..cfebe87d 100755 --- a/scripts/dbgbtree.py +++ b/scripts/dbgbtree.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import bisect +import collections as co import itertools as it import math as m import os @@ -295,10 +296,11 @@ class Rbyd: def lookup(self, id, tag): if not self: - return True, 0, -1, 0, 0, 0, b'' + return True, 0, -1, 0, 0, 0, b'', [] lower = -1 upper = self.weight + path = [] # descend down tree j = self.trunk @@ -314,11 +316,33 @@ class Rbyd: lower += upper-lower-1-weight_ if alt & 0x4 else 0 upper -= upper-lower-1-weight_ if not alt & 0x4 else 0 j = j - jump + + # figure out which color + if alt & 0x2: + _, nalt, _, _, _ = fromtag(self.data[j+jump+d:]) + if nalt & 0x2: + path.append((j+jump, j, True, 'y')) + else: + path.append((j+jump, j, True, 'r')) + else: + path.append((j+jump, j, True, 'b')) + # stay on path else: lower += weight_ if not alt & 0x4 else 0 upper -= weight_ if alt & 0x4 else 0 j = j + d + + # figure out which color + if alt & 0x2: + _, nalt, _, _, _ = fromtag(self.data[j:]) + if nalt & 0x2: + path.append((j-d, j, False, 'y')) + else: + path.append((j-d, j, False, 'r')) + else: + path.append((j-d, j, False, 'b')) + # found tag else: id_ = upper-1 @@ -327,8 +351,7 @@ class Rbyd: done = (id_, tag_) < (id, tag) or tag_ & 2 - return (done, id_, tag_, w_, - j, d, self.data[j+d:j+d+jump]) + return done, id_, tag_, w_, j, d, self.data[j+d:j+d+jump], path def __bool__(self): return bool(self.trunk) @@ -350,6 +373,95 @@ class Rbyd: yield id, tag, w, j, d, data + # create tree representation for debugging + def tree(self): + trunks = co.defaultdict(lambda: (-1, 0)) + alts = co.defaultdict(lambda: {}) + + id, tag = -1, 0 + while True: + done, id, tag, w, j, d, data, path = self.lookup(id, tag+0x10) + # found end of tree? + if done: + break + + # keep track of trunks/alts + trunks[j] = (id, tag) + + for j_, j__, followed, c in path: + if followed: + alts[j_] |= {'f': j__, 'c': c} + else: + alts[j_] |= {'nf': j__, 'c': c} + + # prune any alts with unreachable edges + pruned = {} + for j_, alt in alts.items(): + if 'f' not in alt: + pruned[j_] = alt['nf'] + elif 'nf' not in alt: + pruned[j_] = alt['f'] + for j_ in pruned.keys(): + del alts[j_] + + for j_, alt in alts.items(): + while alt['f'] in pruned: + alt['f'] = pruned[alt['f']] + while alt['nf'] in pruned: + alt['nf'] = pruned[alt['nf']] + + # find the trunk and depth of each alt, assuming pruned alts + # didn't exist + def rec_trunk(j_): + if j_ not in alts: + return trunks[j_] + else: + if 'nft' not in alts[j_]: + alts[j_]['nft'] = rec_trunk(alts[j_]['nf']) + return alts[j_]['nft'] + + for j_ in alts.keys(): + rec_trunk(j_) + for j_, alt in alts.items(): + if alt['f'] in alts: + alt['ft'] = alts[alt['f']]['nft'] + else: + alt['ft'] = trunks[alt['f']] + + def rec_height(j_): + if j_ not in alts: + return 0 + else: + if 'h' not in alts[j_]: + alts[j_]['h'] = max( + rec_height(alts[j_]['f']), + rec_height(alts[j_]['nf'])) + 1 + return alts[j_]['h'] + + for j_ in alts.keys(): + rec_height(j_) + + t_depth = max((alt['h']+1 for alt in alts.values()), default=0) + + # convert to more general tree representation + tree = [] + for j, alt in alts.items(): + # note all non-trunk edges should be black + tree.append({ + 'a': alt['nft'], + 'b': alt['nft'], + 'd': t_depth-1 - alt['h'], + 'c': alt['c'], + }) + tree.append({ + 'a': alt['nft'], + 'b': alt['ft'], + 'd': t_depth-1 - alt['h'], + 'c': 'b', + }) + + return tree, t_depth + def main(disk, roots=None, *, block_size=None, @@ -382,138 +494,300 @@ def main(disk, roots=None, *, btree.addr(), btree.rev, btree.weight)) # look up an id, while keeping track of the search path - def lookup(id, depth=None): + def btree_lookup(bid, depth=None): rbyd = btree - rid = id + rid = bid depth_ = 1 path = [] # corrupted? return a corrupted block once if not rbyd: - return (id > 0, id, 0, rbyd, -1, - (0, 0, 0, b''), - (0, 0, 0, b''), - path) + return bid > 0, bid, 0, rbyd, -1, [], path while True: - # first lookup id/name - (done, rid_, name_tag, w, - name_j, name_d, name) = rbyd.lookup(rid, 0) - if done: - return (True, id, 0, rbyd, -1, - (0, 0, 0, b''), - (0, 0, 0, b''), - path) + # collect all tags, normally you don't need to do this + # but we are debugging here + name = None + tags = [] + branch = None + rid_ = rid + tag = 0 + w = 0 + for i in it.count(): + done, rid__, tag, w_, j, d, data, _ = rbyd.lookup( + rid_, tag+0x10) + if done or (i != 0 and rid__ != rid_): + break - if name_tag & 0xf00f == TAG_NAME: - # then lookup struct - (done, _, struct_tag, _, - struct_j, struct_d, struct_) = rbyd.lookup( - rid_, TAG_STRUCT) - if done: - return (True, id, 0, rbyd, -1, - (0, 0, 0, b''), - (0, 0, 0, b''), - path) - else: - tag = name_tag - struct_tag, struct_j, struct_d, struct_ = ( - name_tag, name_j, name_d, name) - name_tag, name_j, name_d, name = 0, 0, 0, b'' + # first tag indicates the branch's weight + if i == 0: + rid_, w = rid__, w_ - path.append((id + (rid_-rid), w, rbyd, rid_, - (name_tag, name_j, name_d, name), - (struct_tag, struct_j, struct_d, struct_))) + # catch any branches + if tag == TAG_BTREE: + branch = (tag, j, d, data) - # is it another branch? continue down tree - if struct_tag == TAG_BTREE and ( + tags.append((tag, j, d, data)) + + # keep track of path + path.append((bid + (rid_-rid), w, rbyd, rid_, tags)) + + # descend down branch? + if branch is not None and ( not depth or depth_ < depth): - w, trunk, block, crc = frombtree(struct_) + tag, j, d, data = branch + w_, trunk, block, crc = frombtree(data) rbyd = Rbyd.fetch(f, block_size, block, trunk) # corrupted? bail here so we can keep traversing the tree if not rbyd: - return (False, id + (rid_-rid), w, rbyd, -1, - (0, 0, 0, b''), - (0, 0, 0, b''), - path) + return False, bid + (rid_-rid), w, rbyd, -1, [], path rid -= (rid_-(w-1)) depth_ += 1 else: - return (False, id + (rid_-rid), w, rbyd, rid_, - (name_tag, name_j, name_d, name), - (struct_tag, struct_j, struct_d, struct_), - path) + return not tags, bid + (rid_-rid), w, rbyd, rid_, tags, path - # if we're printing the tree, first find the max depth so we know how - # much space to reserve + # precompute rbyd-trees if requested t_width = 0 if args.get('tree'): - t_depth = 0 - id = -1 + # find the max depth of each layer to nicely align trees + # TODO memoize + bdepths = {} + bid = -1 while True: - (done, id, w, rbyd, rid, _, _, - path) = (lookup(id+1, depth=args.get('depth'))) + done, bid, w, rbyd, rid, tags, path = btree_lookup( + bid+1, depth=args.get('depth')) if done: break - t_depth = max(t_depth, len(path)) + for d, (bid, w, rbyd, rid, tags) in enumerate(path): + _, rdepth = rbyd.tree() + bdepths[d] = max(bdepths.get(d, 0), rdepth) - t_width = 2*t_depth+2 if t_depth > 0 else 0 - t_branches = [(0, btree.weight)] + # find all branches + tree = [] + root = None + branches = {} + bid = -1 + while True: + done, bid, w, rbyd, rid, tags, path = btree_lookup( + bid+1, depth=args.get('depth')) + if done: + break - def treerepr(id, w, leaf=True, depth=None): - branches_ = [] - for i in range(len(t_branches)): - if depth is not None and depth == i-1: - branches_.append('+' if leaf else '|') - break + d_ = 0 + leaf = None + for d, (bid, w, rbyd, rid, tags) in enumerate(path): + if not tags: + continue - if i+1 < len(t_branches): - if (leaf - and id-(w-1) == t_branches[i+1][0] - and t_branches[i][0] == t_branches[i+1][0] - and (not args.get('inner') - or (i == 0 and depth == 0))): - branches_.append('+-') - elif (leaf - and id-(w-1) == t_branches[i+1][0] - and t_branches[i][1] == t_branches[i+1][1] - and (not args.get('inner') or depth == i)): - branches_.append('\'-') - elif (leaf - and id-(w-1) == t_branches[i+1][0] - and (not args.get('inner') or depth == i)): - branches_.append('|-') - elif (id-(w-1) >= t_branches[i][0] - and id-(w-1) < t_branches[i][1] - and t_branches[i][1] != t_branches[i+1][1]): - branches_.append('| ') + # map rbyd tree into B-tree space + rtree, rdepth = rbyd.tree() + + # note we adjust our bid/rids to be left-leaning, + # this allows a global order and make tree rendering quite + # a bit easier + for i in range(len(rtree)): + a_rid, a_tag = rtree[i]['a'] + b_rid, b_tag = rtree[i]['b'] + _, _, _, a_w, _, _, _, _ = rbyd.lookup(a_rid, 0) + _, _, _, b_w, _, _, _, _ = rbyd.lookup(b_rid, 0) + rtree[i] = { + 'a': (a_rid-(a_w-1), a_tag), + 'b': (b_rid-(b_w-1), b_tag), + 'd': rtree[i]['d'], + 'c': rtree[i]['c'], + } + + # connect our branch to the rbyd's root + if leaf is not None: + root = min(rtree, + key=lambda branch: branch['d'], + default=None) + + if root is not None: + r_rid, r_tag = root['a'] else: - branches_.append(' ') - else: - if (leaf - and id-(w-1) == t_branches[i][0] - and (not args.get('inner') or i == 0)): - branches_.append('+-%s> ' % ('-'*2*(t_depth-i-1))) - elif (leaf - and id == t_branches[i][1]-1): - branches_.append('\'-%s> ' % ('-'*2*(t_depth-i-1))) - elif (leaf - and id >= t_branches[i][0] - and id-(w-1) < t_branches[i][1]): - branches_.append('|-%s> ' % ('-'*2*(t_depth-i-1))) - elif (id >= t_branches[i][0] - and id < t_branches[i][1]-1): - branches_.append('|') - else: - branches_.append(' ') + r_rid, r_tag = rid-(w-1), tags[0][0] + tree.append({ + 'a': leaf, + 'b': (bid-rid+r_rid, d, r_rid, r_tag), + 'd': d_-1, + 'c': 'b', + }) - return '%s%-*s%s' % ( - '\x1b[90m' if color else '', - t_width, ''.join(branches_), - '\x1b[m' if color else '') + for branch in rtree: + # map rbyd branches into our btree space + a_rid, a_tag = branch['a'] + b_rid, b_tag = branch['b'] + tree.append({ + 'a': (bid-rid+a_rid, d, a_rid, a_tag), + 'b': (bid-rid+b_rid, d, b_rid, b_tag), + 'd': branch['d'] + d_ + bdepths.get(d, 0)-rdepth, + 'c': branch['c'], + }) + + d_ += max(rdepth, 1) + leaf = (bid-(w-1), d, rid-(w-1), TAG_BTREE) + + # remap branches to leaves if we aren't showing inner branches + if not args.get('inner'): + # step through each layer backwards + b_depth = max((branch['a'][1]+1 for branch in tree), default=0) + + # keep track of the original tree to find the original bids, + # unfortunately because we store the bids in the branches we + # overwrite these + tree_ = tree.copy() + + for bd in reversed(range(b_depth-1)): + # find leaf-roots at this level + roots = {} + for branch_, branch in zip(tree_, tree): + bid = branch['b'][0] - branch['b'][2] + # choose the highest node as the root + if (branch_['b'][1] == b_depth-1 + and (bid not in roots + or branch_['d'] < roots[bid]['d'])): + roots[bid] = branch_ + + # remap branches to leaf-roots + tree__ = [] + for branch_ in tree_: + if branch_['a'][1] == bd and branch_['a'][0] in roots: + branch_ = { + 'a': roots[branch_['a'][0]]['b'], + 'b': branch_['b'], + 'd': branch_['d'], + 'c': branch_['c'], + } + if branch_['b'][1] == bd and branch_['b'][0] in roots: + branch_ = { + 'a': branch_['a'], + 'b': roots[branch_['b'][0]]['b'], + 'd': branch_['d'], + 'c': branch_['c'], + } + tree__.append(branch_) + tree_ = tree__ + tree = tree_ + + # precompute B-trees if requested + elif args.get('btree'): + # find all branches + tree = [] + root = None + branches = {} + leaves = {} + bid = -1 + while True: + done, bid, w, rbyd, rid, tags, path = btree_lookup( + bid+1, depth=args.get('depth')) + if done: + break + + # if we're not showing inner nodes, prefer names higher in + # the tree since this avoids showing vestigial names + name = None + if not args.get('inner'): + name = None + for bid_, w_, rbyd_, rid_, tags_ in reversed(path): + for tag_, j_, d_, data_ in tags_: + if tag_ & 0xf00f == TAG_NAME: + name = (tag_, j_, d_, data_) + + if rid_-(w_-1) != 0: + break + + a = root + for d, (bid, w, rbyd, rid, tags) in enumerate(path): + if not tags: + continue + + b = (bid-(w-1), d, rid-(w-1), + (name if name else tags[0])[0]) + + # remap branches to leaves if we aren't showing + # inner branches + if not args.get('inner'): + if b not in branches: + bid, w, rbyd, rid, tags = path[-1] + if not tags: + continue + branches[b] = ( + bid-(w-1), len(path)-1, rid-(w-1), + (name if name else tags[0])[0]) + b = branches[b] + + # found entry point? + if root is None: + root = b + a = root + + tree.append({ + 'a': a, + 'b': b, + 'd': d, + 'c': 'b', + }) + a = b + + # common tree renderer + if args.get('tree') or args.get('btree'): + # find the max depth from the tree + t_depth = max((branch['d']+1 for branch in tree), default=0) + if t_depth > 0: + t_width = 2*t_depth + 2 + + def treerepr(bid, w, bd, rid, tag): +# print('%-32s' % repr((bid-(w-1), bd, rid, tag)), end='') + if t_depth == 0: + return '' + + def branchrepr(x, d, was): + for branch in tree: + if branch['d'] == d and branch['b'] == x: + if any(branch['d'] == d and branch['a'] == x + for branch in tree): + return '+-', branch['c'], branch['c'] + elif any(branch['d'] == d + and x > min(branch['a'], branch['b']) + and x < max(branch['a'], branch['b']) + for branch in tree): + return '|-', branch['c'], branch['c'] + elif branch['a'] < branch['b']: + return '\'-', branch['c'], branch['c'] + else: + return '.-', branch['c'], branch['c'] + for branch in tree: + if branch['d'] == d and branch['a'] == x: + return '+ ', branch['c'], None + for branch in tree: + if (branch['d'] == d + and x > min(branch['a'], branch['b']) + and x < max(branch['a'], branch['b'])): + return '| ', branch['c'], was + if was: + return '--', was, was + return ' ', None, None + + trunk = [] + was = None + for d in range(t_depth): + t, c, was = branchrepr( + (bid-(w-1), bd, rid-(w-1), tag), d, was) + + trunk.append('%s%s%s%s' % ( + '\x1b[33m' if color and c == 'y' + else '\x1b[31m' if color and c == 'r' + else '\x1b[90m' if color and c == 'b' + else '', + t, + ('>' if was else ' ') if d == t_depth-1 else '', + '\x1b[m' if color and c else '')) + + return '%s ' % ''.join(trunk) # print header @@ -527,140 +801,93 @@ def main(disk, roots=None, *, if not args.get('no_truncate') else '')) # prbyd here means the last rendered rbyd, we update - # in dbg_entry to always print interleaved addresses + # in dbg_branch to always print interleaved addresses prbyd = None - def dbg_entry(id, w, rbyd, rid, - name_tag, name_j, name_d, name, - struct_tag, struct_j, struct_d, struct_, - depth=None): + def dbg_branch(bid, w, rbyd, rid, tags, bd): nonlocal prbyd # show human-readable representation - if name_tag: + for i, (tag, j, d, data) in enumerate(tags): print('%10s %s%*s %-22s %s' % ( '%04x.%04x:' % (rbyd.block, rbyd.trunk) if prbyd is None or rbyd != prbyd else '', - treerepr(id, w, True, depth) if args.get('tree') else '', - w_width, '%d-%d' % (id-(w-1), id) if w > 1 - else id if w > 0 + treerepr(bid, w, bd, rid, tag) + if args.get('tree') or args.get('btree') else '', + w_width, '' if i != 0 + else '%d-%d' % (bid-(w-1), bid) if w > 1 + else bid if w > 0 else '', - tagrepr(name_tag, w, len(name), None), + tagrepr(tag, w if i == 0 else 0, len(data), None), + # note we render names a bit different here ''.join( b if b >= ' ' and b <= '~' else '.' - for b in map(chr, name)))) + for b in map(chr, data)) + if tag & 0xf00f == TAG_NAME + else next(xxd(data, 8), '') + if not args.get('no_truncate') + else '')) prbyd = rbyd - print('%10s %s%*s %-22s %s' % ( - '%04x.%04x:' % (rbyd.block, rbyd.trunk) - if prbyd is None or rbyd != prbyd - else '', - treerepr(id, w, not name_tag, depth) - if args.get('tree') else '', - w_width, '' if name_tag - else '%d-%d' % (id-(w-1), id) if w > 1 - else id if w > 0 - else '', - tagrepr(struct_tag, - w if not name_tag else 0, - len(struct_), None), - next(xxd(struct_, 8), '') - if not args.get('no_truncate') else '')) - prbyd = rbyd # show in-device representation if args.get('device'): - if name_tag: + for i, (tag, j, d, data) in enumerate(tags): print('%9s %*s%*s %-22s%s' % ( '', t_width, '', w_width, '', - '%04x %08x %07x' % (name_tag, w, len(name)), + '%04x %08x %07x' % (tag, w if i == 0 else 0, len(data)), ' %s' % ' '.join( '%08x' % fromle32( - rbyd.data[name_j+name_d+i*4 - : name_j+name_d + min(i*4+4,len(name))]) - for i in range(min(m.ceil(len(name)/4), 3)))[:23])) - print('%9s %*s%*s %-22s%s' % ( - '', - t_width, '', - w_width, '', - '%04x %08x %07x' % ( - struct_tag, w if not name_tag else 0, len(struct_)), - ' %s' % ' '.join( - '%08x' % fromle32( - rbyd.data[struct_j+struct_d+i*4 - : struct_j+struct_d + min(i*4+4,len(struct_))]) - for i in range(min(m.ceil(len(struct_)/4), 3)))[:23])) + rbyd.data[j+d+i*4 : j+d + min(i*4+4,len(data))]) + for i in range(min(m.ceil(len(data)/4), 3)))[:23])) # show on-disk encoding of tags/data - if args.get('raw'): - for o, line in enumerate(xxd( - rbyd.data[name_j:name_j+name_d])): - print('%9s: %*s%*s %s' % ( - '%04x' % (name_j + o*16), - t_width, '', - w_width, '', - line)) - if args.get('raw'): - for o, line in enumerate(xxd(name)): - print('%9s: %*s%*s %s' % ( - '%04x' % (name_j+name_d + o*16), - t_width, '', - w_width, '', - line)) - if args.get('raw'): - for o, line in enumerate(xxd( - rbyd.data[struct_j:struct_j+struct_d])): - print('%9s: %*s%*s %s' % ( - '%04x' % (struct_j + o*16), - t_width, '', - w_width, '', - line)) - if args.get('raw') or args.get('no_truncate'): - for o, line in enumerate(xxd(struct_)): - print('%9s: %*s%*s %s' % ( - '%04x' % (struct_j+struct_d + o*16), - t_width, '', - w_width, '', - line)) + for i, (tag, j, d, data) in enumerate(tags): + if args.get('raw'): + for o, line in enumerate(xxd(rbyd.data[j:j+d])): + print('%9s: %*s%*s %s' % ( + '%04x' % (j + o*16), + t_width, '', + w_width, '', + line)) + # note we don't render name tags with no_truncate + if args.get('raw') or ( + args.get('no_truncate') and tag & 0xf00f != TAG_NAME): + for o, line in enumerate(xxd(data)): + print('%9s: %*s%*s %s' % ( + '%04x' % (j+d + o*16), + t_width, '', + w_width, '', + line)) # traverse and print entries - id = -1 + bid = -1 prbyd = None ppath = [] corrupted = False while True: - (done, id, w, rbyd, rid, - (name_tag, name_j, name_d, name), - (struct_tag, struct_j, struct_d, struct_), - path) = (lookup(id+1, depth=args.get('depth'))) + done, bid, w, rbyd, rid, tags, path = btree_lookup( + bid+1, depth=args.get('depth')) if done: break - if args.get('inner') or args.get('tree'): - t_branches = [(0, btree.weight)] + if args.get('inner'): changed = False - for i, (x, px) in enumerate( - it.zip_longest(path[:-1], ppath[:-1])): + for (x, px) in it.zip_longest( + enumerate(path[:-1]), + enumerate(ppath[:-1])): if x is None: break + if not (changed or px is None or x != px): + continue + changed = True - (id_, w_, rbyd_, rid_, - (name_tag_, name_j_, name_d_, name_), - (struct_tag_, struct_j_, struct_d_, struct__)) = x - t_branches.append((id_-(w_-1), id_+1)) - - if args.get('inner'): - if not (changed or px is None or x != px): - continue - changed = True - - # show the inner entry - dbg_entry(id_, w_, rbyd_, rid_, - name_tag_, name_j_, name_d_, name_, - struct_tag_, struct_j_, struct_d_, struct__, - i) + # show the inner entry + d, (bid_, w_, rbyd_, rid_, tags_) = x + dbg_branch(bid_, w_, rbyd_, rid_, tags_, d) + ppath = path # corrupted? try to keep printing the tree if not rbyd: @@ -672,24 +899,28 @@ def main(disk, roots=None, *, '\x1b[m' if color else '')) prbyd = rbyd - ppath = path corrupted = True continue # if we're not showing inner nodes, prefer names higher in the tree # since this avoids showing vestigial names if not args.get('inner'): - for id_, w_, rbyd_, rid_, name_, struct__ in reversed(path): - name_tag_, name_j, name_d, name = name_ + name = None + for bid_, w_, rbyd_, rid_, tags_ in reversed(path): + for tag_, j_, d_, data_ in tags_: + if tag_ & 0xf00f == TAG_NAME: + name = (tag_, j_, d_, data_) + if rid_-(w_-1) != 0: break - # show the entry - dbg_entry(id, w, rbyd, rid, - name_tag, name_j, name_d, name, - struct_tag, struct_j, struct_d, struct_) + if name is not None: + tags = [name] + [(tag, j, d, data) + for tag, j, d, data in tags + if tag & 0xf00f != TAG_NAME] - ppath = path + # show the branch + dbg_branch(bid, w, rbyd, rid, tags, len(path)-1) if args.get('error_on_corrupt') and corrupted: sys.exit(2) @@ -741,6 +972,10 @@ if __name__ == "__main__": parser.add_argument( '-t', '--tree', action='store_true', + help="Show the underlying rbyd trees.") + parser.add_argument( + '-b', '--btree', + action='store_true', help="Show the underlying B-tree.") parser.add_argument( '-Z', '--depth', diff --git a/scripts/dbgrbyd.py b/scripts/dbgrbyd.py index 5b1028b0..11056c8a 100755 --- a/scripts/dbgrbyd.py +++ b/scripts/dbgrbyd.py @@ -552,32 +552,32 @@ def dbg_tree(data, block_size, rev, trunk, weight, *, upper -= upper-lower-1-w if not alt & 0x4 else 0 j = j - jump - if args.get('tree'): - # figure out which color - if alt & 0x2: - _, nalt, _, _, _ = fromtag(data[j+jump+d:]) - if nalt & 0x2: - path.append((j+jump, j, True, 'y')) - else: - path.append((j+jump, j, True, 'r')) + # figure out which color + if alt & 0x2: + _, nalt, _, _, _ = fromtag(data[j+jump+d:]) + if nalt & 0x2: + path.append((j+jump, j, True, 'y')) else: - path.append((j+jump, j, True, 'b')) + path.append((j+jump, j, True, 'r')) + else: + path.append((j+jump, j, True, 'b')) + # stay on path else: lower += w if not alt & 0x4 else 0 upper -= w if alt & 0x4 else 0 j = j + d - if args.get('tree'): - # figure out which color - if alt & 0x2: - _, nalt, _, _, _ = fromtag(data[j:]) - if nalt & 0x2: - path.append((j-d, j, False, 'y')) - else: - path.append((j-d, j, False, 'r')) + # figure out which color + if alt & 0x2: + _, nalt, _, _, _ = fromtag(data[j:]) + if nalt & 0x2: + path.append((j-d, j, False, 'y')) else: - path.append((j-d, j, False, 'b')) + path.append((j-d, j, False, 'r')) + else: + path.append((j-d, j, False, 'b')) + # found tag else: id_ = upper-1 @@ -589,7 +589,7 @@ def dbg_tree(data, block_size, rev, trunk, weight, *, return done, id_, tag_, w_, j, d, jump, path # precompute tree - tree_width = 0 + t_width = 0 if args.get('tree'): trunks = co.defaultdict(lambda: (-1, 0)) alts = co.defaultdict(lambda: {}) @@ -630,100 +630,110 @@ def dbg_tree(data, block_size, rev, trunk, weight, *, # didn't exist def rec_trunk(j_): if j_ not in alts: - return j_ + return trunks[j_] else: - if 't' not in alts[j_]: - alts[j_]['t'] = rec_trunk(alts[j_]['nf']) - return alts[j_]['t'] + if 'nft' not in alts[j_]: + alts[j_]['nft'] = rec_trunk(alts[j_]['nf']) + return alts[j_]['nft'] for j_ in alts.keys(): rec_trunk(j_) for j_, alt in alts.items(): if alt['f'] in alts: - alt['ft'] = alts[alt['f']]['t'] + alt['ft'] = alts[alt['f']]['nft'] else: - alt['ft'] = alt['f'] + alt['ft'] = trunks[alt['f']] - def rec_depth(j_): + def rec_height(j_): if j_ not in alts: return 0 else: - if 'd' not in alts[j_]: - alts[j_]['d'] = max( - rec_depth(alts[j_]['f']), - rec_depth(alts[j_]['nf'])) + 1 - return alts[j_]['d'] + if 'h' not in alts[j_]: + alts[j_]['h'] = max( + rec_height(alts[j_]['f']), + rec_height(alts[j_]['nf'])) + 1 + return alts[j_]['h'] for j_ in alts.keys(): - rec_depth(j_) + rec_height(j_) - # oh hey this also gives us the max depth - tree_depth = max((alt['d'] for alt in alts.values()), default=0) - if tree_depth > 0: - tree_width = 2*tree_depth + 2 + t_depth = max((alt['h']+1 for alt in alts.values()), default=0) - def treerepr(j): - if tree_depth == 0: + # convert to more general tree representation + tree = [] + for j, alt in alts.items(): + # note all non-trunk edges should be black + tree.append({ + 'a': alt['nft'], + 'b': alt['nft'], + 'd': t_depth-1 - alt['h'], + 'c': alt['c'], + }) + tree.append({ + 'a': alt['nft'], + 'b': alt['ft'], + 'd': t_depth-1 - alt['h'], + 'c': 'b', + }) + + # find the max depth from the tree + t_depth = max((branch['d']+1 for branch in tree), default=0) + if t_depth > 0: + t_width = 2*t_depth + 2 + + def treerepr(id, tag): + if t_depth == 0: return '' - def c(s, c): - return '%s%s%s' % ( - '\x1b[33m' if color and c == 'y' - else '\x1b[31m' if color and c == 'r' - else '\x1b[90m' if color - else '', - s, - '\x1b[m' if color else '') - - trunk = [] - def altrepr(j, x, was=None): - # note all non-trunk edges should be black - for alt in alts.values(): - if alt['d'] == x and alt['t'] == j: - return '+-', alt['c'], alt['c'] - for alt in alts.values(): - if (alt['d'] == x - and alt['ft'] == j - and trunks[j] <= trunks[alt['t']]): - return '.-', 'b', 'b' - for alt in alts.values(): - if (alt['d'] == x - and alt['ft'] == j - and trunks[j] >= trunks[alt['t']]): - return '\'-', 'b', 'b' - for alt in alts.values(): - if (alt['d'] == x - and trunks[j] >= min( - trunks[alt['t']], trunks[alt['ft']]) - and trunks[j] <= max( - trunks[alt['t']], trunks[alt['ft']])): - return '| ', 'b', was + def branchrepr(x, d, was): + for branch in tree: + if branch['d'] == d and branch['b'] == x: + if any(branch['d'] == d and branch['a'] == x + for branch in tree): + return '+-', branch['c'], branch['c'] + elif any(branch['d'] == d + and x > min(branch['a'], branch['b']) + and x < max(branch['a'], branch['b']) + for branch in tree): + return '|-', branch['c'], branch['c'] + elif branch['a'] < branch['b']: + return '\'-', branch['c'], branch['c'] + else: + return '.-', branch['c'], branch['c'] + for branch in tree: + if branch['d'] == d and branch['a'] == x: + return '+ ', branch['c'], None + for branch in tree: + if (branch['d'] == d + and x > min(branch['a'], branch['b']) + and x < max(branch['a'], branch['b'])): + return '| ', branch['c'], was if was: return '--', was, was return ' ', None, None + trunk = [] was = None - for x in reversed(range(1, tree_depth+1)): - t, c, was = altrepr(j, x, was) + for d in range(t_depth): + t, c, was = branchrepr((id, tag), d, was) trunk.append('%s%s%s%s' % ( '\x1b[33m' if color and c == 'y' else '\x1b[31m' if color and c == 'r' - else '\x1b[90m' if color + else '\x1b[90m' if color and c == 'b' else '', t, - ('>' if was else ' ') - if x == 1 else '', - '\x1b[m' if color else '')) + ('>' if was else ' ') if d == t_depth-1 else '', + '\x1b[m' if color and c else '')) - return ' %s' % ''.join(trunk) + return '%s ' % ''.join(trunk) # print header w_width = 2*m.ceil(m.log10(max(1, weight)+1))+1 print('%-8s %*s%-*s %-22s %s' % ( 'off', - tree_width, '', + t_width, '', w_width, 'ids', 'tag', 'data (truncated)' @@ -737,9 +747,9 @@ def dbg_tree(data, block_size, rev, trunk, weight, *, break # show human-readable tag representation - print('%08x:%s %-57s' % ( + print('%08x: %s%-57s' % ( j, - treerepr(j) if args.get('tree') else '', + treerepr(id, tag) if args.get('tree') else '', '%*s %-22s%s' % ( w_width, '%d-%d' % (id-(w-1), id) if w > 1 else id @@ -754,7 +764,7 @@ def dbg_tree(data, block_size, rev, trunk, weight, *, if args.get('device'): print('%8s %*s%*s %s' % ( '', - tree_width, '', + t_width, '', w_width, '', '%-22s%s' % ( '%04x %08x %07x' % (tag, w, size), @@ -770,7 +780,7 @@ def dbg_tree(data, block_size, rev, trunk, weight, *, for o, line in enumerate(xxd(data[j:j+d])): print('%8s: %*s%*s %s' % ( '%04x' % (j + o*16), - tree_width, '', + t_width, '', w_width, '', line)) if args.get('raw') or args.get('no_truncate'): @@ -778,7 +788,7 @@ def dbg_tree(data, block_size, rev, trunk, weight, *, for o, line in enumerate(xxd(data[j+d:j+d+size])): print('%8s: %*s%*s %s' % ( '%04x' % (j+d + o*16), - tree_width, '', + t_width, '', w_width, '', line))