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.
This commit is contained in:
+3
-3
@@ -218,10 +218,10 @@ class SymInfo:
|
|||||||
|
|
||||||
# find sym by range
|
# find sym by range
|
||||||
i = bisect.bisect(self._by_addr, k,
|
i = bisect.bisect(self._by_addr, k,
|
||||||
key=lambda x: x.addr)
|
key=lambda x: x.addr) - 1
|
||||||
# check that we're actually in this sym's size
|
# check that we're actually in this sym's size
|
||||||
if i > 0 and k < self._by_addr[i-1].addr+self._by_addr[i-1].size:
|
if i > -1 and k < self._by_addr[i].addr+self._by_addr[i].size:
|
||||||
return self._by_addr[i-1]
|
return self._by_addr[i]
|
||||||
else:
|
else:
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|||||||
+65
-31
@@ -198,37 +198,54 @@ def dat(x, *args):
|
|||||||
|
|
||||||
# a representation of optionally key-mapped attrs
|
# a representation of optionally key-mapped attrs
|
||||||
class Attr:
|
class Attr:
|
||||||
def __init__(self, attrs, *,
|
def __init__(self, attrs, defaults=None):
|
||||||
defaults=None):
|
if attrs is None:
|
||||||
# include defaults?
|
attrs = []
|
||||||
if (defaults is not None
|
if isinstance(attrs, dict):
|
||||||
and not any(
|
attrs = attrs.items()
|
||||||
not isinstance(attr, tuple)
|
|
||||||
or attr[0] in {None, (), ('*',)}
|
|
||||||
for attr in (attrs or []))):
|
|
||||||
attrs = list(defaults) + (attrs or [])
|
|
||||||
|
|
||||||
# normalize
|
# normalize
|
||||||
self.attrs = []
|
self.attrs = []
|
||||||
self.keyed = co.OrderedDict()
|
self.keyed = co.OrderedDict()
|
||||||
for attr in (attrs or []):
|
for attr in attrs:
|
||||||
if not isinstance(attr, tuple):
|
if (not isinstance(attr, tuple)
|
||||||
|
or attr[0] in {None, (), (None,), ('*',)}):
|
||||||
attr = ((), attr)
|
attr = ((), attr)
|
||||||
elif attr[0] in {None, (), ('*',)}:
|
if not isinstance(attr[0], tuple):
|
||||||
attr = ((), attr[1])
|
attr = ((attr[0],), attr[1])
|
||||||
|
|
||||||
self.attrs.append(attr)
|
self.attrs.append(attr)
|
||||||
if attr[0] not in self.keyed:
|
if attr[0] not in self.keyed:
|
||||||
self.keyed[attr[0]] = []
|
self.keyed[attr[0]] = []
|
||||||
self.keyed[attr[0]].append(attr[1])
|
self.keyed[attr[0]].append(attr[1])
|
||||||
|
|
||||||
|
# create attrs object for defaults
|
||||||
|
if isinstance(defaults, Attr):
|
||||||
|
self.defaults = defaults
|
||||||
|
elif defaults is not None:
|
||||||
|
self.defaults = Attr(defaults)
|
||||||
|
else:
|
||||||
|
self.defaults = None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Attr(%r)' % [
|
if self.defaults is None:
|
||||||
(','.join(attr[0]), attr[1])
|
return 'Attr(%r)' % (
|
||||||
for attr in self.attrs]
|
[(','.join(attr[0]), attr[1])
|
||||||
|
for attr in self.attrs])
|
||||||
|
else:
|
||||||
|
return 'Attr(%r, %r)' % (
|
||||||
|
[(','.join(attr[0]), attr[1])
|
||||||
|
for attr in self.attrs],
|
||||||
|
[(','.join(attr[0]), attr[1])
|
||||||
|
for attr in self.defaults.attrs])
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return it.cycle(self.keyed[()])
|
if () in self.keyed:
|
||||||
|
return it.cycle(self.keyed[()])
|
||||||
|
elif self.defaults is not None:
|
||||||
|
return iter(self.defaults)
|
||||||
|
else:
|
||||||
|
return iter(())
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
return bool(self.attrs)
|
return bool(self.attrs)
|
||||||
@@ -242,6 +259,9 @@ class Attr:
|
|||||||
else:
|
else:
|
||||||
i, key = key, ()
|
i, key = key, ()
|
||||||
|
|
||||||
|
if not isinstance(key, tuple):
|
||||||
|
key = (key,)
|
||||||
|
|
||||||
# try to lookup by key
|
# try to lookup by key
|
||||||
best = None
|
best = None
|
||||||
for ks, vs in self.keyed.items():
|
for ks, vs in self.keyed.items():
|
||||||
@@ -261,6 +281,10 @@ class Attr:
|
|||||||
# cycle based on index
|
# cycle based on index
|
||||||
return best[1][i % len(best[1])]
|
return best[1][i % len(best[1])]
|
||||||
|
|
||||||
|
# fallback to defaults?
|
||||||
|
if self.defaults is not None:
|
||||||
|
return self.defaults[i, key]
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
@@ -268,11 +292,8 @@ class Attr:
|
|||||||
|
|
||||||
# a key function for sorting by key order
|
# a key function for sorting by key order
|
||||||
def key(self, key):
|
def key(self, key):
|
||||||
# allow key to be a tuple to make sorting dicts easier
|
if not isinstance(key, tuple):
|
||||||
if (isinstance(key, tuple)
|
key = (key,)
|
||||||
and len(key) >= 1
|
|
||||||
and isinstance(key[0], tuple)):
|
|
||||||
key = key[0]
|
|
||||||
|
|
||||||
best = None
|
best = None
|
||||||
for i, ks in enumerate(self.keyed.keys()):
|
for i, ks in enumerate(self.keyed.keys()):
|
||||||
@@ -291,6 +312,10 @@ class Attr:
|
|||||||
if best is not None:
|
if best is not None:
|
||||||
return best[1]
|
return best[1]
|
||||||
|
|
||||||
|
# fallback to defaults?
|
||||||
|
if self.defaults is not None:
|
||||||
|
return len(self.keyed) + self.defaults.key(key)
|
||||||
|
|
||||||
return len(self.keyed)
|
return len(self.keyed)
|
||||||
|
|
||||||
# parse %-escaped strings
|
# parse %-escaped strings
|
||||||
@@ -512,8 +537,8 @@ class Canvas:
|
|||||||
|
|
||||||
# a type to represent tiles
|
# a type to represent tiles
|
||||||
class Tile:
|
class Tile:
|
||||||
def __init__(self, key, children,
|
def __init__(self, key, children, *,
|
||||||
x=None, y=None, width=None, height=None, *,
|
x=None, y=None, width=None, height=None,
|
||||||
depth=None,
|
depth=None,
|
||||||
attrs=None,
|
attrs=None,
|
||||||
label=None,
|
label=None,
|
||||||
@@ -536,7 +561,7 @@ class Tile:
|
|||||||
self.color = color
|
self.color = color
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Tile(%r, %r, %r, %r, %r, %r)' % (
|
return 'Tile(%r, %r, x=%r, y=%r, width=%r, height=%r)' % (
|
||||||
','.join(self.key), self.value,
|
','.join(self.key), self.value,
|
||||||
self.x, self.y, self.width, self.height)
|
self.x, self.y, self.width, self.height)
|
||||||
|
|
||||||
@@ -567,6 +592,15 @@ class Tile:
|
|||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
return self.value < other.value
|
return self.value < other.value
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return self.value <= other.value
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self.value > other.value
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return self.value >= other.value
|
||||||
|
|
||||||
# recursive traversals
|
# recursive traversals
|
||||||
def tiles(self):
|
def tiles(self):
|
||||||
yield self
|
yield self
|
||||||
@@ -584,7 +618,7 @@ class Tile:
|
|||||||
for t in self.children:
|
for t in self.children:
|
||||||
t.sort()
|
t.sort()
|
||||||
|
|
||||||
# recursive align to int boundaries
|
# recursive align to pixel boundaries
|
||||||
def align(self):
|
def align(self):
|
||||||
# this extra +0.1 and using points instead of width/height is
|
# this extra +0.1 and using points instead of width/height is
|
||||||
# to help minimize rounding errors
|
# to help minimize rounding errors
|
||||||
@@ -1046,7 +1080,7 @@ def main_(f, paths, *,
|
|||||||
# before tile generation, we want code and stack tiles to have the
|
# before tile generation, we want code and stack tiles to have the
|
||||||
# same color if they're in the same subsystem
|
# same color if they're in the same subsystem
|
||||||
for i, (k, s) in enumerate(subsystems.items()):
|
for i, (k, s) in enumerate(subsystems.items()):
|
||||||
s['color'] = punescape(colors_[i, (k,)], s['attrs'] | s)
|
s['color'] = punescape(colors_[i, k], s['attrs'] | s)
|
||||||
|
|
||||||
|
|
||||||
# build code heirarchy
|
# build code heirarchy
|
||||||
@@ -1062,9 +1096,9 @@ def main_(f, paths, *,
|
|||||||
# assign colors/chars/labels to code tiles
|
# assign colors/chars/labels to code tiles
|
||||||
for i, t in enumerate(code.leaves()):
|
for i, t in enumerate(code.leaves()):
|
||||||
t.color = subsystems[t.attrs['subsystem']]['color']
|
t.color = subsystems[t.attrs['subsystem']]['color']
|
||||||
if (i, (t.attrs['name'],)) in chars_:
|
if (i, t.attrs['name']) in chars_:
|
||||||
t.char = punescape(
|
t.char = punescape(
|
||||||
chars_[i, (t.attrs['name'],)],
|
chars_[i, t.attrs['name']],
|
||||||
t.attrs['attrs'] | t.attrs)[0] # limit to 1 char
|
t.attrs['attrs'] | t.attrs)[0] # limit to 1 char
|
||||||
elif len(t.attrs['subsystem']) < len(t.attrs['name']):
|
elif len(t.attrs['subsystem']) < len(t.attrs['name']):
|
||||||
t.char = (t.attrs['name'][len(t.attrs['subsystem']):].lstrip('_')
|
t.char = (t.attrs['name'][len(t.attrs['subsystem']):].lstrip('_')
|
||||||
@@ -1072,9 +1106,9 @@ def main_(f, paths, *,
|
|||||||
else:
|
else:
|
||||||
t.char = (t.attrs['subsystem'].rstrip('_').rsplit('_', 1)[-1]
|
t.char = (t.attrs['subsystem'].rstrip('_').rsplit('_', 1)[-1]
|
||||||
or '')[0]
|
or '')[0]
|
||||||
if (i, (t.attrs['name'],)) in labels_:
|
if (i, t.attrs['name']) in labels_:
|
||||||
t.label = punescape(
|
t.label = punescape(
|
||||||
labels_[i, (t.attrs['name'],)],
|
labels_[i, t.attrs['name']],
|
||||||
t.attrs['attrs'] | t.attrs)
|
t.attrs['attrs'] | t.attrs)
|
||||||
else:
|
else:
|
||||||
t.label = t.attrs['name']
|
t.label = t.attrs['name']
|
||||||
|
|||||||
+72
-34
@@ -106,37 +106,54 @@ def dat(x, *args):
|
|||||||
|
|
||||||
# a representation of optionally key-mapped attrs
|
# a representation of optionally key-mapped attrs
|
||||||
class Attr:
|
class Attr:
|
||||||
def __init__(self, attrs, *,
|
def __init__(self, attrs, defaults=None):
|
||||||
defaults=None):
|
if attrs is None:
|
||||||
# include defaults?
|
attrs = []
|
||||||
if (defaults is not None
|
if isinstance(attrs, dict):
|
||||||
and not any(
|
attrs = attrs.items()
|
||||||
not isinstance(attr, tuple)
|
|
||||||
or attr[0] in {None, (), ('*',)}
|
|
||||||
for attr in (attrs or []))):
|
|
||||||
attrs = list(defaults) + (attrs or [])
|
|
||||||
|
|
||||||
# normalize
|
# normalize
|
||||||
self.attrs = []
|
self.attrs = []
|
||||||
self.keyed = co.OrderedDict()
|
self.keyed = co.OrderedDict()
|
||||||
for attr in (attrs or []):
|
for attr in attrs:
|
||||||
if not isinstance(attr, tuple):
|
if (not isinstance(attr, tuple)
|
||||||
|
or attr[0] in {None, (), (None,), ('*',)}):
|
||||||
attr = ((), attr)
|
attr = ((), attr)
|
||||||
elif attr[0] in {None, (), ('*',)}:
|
if not isinstance(attr[0], tuple):
|
||||||
attr = ((), attr[1])
|
attr = ((attr[0],), attr[1])
|
||||||
|
|
||||||
self.attrs.append(attr)
|
self.attrs.append(attr)
|
||||||
if attr[0] not in self.keyed:
|
if attr[0] not in self.keyed:
|
||||||
self.keyed[attr[0]] = []
|
self.keyed[attr[0]] = []
|
||||||
self.keyed[attr[0]].append(attr[1])
|
self.keyed[attr[0]].append(attr[1])
|
||||||
|
|
||||||
|
# create attrs object for defaults
|
||||||
|
if isinstance(defaults, Attr):
|
||||||
|
self.defaults = defaults
|
||||||
|
elif defaults is not None:
|
||||||
|
self.defaults = Attr(defaults)
|
||||||
|
else:
|
||||||
|
self.defaults = None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Attr(%r)' % [
|
if self.defaults is None:
|
||||||
(','.join(attr[0]), attr[1])
|
return 'Attr(%r)' % (
|
||||||
for attr in self.attrs]
|
[(','.join(attr[0]), attr[1])
|
||||||
|
for attr in self.attrs])
|
||||||
|
else:
|
||||||
|
return 'Attr(%r, %r)' % (
|
||||||
|
[(','.join(attr[0]), attr[1])
|
||||||
|
for attr in self.attrs],
|
||||||
|
[(','.join(attr[0]), attr[1])
|
||||||
|
for attr in self.defaults.attrs])
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return it.cycle(self.keyed[()])
|
if () in self.keyed:
|
||||||
|
return it.cycle(self.keyed[()])
|
||||||
|
elif self.defaults is not None:
|
||||||
|
return iter(self.defaults)
|
||||||
|
else:
|
||||||
|
return iter(())
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
return bool(self.attrs)
|
return bool(self.attrs)
|
||||||
@@ -150,6 +167,9 @@ class Attr:
|
|||||||
else:
|
else:
|
||||||
i, key = key, ()
|
i, key = key, ()
|
||||||
|
|
||||||
|
if not isinstance(key, tuple):
|
||||||
|
key = (key,)
|
||||||
|
|
||||||
# try to lookup by key
|
# try to lookup by key
|
||||||
best = None
|
best = None
|
||||||
for ks, vs in self.keyed.items():
|
for ks, vs in self.keyed.items():
|
||||||
@@ -169,6 +189,10 @@ class Attr:
|
|||||||
# cycle based on index
|
# cycle based on index
|
||||||
return best[1][i % len(best[1])]
|
return best[1][i % len(best[1])]
|
||||||
|
|
||||||
|
# fallback to defaults?
|
||||||
|
if self.defaults is not None:
|
||||||
|
return self.defaults[i, key]
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
@@ -176,11 +200,8 @@ class Attr:
|
|||||||
|
|
||||||
# a key function for sorting by key order
|
# a key function for sorting by key order
|
||||||
def key(self, key):
|
def key(self, key):
|
||||||
# allow key to be a tuple to make sorting dicts easier
|
if not isinstance(key, tuple):
|
||||||
if (isinstance(key, tuple)
|
key = (key,)
|
||||||
and len(key) >= 1
|
|
||||||
and isinstance(key[0], tuple)):
|
|
||||||
key = key[0]
|
|
||||||
|
|
||||||
best = None
|
best = None
|
||||||
for i, ks in enumerate(self.keyed.keys()):
|
for i, ks in enumerate(self.keyed.keys()):
|
||||||
@@ -199,6 +220,10 @@ class Attr:
|
|||||||
if best is not None:
|
if best is not None:
|
||||||
return best[1]
|
return best[1]
|
||||||
|
|
||||||
|
# fallback to defaults?
|
||||||
|
if self.defaults is not None:
|
||||||
|
return len(self.keyed) + self.defaults.key(key)
|
||||||
|
|
||||||
return len(self.keyed)
|
return len(self.keyed)
|
||||||
|
|
||||||
# parse %-escaped strings
|
# parse %-escaped strings
|
||||||
@@ -248,8 +273,8 @@ def punescape(s, attrs=None):
|
|||||||
|
|
||||||
# a type to represent tiles
|
# a type to represent tiles
|
||||||
class Tile:
|
class Tile:
|
||||||
def __init__(self, key, children,
|
def __init__(self, key, children, *,
|
||||||
x=None, y=None, width=None, height=None, *,
|
x=None, y=None, width=None, height=None,
|
||||||
depth=None,
|
depth=None,
|
||||||
attrs=None,
|
attrs=None,
|
||||||
label=None,
|
label=None,
|
||||||
@@ -272,7 +297,7 @@ class Tile:
|
|||||||
self.color = color
|
self.color = color
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Tile(%r, %r, %r, %r, %r, %r)' % (
|
return 'Tile(%r, %r, x=%r, y=%r, width=%r, height=%r)' % (
|
||||||
','.join(self.key), self.value,
|
','.join(self.key), self.value,
|
||||||
self.x, self.y, self.width, self.height)
|
self.x, self.y, self.width, self.height)
|
||||||
|
|
||||||
@@ -303,6 +328,15 @@ class Tile:
|
|||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
return self.value < other.value
|
return self.value < other.value
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return self.value <= other.value
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self.value > other.value
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return self.value >= other.value
|
||||||
|
|
||||||
# recursive traversals
|
# recursive traversals
|
||||||
def tiles(self):
|
def tiles(self):
|
||||||
yield self
|
yield self
|
||||||
@@ -320,7 +354,7 @@ class Tile:
|
|||||||
for t in self.children:
|
for t in self.children:
|
||||||
t.sort()
|
t.sort()
|
||||||
|
|
||||||
# recursive align to int boundaries
|
# recursive align to pixel boundaries
|
||||||
def align(self):
|
def align(self):
|
||||||
# this extra +0.1 and using points instead of width/height is
|
# this extra +0.1 and using points instead of width/height is
|
||||||
# to help minimize rounding errors
|
# to help minimize rounding errors
|
||||||
@@ -793,7 +827,7 @@ def main(paths, output, *,
|
|||||||
# before tile generation, we want code and stack tiles to have the
|
# before tile generation, we want code and stack tiles to have the
|
||||||
# same color if they're in the same subsystem
|
# same color if they're in the same subsystem
|
||||||
for i, (k, s) in enumerate(subsystems.items()):
|
for i, (k, s) in enumerate(subsystems.items()):
|
||||||
s['color'] = punescape(colors_[i, (k,)], s['attrs'] | s)
|
s['color'] = punescape(colors_[i, k], s['attrs'] | s)
|
||||||
|
|
||||||
|
|
||||||
# build code heirarchy
|
# build code heirarchy
|
||||||
@@ -809,9 +843,9 @@ def main(paths, output, *,
|
|||||||
# assign colors/labels to code tiles
|
# assign colors/labels to code tiles
|
||||||
for i, t in enumerate(code.leaves()):
|
for i, t in enumerate(code.leaves()):
|
||||||
t.color = subsystems[t.attrs['subsystem']]['color']
|
t.color = subsystems[t.attrs['subsystem']]['color']
|
||||||
if (i, (t.attrs['name'],)) in labels_:
|
if (i, t.attrs['name']) in labels_:
|
||||||
t.label = punescape(
|
t.label = punescape(
|
||||||
labels_[i, (t.attrs['name'],)],
|
labels_[i, t.attrs['name']],
|
||||||
t.attrs['attrs'] | t.attrs)
|
t.attrs['attrs'] | t.attrs)
|
||||||
else:
|
else:
|
||||||
t.label = '%s%s%s%s' % (
|
t.label = '%s%s%s%s' % (
|
||||||
@@ -853,9 +887,9 @@ def main(paths, output, *,
|
|||||||
# assign colors/labels to stack tiles
|
# assign colors/labels to stack tiles
|
||||||
for i, t in enumerate(stacks[k].leaves()):
|
for i, t in enumerate(stacks[k].leaves()):
|
||||||
t.color = subsystems[t.attrs['subsystem']]['color']
|
t.color = subsystems[t.attrs['subsystem']]['color']
|
||||||
if (i, (t.attrs['name'],)) in labels_:
|
if (i, t.attrs['name']) in labels_:
|
||||||
t.label = punescape(
|
t.label = punescape(
|
||||||
labels_[i, (t.attrs['name'],)],
|
labels_[i, t.attrs['name']],
|
||||||
t.attrs['attrs'] | t.attrs)
|
t.attrs['attrs'] | t.attrs)
|
||||||
else:
|
else:
|
||||||
t.label = '%s\nframe %d' % (
|
t.label = '%s\nframe %d' % (
|
||||||
@@ -884,9 +918,9 @@ def main(paths, output, *,
|
|||||||
# assign colors/labels to ctx tiles
|
# assign colors/labels to ctx tiles
|
||||||
for i, t in enumerate(ctxs[k].leaves()):
|
for i, t in enumerate(ctxs[k].leaves()):
|
||||||
t.color = subsystems[t.attrs['subsystem']]['color']
|
t.color = subsystems[t.attrs['subsystem']]['color']
|
||||||
if (i, (t.attrs['name'],)) in labels_:
|
if (i, t.attrs['name']) in labels_:
|
||||||
t.label = punescape(
|
t.label = punescape(
|
||||||
labels_[i, (t.attrs['name'],)],
|
labels_[i, t.attrs['name']],
|
||||||
t.attrs['attrs'] | t.attrs)
|
t.attrs['attrs'] | t.attrs)
|
||||||
else:
|
else:
|
||||||
t.label = '%s\nctx %d' % (
|
t.label = '%s\nctx %d' % (
|
||||||
@@ -1124,7 +1158,11 @@ def main(paths, output, *,
|
|||||||
f.write('<tspan id="mode" x="%(x)d" y="1.1em" '
|
f.write('<tspan id="mode" x="%(x)d" y="1.1em" '
|
||||||
'text-anchor="end">' % dict(
|
'text-anchor="end">' % dict(
|
||||||
x=width_-3))
|
x=width_-3))
|
||||||
f.write('mode: callgraph')
|
f.write('mode: %s' % (
|
||||||
|
'callgraph' if mode_callgraph
|
||||||
|
else 'deepest' if mode_deepest
|
||||||
|
else 'callees' if mode_callees
|
||||||
|
else 'callers'))
|
||||||
f.write('</tspan>')
|
f.write('</tspan>')
|
||||||
f.write('</text>')
|
f.write('</text>')
|
||||||
f.write('</g>')
|
f.write('</g>')
|
||||||
|
|||||||
+3
-3
@@ -223,10 +223,10 @@ class SymInfo:
|
|||||||
|
|
||||||
# find sym by range
|
# find sym by range
|
||||||
i = bisect.bisect(self._by_addr, k,
|
i = bisect.bisect(self._by_addr, k,
|
||||||
key=lambda x: x.addr)
|
key=lambda x: x.addr) - 1
|
||||||
# check that we're actually in this sym's size
|
# check that we're actually in this sym's size
|
||||||
if i > 0 and k < self._by_addr[i-1].addr+self._by_addr[i-1].size:
|
if i > -1 and k < self._by_addr[i].addr+self._by_addr[i].size:
|
||||||
return self._by_addr[i-1]
|
return self._by_addr[i]
|
||||||
else:
|
else:
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -218,10 +218,10 @@ class SymInfo:
|
|||||||
|
|
||||||
# find sym by range
|
# find sym by range
|
||||||
i = bisect.bisect(self._by_addr, k,
|
i = bisect.bisect(self._by_addr, k,
|
||||||
key=lambda x: x.addr)
|
key=lambda x: x.addr) - 1
|
||||||
# check that we're actually in this sym's size
|
# check that we're actually in this sym's size
|
||||||
if i > 0 and k < self._by_addr[i-1].addr+self._by_addr[i-1].size:
|
if i > -1 and k < self._by_addr[i].addr+self._by_addr[i].size:
|
||||||
return self._by_addr[i-1]
|
return self._by_addr[i]
|
||||||
else:
|
else:
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -140,9 +140,13 @@ def main(disk, blocks=None, *,
|
|||||||
|
|
||||||
# hexdump the blocks
|
# hexdump the blocks
|
||||||
for block, off in zip(blocks, offs):
|
for block, off in zip(blocks, offs):
|
||||||
|
# bound to block_size
|
||||||
block_ = block if block is not None else 0
|
block_ = block if block is not None else 0
|
||||||
off_ = off if off is not None else 0
|
off_ = off if off is not None else 0
|
||||||
size_ = size if size is not None else block_size - off_
|
size_ = size if size is not None else block_size - off_
|
||||||
|
if off_ >= block_size:
|
||||||
|
continue
|
||||||
|
size_ = min(off_ + size_, block_size) - off_
|
||||||
|
|
||||||
# read the block
|
# read the block
|
||||||
f.seek((block_ * block_size) + off_)
|
f.seek((block_ * block_size) + off_)
|
||||||
@@ -185,7 +189,7 @@ if __name__ == "__main__":
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--block-size',
|
'-b', '--block-size',
|
||||||
type=bdgeom,
|
type=bdgeom,
|
||||||
help="Block size/geometry in bytes.")
|
help="Block size/geometry in bytes. Accepts <size>x<count>.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--block-count',
|
'--block-count',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
|
|||||||
+1
-1
@@ -1420,7 +1420,7 @@ if __name__ == "__main__":
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--block-size',
|
'-b', '--block-size',
|
||||||
type=bdgeom,
|
type=bdgeom,
|
||||||
help="Block size/geometry in bytes.")
|
help="Block size/geometry in bytes. Accepts <size>x<count>.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--block-count',
|
'--block-count',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
|
|||||||
Executable
+5906
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1831,7 +1831,7 @@ if __name__ == "__main__":
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--block-size',
|
'-b', '--block-size',
|
||||||
type=bdgeom,
|
type=bdgeom,
|
||||||
help="Block size/geometry in bytes.")
|
help="Block size/geometry in bytes. Accepts <size>x<count>.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--block-count',
|
'--block-count',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
|
|||||||
+4
-1
@@ -144,6 +144,9 @@ def main(disk, blocks=None, *,
|
|||||||
block_ = block if block is not None else 0
|
block_ = block if block is not None else 0
|
||||||
off_ = off if off is not None else 0
|
off_ = off if off is not None else 0
|
||||||
size_ = size if size is not None else block_size - off_
|
size_ = size if size is not None else block_size - off_
|
||||||
|
if off_ >= block_size:
|
||||||
|
continue
|
||||||
|
size_ = min(off_ + size_, block_size) - off_
|
||||||
|
|
||||||
# cat the block
|
# cat the block
|
||||||
f.seek((block_ * block_size) + off_)
|
f.seek((block_ * block_size) + off_)
|
||||||
@@ -174,7 +177,7 @@ if __name__ == "__main__":
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--block-size',
|
'-b', '--block-size',
|
||||||
type=bdgeom,
|
type=bdgeom,
|
||||||
help="Block size/geometry in bytes.")
|
help="Block size/geometry in bytes. Accepts <size>x<count>.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--block-count',
|
'--block-count',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
|
|||||||
+1
-1
@@ -4514,7 +4514,7 @@ if __name__ == "__main__":
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--block-size',
|
'-b', '--block-size',
|
||||||
type=bdgeom,
|
type=bdgeom,
|
||||||
help="Block size/geometry in bytes.")
|
help="Block size/geometry in bytes. Accepts <size>x<count>.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--block-count',
|
'--block-count',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
|
|||||||
+1
-1
@@ -3047,7 +3047,7 @@ if __name__ == "__main__":
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--block-size',
|
'-b', '--block-size',
|
||||||
type=bdgeom,
|
type=bdgeom,
|
||||||
help="Block size/geometry in bytes.")
|
help="Block size/geometry in bytes. Accepts <size>x<count>.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--block-count',
|
'--block-count',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
|
|||||||
+1
-1
@@ -1753,7 +1753,7 @@ if __name__ == "__main__":
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--block-size',
|
'-b', '--block-size',
|
||||||
type=bdgeom,
|
type=bdgeom,
|
||||||
help="Block size/geometry in bytes.")
|
help="Block size/geometry in bytes. Accepts <size>x<count>.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--block-count',
|
'--block-count',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
|
|||||||
+9
-9
@@ -320,10 +320,10 @@ class SymInfo:
|
|||||||
|
|
||||||
# find sym by range
|
# find sym by range
|
||||||
i = bisect.bisect(self._by_addr, k,
|
i = bisect.bisect(self._by_addr, k,
|
||||||
key=lambda x: x.addr)
|
key=lambda x: x.addr) - 1
|
||||||
# check that we're actually in this sym's size
|
# check that we're actually in this sym's size
|
||||||
if i > 0 and k < self._by_addr[i-1].addr+self._by_addr[i-1].size:
|
if i > -1 and k < self._by_addr[i].addr+self._by_addr[i].size:
|
||||||
return self._by_addr[i-1]
|
return self._by_addr[i]
|
||||||
else:
|
else:
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@@ -444,9 +444,9 @@ class LineInfo:
|
|||||||
|
|
||||||
# find file+line by addr
|
# find file+line by addr
|
||||||
i = bisect.bisect(self._by_addr, k,
|
i = bisect.bisect(self._by_addr, k,
|
||||||
key=lambda x: x.addr)
|
key=lambda x: x.addr) - 1
|
||||||
if i > 0:
|
if i > -1:
|
||||||
return self._by_addr[i-1]
|
return self._by_addr[i]
|
||||||
else:
|
else:
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@@ -469,10 +469,10 @@ class LineInfo:
|
|||||||
|
|
||||||
# find addr by file+line tuple
|
# find addr by file+line tuple
|
||||||
i = bisect.bisect(self._by_line, k,
|
i = bisect.bisect(self._by_line, k,
|
||||||
key=lambda x: (x.file, x.line))
|
key=lambda x: (x.file, x.line)) - 1
|
||||||
# make sure file at least matches!
|
# make sure file at least matches!
|
||||||
if i > 0 and self._by_line[i-1].file == k[0]:
|
if i > -1 and self._by_line[i].file == k[0]:
|
||||||
return self._by_line[i-1]
|
return self._by_line[i]
|
||||||
else:
|
else:
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|||||||
+9
-9
@@ -232,10 +232,10 @@ class SymInfo:
|
|||||||
|
|
||||||
# find sym by range
|
# find sym by range
|
||||||
i = bisect.bisect(self._by_addr, k,
|
i = bisect.bisect(self._by_addr, k,
|
||||||
key=lambda x: x.addr)
|
key=lambda x: x.addr) - 1
|
||||||
# check that we're actually in this sym's size
|
# check that we're actually in this sym's size
|
||||||
if i > 0 and k < self._by_addr[i-1].addr+self._by_addr[i-1].size:
|
if i > -1 and k < self._by_addr[i].addr+self._by_addr[i].size:
|
||||||
return self._by_addr[i-1]
|
return self._by_addr[i]
|
||||||
else:
|
else:
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@@ -355,9 +355,9 @@ class LineInfo:
|
|||||||
|
|
||||||
# find file+line by addr
|
# find file+line by addr
|
||||||
i = bisect.bisect(self._by_addr, k,
|
i = bisect.bisect(self._by_addr, k,
|
||||||
key=lambda x: x.addr)
|
key=lambda x: x.addr) - 1
|
||||||
if i > 0:
|
if i > -1:
|
||||||
return self._by_addr[i-1]
|
return self._by_addr[i]
|
||||||
else:
|
else:
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@@ -380,10 +380,10 @@ class LineInfo:
|
|||||||
|
|
||||||
# find addr by file+line tuple
|
# find addr by file+line tuple
|
||||||
i = bisect.bisect(self._by_line, k,
|
i = bisect.bisect(self._by_line, k,
|
||||||
key=lambda x: (x.file, x.line))
|
key=lambda x: (x.file, x.line)) - 1
|
||||||
# make sure file at least matches!
|
# make sure file at least matches!
|
||||||
if i > 0 and self._by_line[i-1].file == k[0]:
|
if i > -1 and self._by_line[i].file == k[0]:
|
||||||
return self._by_line[i-1]
|
return self._by_line[i]
|
||||||
else:
|
else:
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|||||||
+49
-24
@@ -375,37 +375,54 @@ def fold(results, by=None, x=None, y=None, defines=[]):
|
|||||||
|
|
||||||
# a representation of optionally key-mapped attrs
|
# a representation of optionally key-mapped attrs
|
||||||
class Attr:
|
class Attr:
|
||||||
def __init__(self, attrs, *,
|
def __init__(self, attrs, defaults=None):
|
||||||
defaults=None):
|
if attrs is None:
|
||||||
# include defaults?
|
attrs = []
|
||||||
if (defaults is not None
|
if isinstance(attrs, dict):
|
||||||
and not any(
|
attrs = attrs.items()
|
||||||
not isinstance(attr, tuple)
|
|
||||||
or attr[0] in {None, (), ('*',)}
|
|
||||||
for attr in (attrs or []))):
|
|
||||||
attrs = list(defaults) + (attrs or [])
|
|
||||||
|
|
||||||
# normalize
|
# normalize
|
||||||
self.attrs = []
|
self.attrs = []
|
||||||
self.keyed = co.OrderedDict()
|
self.keyed = co.OrderedDict()
|
||||||
for attr in (attrs or []):
|
for attr in attrs:
|
||||||
if not isinstance(attr, tuple):
|
if (not isinstance(attr, tuple)
|
||||||
|
or attr[0] in {None, (), (None,), ('*',)}):
|
||||||
attr = ((), attr)
|
attr = ((), attr)
|
||||||
elif attr[0] in {None, (), ('*',)}:
|
if not isinstance(attr[0], tuple):
|
||||||
attr = ((), attr[1])
|
attr = ((attr[0],), attr[1])
|
||||||
|
|
||||||
self.attrs.append(attr)
|
self.attrs.append(attr)
|
||||||
if attr[0] not in self.keyed:
|
if attr[0] not in self.keyed:
|
||||||
self.keyed[attr[0]] = []
|
self.keyed[attr[0]] = []
|
||||||
self.keyed[attr[0]].append(attr[1])
|
self.keyed[attr[0]].append(attr[1])
|
||||||
|
|
||||||
|
# create attrs object for defaults
|
||||||
|
if isinstance(defaults, Attr):
|
||||||
|
self.defaults = defaults
|
||||||
|
elif defaults is not None:
|
||||||
|
self.defaults = Attr(defaults)
|
||||||
|
else:
|
||||||
|
self.defaults = None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Attr(%r)' % [
|
if self.defaults is None:
|
||||||
(','.join(attr[0]), attr[1])
|
return 'Attr(%r)' % (
|
||||||
for attr in self.attrs]
|
[(','.join(attr[0]), attr[1])
|
||||||
|
for attr in self.attrs])
|
||||||
|
else:
|
||||||
|
return 'Attr(%r, %r)' % (
|
||||||
|
[(','.join(attr[0]), attr[1])
|
||||||
|
for attr in self.attrs],
|
||||||
|
[(','.join(attr[0]), attr[1])
|
||||||
|
for attr in self.defaults.attrs])
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return it.cycle(self.keyed[()])
|
if () in self.keyed:
|
||||||
|
return it.cycle(self.keyed[()])
|
||||||
|
elif self.defaults is not None:
|
||||||
|
return iter(self.defaults)
|
||||||
|
else:
|
||||||
|
return iter(())
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
return bool(self.attrs)
|
return bool(self.attrs)
|
||||||
@@ -419,6 +436,9 @@ class Attr:
|
|||||||
else:
|
else:
|
||||||
i, key = key, ()
|
i, key = key, ()
|
||||||
|
|
||||||
|
if not isinstance(key, tuple):
|
||||||
|
key = (key,)
|
||||||
|
|
||||||
# try to lookup by key
|
# try to lookup by key
|
||||||
best = None
|
best = None
|
||||||
for ks, vs in self.keyed.items():
|
for ks, vs in self.keyed.items():
|
||||||
@@ -438,6 +458,10 @@ class Attr:
|
|||||||
# cycle based on index
|
# cycle based on index
|
||||||
return best[1][i % len(best[1])]
|
return best[1][i % len(best[1])]
|
||||||
|
|
||||||
|
# fallback to defaults?
|
||||||
|
if self.defaults is not None:
|
||||||
|
return self.defaults[i, key]
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
@@ -445,11 +469,8 @@ class Attr:
|
|||||||
|
|
||||||
# a key function for sorting by key order
|
# a key function for sorting by key order
|
||||||
def key(self, key):
|
def key(self, key):
|
||||||
# allow key to be a tuple to make sorting dicts easier
|
if not isinstance(key, tuple):
|
||||||
if (isinstance(key, tuple)
|
key = (key,)
|
||||||
and len(key) >= 1
|
|
||||||
and isinstance(key[0], tuple)):
|
|
||||||
key = key[0]
|
|
||||||
|
|
||||||
best = None
|
best = None
|
||||||
for i, ks in enumerate(self.keyed.keys()):
|
for i, ks in enumerate(self.keyed.keys()):
|
||||||
@@ -468,6 +489,10 @@ class Attr:
|
|||||||
if best is not None:
|
if best is not None:
|
||||||
return best[1]
|
return best[1]
|
||||||
|
|
||||||
|
# fallback to defaults?
|
||||||
|
if self.defaults is not None:
|
||||||
|
return len(self.keyed) + self.defaults.key(key)
|
||||||
|
|
||||||
return len(self.keyed)
|
return len(self.keyed)
|
||||||
|
|
||||||
# parse %-escaped strings
|
# parse %-escaped strings
|
||||||
@@ -1278,7 +1303,7 @@ def main_(f, csv_paths, *,
|
|||||||
# order by labels
|
# order by labels
|
||||||
datasets_ = co.OrderedDict(sorted(
|
datasets_ = co.OrderedDict(sorted(
|
||||||
datasets_.items(),
|
datasets_.items(),
|
||||||
key=labels_.key))
|
key=lambda kv: labels_.key(kv[0])))
|
||||||
|
|
||||||
# and merge dataattrs
|
# and merge dataattrs
|
||||||
mergedattrs_ = {k: v
|
mergedattrs_ = {k: v
|
||||||
@@ -1455,7 +1480,7 @@ def main_(f, csv_paths, *,
|
|||||||
# order by labels
|
# order by labels
|
||||||
subdatasets = co.OrderedDict(sorted(
|
subdatasets = co.OrderedDict(sorted(
|
||||||
subdatasets.items(),
|
subdatasets.items(),
|
||||||
key=labels_.key))
|
key=lambda kv: labels_.key(kv[0])))
|
||||||
|
|
||||||
# filter by subplot x/y
|
# filter by subplot x/y
|
||||||
subdatasets = co.OrderedDict([(name, dataset)
|
subdatasets = co.OrderedDict([(name, dataset)
|
||||||
|
|||||||
+49
-24
@@ -298,37 +298,54 @@ def fold(results, by=None, x=None, y=None, defines=[]):
|
|||||||
|
|
||||||
# a representation of optionally key-mapped attrs
|
# a representation of optionally key-mapped attrs
|
||||||
class Attr:
|
class Attr:
|
||||||
def __init__(self, attrs, *,
|
def __init__(self, attrs, defaults=None):
|
||||||
defaults=None):
|
if attrs is None:
|
||||||
# include defaults?
|
attrs = []
|
||||||
if (defaults is not None
|
if isinstance(attrs, dict):
|
||||||
and not any(
|
attrs = attrs.items()
|
||||||
not isinstance(attr, tuple)
|
|
||||||
or attr[0] in {None, (), ('*',)}
|
|
||||||
for attr in (attrs or []))):
|
|
||||||
attrs = list(defaults) + (attrs or [])
|
|
||||||
|
|
||||||
# normalize
|
# normalize
|
||||||
self.attrs = []
|
self.attrs = []
|
||||||
self.keyed = co.OrderedDict()
|
self.keyed = co.OrderedDict()
|
||||||
for attr in (attrs or []):
|
for attr in attrs:
|
||||||
if not isinstance(attr, tuple):
|
if (not isinstance(attr, tuple)
|
||||||
|
or attr[0] in {None, (), (None,), ('*',)}):
|
||||||
attr = ((), attr)
|
attr = ((), attr)
|
||||||
elif attr[0] in {None, (), ('*',)}:
|
if not isinstance(attr[0], tuple):
|
||||||
attr = ((), attr[1])
|
attr = ((attr[0],), attr[1])
|
||||||
|
|
||||||
self.attrs.append(attr)
|
self.attrs.append(attr)
|
||||||
if attr[0] not in self.keyed:
|
if attr[0] not in self.keyed:
|
||||||
self.keyed[attr[0]] = []
|
self.keyed[attr[0]] = []
|
||||||
self.keyed[attr[0]].append(attr[1])
|
self.keyed[attr[0]].append(attr[1])
|
||||||
|
|
||||||
|
# create attrs object for defaults
|
||||||
|
if isinstance(defaults, Attr):
|
||||||
|
self.defaults = defaults
|
||||||
|
elif defaults is not None:
|
||||||
|
self.defaults = Attr(defaults)
|
||||||
|
else:
|
||||||
|
self.defaults = None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Attr(%r)' % [
|
if self.defaults is None:
|
||||||
(','.join(attr[0]), attr[1])
|
return 'Attr(%r)' % (
|
||||||
for attr in self.attrs]
|
[(','.join(attr[0]), attr[1])
|
||||||
|
for attr in self.attrs])
|
||||||
|
else:
|
||||||
|
return 'Attr(%r, %r)' % (
|
||||||
|
[(','.join(attr[0]), attr[1])
|
||||||
|
for attr in self.attrs],
|
||||||
|
[(','.join(attr[0]), attr[1])
|
||||||
|
for attr in self.defaults.attrs])
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return it.cycle(self.keyed[()])
|
if () in self.keyed:
|
||||||
|
return it.cycle(self.keyed[()])
|
||||||
|
elif self.defaults is not None:
|
||||||
|
return iter(self.defaults)
|
||||||
|
else:
|
||||||
|
return iter(())
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
return bool(self.attrs)
|
return bool(self.attrs)
|
||||||
@@ -342,6 +359,9 @@ class Attr:
|
|||||||
else:
|
else:
|
||||||
i, key = key, ()
|
i, key = key, ()
|
||||||
|
|
||||||
|
if not isinstance(key, tuple):
|
||||||
|
key = (key,)
|
||||||
|
|
||||||
# try to lookup by key
|
# try to lookup by key
|
||||||
best = None
|
best = None
|
||||||
for ks, vs in self.keyed.items():
|
for ks, vs in self.keyed.items():
|
||||||
@@ -361,6 +381,10 @@ class Attr:
|
|||||||
# cycle based on index
|
# cycle based on index
|
||||||
return best[1][i % len(best[1])]
|
return best[1][i % len(best[1])]
|
||||||
|
|
||||||
|
# fallback to defaults?
|
||||||
|
if self.defaults is not None:
|
||||||
|
return self.defaults[i, key]
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
@@ -368,11 +392,8 @@ class Attr:
|
|||||||
|
|
||||||
# a key function for sorting by key order
|
# a key function for sorting by key order
|
||||||
def key(self, key):
|
def key(self, key):
|
||||||
# allow key to be a tuple to make sorting dicts easier
|
if not isinstance(key, tuple):
|
||||||
if (isinstance(key, tuple)
|
key = (key,)
|
||||||
and len(key) >= 1
|
|
||||||
and isinstance(key[0], tuple)):
|
|
||||||
key = key[0]
|
|
||||||
|
|
||||||
best = None
|
best = None
|
||||||
for i, ks in enumerate(self.keyed.keys()):
|
for i, ks in enumerate(self.keyed.keys()):
|
||||||
@@ -391,6 +412,10 @@ class Attr:
|
|||||||
if best is not None:
|
if best is not None:
|
||||||
return best[1]
|
return best[1]
|
||||||
|
|
||||||
|
# fallback to defaults?
|
||||||
|
if self.defaults is not None:
|
||||||
|
return len(self.keyed) + self.defaults.key(key)
|
||||||
|
|
||||||
return len(self.keyed)
|
return len(self.keyed)
|
||||||
|
|
||||||
# parse %-escaped strings
|
# parse %-escaped strings
|
||||||
@@ -882,7 +907,7 @@ def main(csv_paths, output, *,
|
|||||||
# order by labels
|
# order by labels
|
||||||
datasets_ = co.OrderedDict(sorted(
|
datasets_ = co.OrderedDict(sorted(
|
||||||
datasets_.items(),
|
datasets_.items(),
|
||||||
key=labels_.key))
|
key=lambda kv: labels_.key(kv[0])))
|
||||||
|
|
||||||
# and merge dataattrs
|
# and merge dataattrs
|
||||||
mergedattrs_ = {k: v
|
mergedattrs_ = {k: v
|
||||||
@@ -969,7 +994,7 @@ def main(csv_paths, output, *,
|
|||||||
# order by labels
|
# order by labels
|
||||||
subdatasets = co.OrderedDict(sorted(
|
subdatasets = co.OrderedDict(sorted(
|
||||||
subdatasets.items(),
|
subdatasets.items(),
|
||||||
key=labels_.key))
|
key=lambda kv: labels_.key(kv[0])))
|
||||||
|
|
||||||
# filter by subplot x/y
|
# filter by subplot x/y
|
||||||
subdatasets = co.OrderedDict([(name, dataset)
|
subdatasets = co.OrderedDict([(name, dataset)
|
||||||
|
|||||||
+1
-1
@@ -1081,7 +1081,7 @@ if __name__ == "__main__":
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--block-size',
|
'-b', '--block-size',
|
||||||
type=bdgeom,
|
type=bdgeom,
|
||||||
help="Block size/geometry in bytes.")
|
help="Block size/geometry in bytes. Accepts <size>x<count>.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--block-count',
|
'--block-count',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
|
|||||||
+60
-26
@@ -267,37 +267,54 @@ def fold(results, by=None, fields=None, defines=[]):
|
|||||||
|
|
||||||
# a representation of optionally key-mapped attrs
|
# a representation of optionally key-mapped attrs
|
||||||
class Attr:
|
class Attr:
|
||||||
def __init__(self, attrs, *,
|
def __init__(self, attrs, defaults=None):
|
||||||
defaults=None):
|
if attrs is None:
|
||||||
# include defaults?
|
attrs = []
|
||||||
if (defaults is not None
|
if isinstance(attrs, dict):
|
||||||
and not any(
|
attrs = attrs.items()
|
||||||
not isinstance(attr, tuple)
|
|
||||||
or attr[0] in {None, (), ('*',)}
|
|
||||||
for attr in (attrs or []))):
|
|
||||||
attrs = list(defaults) + (attrs or [])
|
|
||||||
|
|
||||||
# normalize
|
# normalize
|
||||||
self.attrs = []
|
self.attrs = []
|
||||||
self.keyed = co.OrderedDict()
|
self.keyed = co.OrderedDict()
|
||||||
for attr in (attrs or []):
|
for attr in attrs:
|
||||||
if not isinstance(attr, tuple):
|
if (not isinstance(attr, tuple)
|
||||||
|
or attr[0] in {None, (), (None,), ('*',)}):
|
||||||
attr = ((), attr)
|
attr = ((), attr)
|
||||||
elif attr[0] in {None, (), ('*',)}:
|
if not isinstance(attr[0], tuple):
|
||||||
attr = ((), attr[1])
|
attr = ((attr[0],), attr[1])
|
||||||
|
|
||||||
self.attrs.append(attr)
|
self.attrs.append(attr)
|
||||||
if attr[0] not in self.keyed:
|
if attr[0] not in self.keyed:
|
||||||
self.keyed[attr[0]] = []
|
self.keyed[attr[0]] = []
|
||||||
self.keyed[attr[0]].append(attr[1])
|
self.keyed[attr[0]].append(attr[1])
|
||||||
|
|
||||||
|
# create attrs object for defaults
|
||||||
|
if isinstance(defaults, Attr):
|
||||||
|
self.defaults = defaults
|
||||||
|
elif defaults is not None:
|
||||||
|
self.defaults = Attr(defaults)
|
||||||
|
else:
|
||||||
|
self.defaults = None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Attr(%r)' % [
|
if self.defaults is None:
|
||||||
(','.join(attr[0]), attr[1])
|
return 'Attr(%r)' % (
|
||||||
for attr in self.attrs]
|
[(','.join(attr[0]), attr[1])
|
||||||
|
for attr in self.attrs])
|
||||||
|
else:
|
||||||
|
return 'Attr(%r, %r)' % (
|
||||||
|
[(','.join(attr[0]), attr[1])
|
||||||
|
for attr in self.attrs],
|
||||||
|
[(','.join(attr[0]), attr[1])
|
||||||
|
for attr in self.defaults.attrs])
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return it.cycle(self.keyed[()])
|
if () in self.keyed:
|
||||||
|
return it.cycle(self.keyed[()])
|
||||||
|
elif self.defaults is not None:
|
||||||
|
return iter(self.defaults)
|
||||||
|
else:
|
||||||
|
return iter(())
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
return bool(self.attrs)
|
return bool(self.attrs)
|
||||||
@@ -311,6 +328,9 @@ class Attr:
|
|||||||
else:
|
else:
|
||||||
i, key = key, ()
|
i, key = key, ()
|
||||||
|
|
||||||
|
if not isinstance(key, tuple):
|
||||||
|
key = (key,)
|
||||||
|
|
||||||
# try to lookup by key
|
# try to lookup by key
|
||||||
best = None
|
best = None
|
||||||
for ks, vs in self.keyed.items():
|
for ks, vs in self.keyed.items():
|
||||||
@@ -330,6 +350,10 @@ class Attr:
|
|||||||
# cycle based on index
|
# cycle based on index
|
||||||
return best[1][i % len(best[1])]
|
return best[1][i % len(best[1])]
|
||||||
|
|
||||||
|
# fallback to defaults?
|
||||||
|
if self.defaults is not None:
|
||||||
|
return self.defaults[i, key]
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
@@ -337,11 +361,8 @@ class Attr:
|
|||||||
|
|
||||||
# a key function for sorting by key order
|
# a key function for sorting by key order
|
||||||
def key(self, key):
|
def key(self, key):
|
||||||
# allow key to be a tuple to make sorting dicts easier
|
if not isinstance(key, tuple):
|
||||||
if (isinstance(key, tuple)
|
key = (key,)
|
||||||
and len(key) >= 1
|
|
||||||
and isinstance(key[0], tuple)):
|
|
||||||
key = key[0]
|
|
||||||
|
|
||||||
best = None
|
best = None
|
||||||
for i, ks in enumerate(self.keyed.keys()):
|
for i, ks in enumerate(self.keyed.keys()):
|
||||||
@@ -360,6 +381,10 @@ class Attr:
|
|||||||
if best is not None:
|
if best is not None:
|
||||||
return best[1]
|
return best[1]
|
||||||
|
|
||||||
|
# fallback to defaults?
|
||||||
|
if self.defaults is not None:
|
||||||
|
return len(self.keyed) + self.defaults.key(key)
|
||||||
|
|
||||||
return len(self.keyed)
|
return len(self.keyed)
|
||||||
|
|
||||||
# parse %-escaped strings
|
# parse %-escaped strings
|
||||||
@@ -581,8 +606,8 @@ class Canvas:
|
|||||||
|
|
||||||
# a type to represent tiles
|
# a type to represent tiles
|
||||||
class Tile:
|
class Tile:
|
||||||
def __init__(self, key, children,
|
def __init__(self, key, children, *,
|
||||||
x=None, y=None, width=None, height=None, *,
|
x=None, y=None, width=None, height=None,
|
||||||
depth=None,
|
depth=None,
|
||||||
attrs=None,
|
attrs=None,
|
||||||
label=None,
|
label=None,
|
||||||
@@ -605,7 +630,7 @@ class Tile:
|
|||||||
self.color = color
|
self.color = color
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Tile(%r, %r, %r, %r, %r, %r)' % (
|
return 'Tile(%r, %r, x=%r, y=%r, width=%r, height=%r)' % (
|
||||||
','.join(self.key), self.value,
|
','.join(self.key), self.value,
|
||||||
self.x, self.y, self.width, self.height)
|
self.x, self.y, self.width, self.height)
|
||||||
|
|
||||||
@@ -636,6 +661,15 @@ class Tile:
|
|||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
return self.value < other.value
|
return self.value < other.value
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return self.value <= other.value
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self.value > other.value
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return self.value >= other.value
|
||||||
|
|
||||||
# recursive traversals
|
# recursive traversals
|
||||||
def tiles(self):
|
def tiles(self):
|
||||||
yield self
|
yield self
|
||||||
@@ -653,7 +687,7 @@ class Tile:
|
|||||||
for t in self.children:
|
for t in self.children:
|
||||||
t.sort()
|
t.sort()
|
||||||
|
|
||||||
# recursive align to int boundaries
|
# recursive align to pixel boundaries
|
||||||
def align(self):
|
def align(self):
|
||||||
# this extra +0.1 and using points instead of width/height is
|
# this extra +0.1 and using points instead of width/height is
|
||||||
# to help minimize rounding errors
|
# to help minimize rounding errors
|
||||||
|
|||||||
+60
-26
@@ -174,37 +174,54 @@ def fold(results, by=None, fields=None, defines=[]):
|
|||||||
|
|
||||||
# a representation of optionally key-mapped attrs
|
# a representation of optionally key-mapped attrs
|
||||||
class Attr:
|
class Attr:
|
||||||
def __init__(self, attrs, *,
|
def __init__(self, attrs, defaults=None):
|
||||||
defaults=None):
|
if attrs is None:
|
||||||
# include defaults?
|
attrs = []
|
||||||
if (defaults is not None
|
if isinstance(attrs, dict):
|
||||||
and not any(
|
attrs = attrs.items()
|
||||||
not isinstance(attr, tuple)
|
|
||||||
or attr[0] in {None, (), ('*',)}
|
|
||||||
for attr in (attrs or []))):
|
|
||||||
attrs = list(defaults) + (attrs or [])
|
|
||||||
|
|
||||||
# normalize
|
# normalize
|
||||||
self.attrs = []
|
self.attrs = []
|
||||||
self.keyed = co.OrderedDict()
|
self.keyed = co.OrderedDict()
|
||||||
for attr in (attrs or []):
|
for attr in attrs:
|
||||||
if not isinstance(attr, tuple):
|
if (not isinstance(attr, tuple)
|
||||||
|
or attr[0] in {None, (), (None,), ('*',)}):
|
||||||
attr = ((), attr)
|
attr = ((), attr)
|
||||||
elif attr[0] in {None, (), ('*',)}:
|
if not isinstance(attr[0], tuple):
|
||||||
attr = ((), attr[1])
|
attr = ((attr[0],), attr[1])
|
||||||
|
|
||||||
self.attrs.append(attr)
|
self.attrs.append(attr)
|
||||||
if attr[0] not in self.keyed:
|
if attr[0] not in self.keyed:
|
||||||
self.keyed[attr[0]] = []
|
self.keyed[attr[0]] = []
|
||||||
self.keyed[attr[0]].append(attr[1])
|
self.keyed[attr[0]].append(attr[1])
|
||||||
|
|
||||||
|
# create attrs object for defaults
|
||||||
|
if isinstance(defaults, Attr):
|
||||||
|
self.defaults = defaults
|
||||||
|
elif defaults is not None:
|
||||||
|
self.defaults = Attr(defaults)
|
||||||
|
else:
|
||||||
|
self.defaults = None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Attr(%r)' % [
|
if self.defaults is None:
|
||||||
(','.join(attr[0]), attr[1])
|
return 'Attr(%r)' % (
|
||||||
for attr in self.attrs]
|
[(','.join(attr[0]), attr[1])
|
||||||
|
for attr in self.attrs])
|
||||||
|
else:
|
||||||
|
return 'Attr(%r, %r)' % (
|
||||||
|
[(','.join(attr[0]), attr[1])
|
||||||
|
for attr in self.attrs],
|
||||||
|
[(','.join(attr[0]), attr[1])
|
||||||
|
for attr in self.defaults.attrs])
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return it.cycle(self.keyed[()])
|
if () in self.keyed:
|
||||||
|
return it.cycle(self.keyed[()])
|
||||||
|
elif self.defaults is not None:
|
||||||
|
return iter(self.defaults)
|
||||||
|
else:
|
||||||
|
return iter(())
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
return bool(self.attrs)
|
return bool(self.attrs)
|
||||||
@@ -218,6 +235,9 @@ class Attr:
|
|||||||
else:
|
else:
|
||||||
i, key = key, ()
|
i, key = key, ()
|
||||||
|
|
||||||
|
if not isinstance(key, tuple):
|
||||||
|
key = (key,)
|
||||||
|
|
||||||
# try to lookup by key
|
# try to lookup by key
|
||||||
best = None
|
best = None
|
||||||
for ks, vs in self.keyed.items():
|
for ks, vs in self.keyed.items():
|
||||||
@@ -237,6 +257,10 @@ class Attr:
|
|||||||
# cycle based on index
|
# cycle based on index
|
||||||
return best[1][i % len(best[1])]
|
return best[1][i % len(best[1])]
|
||||||
|
|
||||||
|
# fallback to defaults?
|
||||||
|
if self.defaults is not None:
|
||||||
|
return self.defaults[i, key]
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
@@ -244,11 +268,8 @@ class Attr:
|
|||||||
|
|
||||||
# a key function for sorting by key order
|
# a key function for sorting by key order
|
||||||
def key(self, key):
|
def key(self, key):
|
||||||
# allow key to be a tuple to make sorting dicts easier
|
if not isinstance(key, tuple):
|
||||||
if (isinstance(key, tuple)
|
key = (key,)
|
||||||
and len(key) >= 1
|
|
||||||
and isinstance(key[0], tuple)):
|
|
||||||
key = key[0]
|
|
||||||
|
|
||||||
best = None
|
best = None
|
||||||
for i, ks in enumerate(self.keyed.keys()):
|
for i, ks in enumerate(self.keyed.keys()):
|
||||||
@@ -267,6 +288,10 @@ class Attr:
|
|||||||
if best is not None:
|
if best is not None:
|
||||||
return best[1]
|
return best[1]
|
||||||
|
|
||||||
|
# fallback to defaults?
|
||||||
|
if self.defaults is not None:
|
||||||
|
return len(self.keyed) + self.defaults.key(key)
|
||||||
|
|
||||||
return len(self.keyed)
|
return len(self.keyed)
|
||||||
|
|
||||||
# parse %-escaped strings
|
# parse %-escaped strings
|
||||||
@@ -316,8 +341,8 @@ def punescape(s, attrs=None):
|
|||||||
|
|
||||||
# a type to represent tiles
|
# a type to represent tiles
|
||||||
class Tile:
|
class Tile:
|
||||||
def __init__(self, key, children,
|
def __init__(self, key, children, *,
|
||||||
x=None, y=None, width=None, height=None, *,
|
x=None, y=None, width=None, height=None,
|
||||||
depth=None,
|
depth=None,
|
||||||
attrs=None,
|
attrs=None,
|
||||||
label=None,
|
label=None,
|
||||||
@@ -340,7 +365,7 @@ class Tile:
|
|||||||
self.color = color
|
self.color = color
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Tile(%r, %r, %r, %r, %r, %r)' % (
|
return 'Tile(%r, %r, x=%r, y=%r, width=%r, height=%r)' % (
|
||||||
','.join(self.key), self.value,
|
','.join(self.key), self.value,
|
||||||
self.x, self.y, self.width, self.height)
|
self.x, self.y, self.width, self.height)
|
||||||
|
|
||||||
@@ -372,6 +397,15 @@ class Tile:
|
|||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
return self.value < other.value
|
return self.value < other.value
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return self.value <= other.value
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self.value > other.value
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return self.value >= other.value
|
||||||
|
|
||||||
# recursive traversals
|
# recursive traversals
|
||||||
def tiles(self):
|
def tiles(self):
|
||||||
yield self
|
yield self
|
||||||
@@ -389,7 +423,7 @@ class Tile:
|
|||||||
for t in self.children:
|
for t in self.children:
|
||||||
t.sort()
|
t.sort()
|
||||||
|
|
||||||
# recursive align to int boundaries
|
# recursive align to pixel boundaries
|
||||||
def align(self):
|
def align(self):
|
||||||
# this extra +0.1 and using points instead of width/height is
|
# this extra +0.1 and using points instead of width/height is
|
||||||
# to help minimize rounding errors
|
# to help minimize rounding errors
|
||||||
|
|||||||
Reference in New Issue
Block a user