scripts: Adopted double-indent on multiline expressions
This matches the style used in C, which is good for consistency:
a_really_long_function_name(
double_indent_after_first_newline(
single_indent_nested_newlines))
We were already doing this for multiline control-flow statements, simply
because I'm not sure how else you could indent this without making
things really confusing:
if a_really_long_function_name(
double_indent_after_first_newline(
single_indent_nested_newlines)):
do_the_thing()
This was the only real difference style-wise between the Python code and
C code, so now both should be following roughly the same style (80 cols,
double-indent multiline exprs, prefix multiline binary ops, etc).
This commit is contained in:
+81
-81
@@ -53,8 +53,8 @@ def collect(csv_paths, renames=[], defines=[]):
|
||||
with openio(path) as f:
|
||||
reader = csv.DictReader(f, restval='')
|
||||
fields.extend(
|
||||
k for k in reader.fieldnames
|
||||
if k not in fields)
|
||||
k for k in reader.fieldnames
|
||||
if k not in fields)
|
||||
for r in reader:
|
||||
# apply any renames
|
||||
if renames:
|
||||
@@ -90,8 +90,8 @@ def main(csv_paths, output, *,
|
||||
|
||||
# separate out renames
|
||||
renames = list(it.chain.from_iterable(
|
||||
((k, v) for v in vs)
|
||||
for k, vs in it.chain(by or [], fields or [])))
|
||||
((k, v) for v in vs)
|
||||
for k, vs in it.chain(by or [], fields or [])))
|
||||
if by is not None:
|
||||
by = [k for k, _ in by]
|
||||
if fields is not None:
|
||||
@@ -99,7 +99,7 @@ def main(csv_paths, output, *,
|
||||
|
||||
if by is None and fields is None:
|
||||
print("error: needs --by or --fields to figure out fields",
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
# collect results from csv files
|
||||
@@ -108,24 +108,22 @@ def main(csv_paths, output, *,
|
||||
# if by not specified, guess it's anything not in
|
||||
# iter/size/fields/renames/defines
|
||||
if by is None:
|
||||
by = [
|
||||
k for k in fields_
|
||||
if k != iter
|
||||
and k != size
|
||||
and k not in (fields or [])
|
||||
and not any(k == old_k for _, old_k in renames)
|
||||
and not any(k == k_ for k_, _ in defines)]
|
||||
by = [k for k in fields_
|
||||
if k != iter
|
||||
and k != size
|
||||
and k not in (fields or [])
|
||||
and not any(k == old_k for _, old_k in renames)
|
||||
and not any(k == k_ for k_, _ in defines)]
|
||||
|
||||
# if fields not specified, guess it's anything not in
|
||||
# by/iter/size/renames/defines
|
||||
if fields is None:
|
||||
fields = [
|
||||
k for k in fields_
|
||||
if k not in (by or [])
|
||||
and k != iter
|
||||
and k != size
|
||||
and not any(k == old_k for _, old_k in renames)
|
||||
and not any(k == k_ for k_, _ in defines)]
|
||||
fields = [k for k in fields_
|
||||
if k not in (by or [])
|
||||
and k != iter
|
||||
and k != size
|
||||
and not any(k == old_k for _, old_k in renames)
|
||||
and not any(k == k_ for k_, _ in defines)]
|
||||
|
||||
# add meas to by if it isn't already present
|
||||
if meas is not None and meas not in by:
|
||||
@@ -161,23 +159,23 @@ def main(csv_paths, output, *,
|
||||
# find amortized results
|
||||
if amor:
|
||||
amors.append(r
|
||||
| {f: sums[f] / size_ for f in fields}
|
||||
| ({} if meas is None
|
||||
else {meas: r[meas]+'+amor'} if meas in r
|
||||
else {meas: 'amor'}))
|
||||
| {f: sums[f] / size_ for f in fields}
|
||||
| ({} if meas is None
|
||||
else {meas: r[meas]+'+amor'} if meas in r
|
||||
else {meas: 'amor'}))
|
||||
|
||||
# also find per-byte results
|
||||
if per:
|
||||
amors.append(r
|
||||
| {f: r.get(f, 0) / size_ for f in fields}
|
||||
| ({} if meas is None
|
||||
else {meas: r[meas]+'+per'} if meas in r
|
||||
else {meas: 'per'}))
|
||||
| {f: r.get(f, 0) / size_ for f in fields}
|
||||
| ({} if meas is None
|
||||
else {meas: r[meas]+'+per'} if meas in r
|
||||
else {meas: 'per'}))
|
||||
|
||||
# write results to CSV
|
||||
with openio(output, 'w') as f:
|
||||
writer = csv.DictWriter(f,
|
||||
by + [iter] + ([size] if size is not None else []) + fields)
|
||||
by + [iter] + ([size] if size is not None else []) + fields)
|
||||
writer.writeheader()
|
||||
for r in amors:
|
||||
writer.writerow(r)
|
||||
@@ -187,68 +185,70 @@ if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Amortize benchmark measurements.",
|
||||
allow_abbrev=False)
|
||||
description="Amortize benchmark measurements.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'csv_paths',
|
||||
nargs='*',
|
||||
help="Input *.csv files.")
|
||||
'csv_paths',
|
||||
nargs='*',
|
||||
help="Input *.csv files.")
|
||||
parser.add_argument(
|
||||
'-o', '--output',
|
||||
required=True,
|
||||
help="*.csv file to write amortized measurements to.")
|
||||
'-o', '--output',
|
||||
required=True,
|
||||
help="*.csv file to write amortized measurements to.")
|
||||
parser.add_argument(
|
||||
'--amor',
|
||||
action='store_true',
|
||||
help="Compute amortized results.")
|
||||
'--amor',
|
||||
action='store_true',
|
||||
help="Compute amortized results.")
|
||||
parser.add_argument(
|
||||
'--per',
|
||||
action='store_true',
|
||||
help="Compute per-byte results.")
|
||||
'--per',
|
||||
action='store_true',
|
||||
help="Compute per-byte results.")
|
||||
parser.add_argument(
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Group by this field. Can rename fields with new_name=old_name.")
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Group by this field. Can rename fields with "
|
||||
"new_name=old_name.")
|
||||
parser.add_argument(
|
||||
'-m', '--meas',
|
||||
help="Optional name of measurement name field. If provided, the name "
|
||||
"will be modified with +amor or +per.")
|
||||
'-m', '--meas',
|
||||
help="Optional name of measurement name field. If provided, the "
|
||||
"name will be modified with +amor or +per.")
|
||||
parser.add_argument(
|
||||
'-i', '--iter',
|
||||
required=True,
|
||||
help="Name of iteration field.")
|
||||
'-i', '--iter',
|
||||
required=True,
|
||||
help="Name of iteration field.")
|
||||
parser.add_argument(
|
||||
'-n', '--size',
|
||||
help="Optional name of size field.")
|
||||
'-n', '--size',
|
||||
help="Optional name of size field.")
|
||||
parser.add_argument(
|
||||
'-f', '--field',
|
||||
dest='fields',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Field to amortize. Can rename fields with new_name=old_name.")
|
||||
'-f', '--field',
|
||||
dest='fields',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Field to amortize. Can rename fields with "
|
||||
"new_name=old_name.")
|
||||
parser.add_argument(
|
||||
'-D', '--define',
|
||||
dest='defines',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
help="Only include results where this field is this value. May include "
|
||||
"comma-separated options.")
|
||||
'-D', '--define',
|
||||
dest='defines',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
help="Only include results where this field is this value. May "
|
||||
"include comma-separated options.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
|
||||
+110
-111
@@ -53,8 +53,8 @@ def collect(csv_paths, renames=[], defines=[]):
|
||||
with openio(path) as f:
|
||||
reader = csv.DictReader(f, restval='')
|
||||
fields.extend(
|
||||
k for k in reader.fieldnames
|
||||
if k not in fields)
|
||||
k for k in reader.fieldnames
|
||||
if k not in fields)
|
||||
for r in reader:
|
||||
# apply any renames
|
||||
if renames:
|
||||
@@ -108,8 +108,8 @@ def main(csv_paths, output, *,
|
||||
|
||||
# separate out renames
|
||||
renames = list(it.chain.from_iterable(
|
||||
((k, v) for v in vs)
|
||||
for k, vs in it.chain(by or [], seeds or [], fields or [])))
|
||||
((k, v) for v in vs)
|
||||
for k, vs in it.chain(by or [], seeds or [], fields or [])))
|
||||
if by is not None:
|
||||
by = [k for k, _ in by]
|
||||
if seeds is not None:
|
||||
@@ -119,7 +119,7 @@ def main(csv_paths, output, *,
|
||||
|
||||
if by is None and fields is None:
|
||||
print("error: needs --by or --fields to figure out fields",
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
# collect results from csv files
|
||||
@@ -128,22 +128,20 @@ def main(csv_paths, output, *,
|
||||
# if by not specified, guess it's anything not in
|
||||
# seeds/fields/renames/defines
|
||||
if by is None:
|
||||
by = [
|
||||
k for k in fields_
|
||||
if k not in (seeds or [])
|
||||
and k not in (fields or [])
|
||||
and not any(k == old_k for _, old_k in renames)
|
||||
and not any(k == k_ for k_, _ in defines)]
|
||||
by = [k for k in fields_
|
||||
if k not in (seeds or [])
|
||||
and k not in (fields or [])
|
||||
and not any(k == old_k for _, old_k in renames)
|
||||
and not any(k == k_ for k_, _ in defines)]
|
||||
|
||||
# if fields not specified, guess it's anything not in
|
||||
# by/seeds/renames/defines
|
||||
if fields is None:
|
||||
fields = [
|
||||
k for k in fields_
|
||||
if k not in (by or [])
|
||||
and k not in (seeds or [])
|
||||
and not any(k == old_k for _, old_k in renames)
|
||||
and not any(k == k_ for k_, _ in defines)]
|
||||
fields = [k for k in fields_
|
||||
if k not in (by or [])
|
||||
and k not in (seeds or [])
|
||||
and not any(k == old_k for _, old_k in renames)
|
||||
and not any(k == k_ for k_, _ in defines)]
|
||||
|
||||
# add meas to by if it isn't already present
|
||||
if meas is not None and meas not in by:
|
||||
@@ -174,12 +172,11 @@ def main(csv_paths, output, *,
|
||||
meas__ = r[meas]
|
||||
|
||||
def append(meas_, f_):
|
||||
avgs.append(
|
||||
{k: v for k, v in zip(by, key)}
|
||||
| {f: f_(vs_) for f, vs_ in vs.items()}
|
||||
| ({} if meas is None
|
||||
else {meas: meas_} if meas__ is None
|
||||
else {meas: meas__+'+'+meas_}))
|
||||
avgs.append({k: v for k, v in zip(by, key)}
|
||||
| {f: f_(vs_) for f, vs_ in vs.items()}
|
||||
| ({} if meas is None
|
||||
else {meas: meas_} if meas__ is None
|
||||
else {meas: meas__+'+'+meas_}))
|
||||
|
||||
if sum_: append('sum', lambda vs: sum(vs))
|
||||
if prod: append('prod', lambda vs: mt.prod(vs))
|
||||
@@ -189,16 +186,16 @@ def main(csv_paths, output, *,
|
||||
if bnd: append('bnd', lambda vs: max(vs, default=0))
|
||||
if avg: append('avg', lambda vs: sum(vs) / max(len(vs), 1))
|
||||
if stddev: append('stddev', lambda vs: (
|
||||
lambda avg: mt.sqrt(
|
||||
sum((v - avg)**2 for v in vs) / max(len(vs), 1))
|
||||
)(sum(vs) / max(len(vs), 1)))
|
||||
lambda avg: mt.sqrt(
|
||||
sum((v - avg)**2 for v in vs) / max(len(vs), 1))
|
||||
)(sum(vs) / max(len(vs), 1)))
|
||||
if gmean: append('gmean', lambda vs:
|
||||
mt.prod(float(v) for v in vs)**(1 / max(len(vs), 1)))
|
||||
mt.prod(float(v) for v in vs)**(1 / max(len(vs), 1)))
|
||||
if gstddev: append('gstddev', lambda vs: (
|
||||
lambda gmean: mt.exp(mt.sqrt(
|
||||
sum(mt.log(v/gmean)**2 for v in vs) / max(len(vs), 1)))
|
||||
if gmean else mt.inf
|
||||
)(mt.prod(float(v) for v in vs)**(1 / max(len(vs), 1))))
|
||||
lambda gmean: mt.exp(mt.sqrt(
|
||||
sum(mt.log(v/gmean)**2 for v in vs) / max(len(vs), 1)))
|
||||
if gmean else mt.inf
|
||||
)(mt.prod(float(v) for v in vs)**(1 / max(len(vs), 1))))
|
||||
|
||||
# write results to CSVS
|
||||
with openio(output, 'w') as f:
|
||||
@@ -212,101 +209,103 @@ if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Compute averages/etc of benchmark measurements.",
|
||||
allow_abbrev=False)
|
||||
description="Compute averages/etc of benchmark measurements.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'csv_paths',
|
||||
nargs='*',
|
||||
help="Input *.csv files.")
|
||||
'csv_paths',
|
||||
nargs='*',
|
||||
help="Input *.csv files.")
|
||||
parser.add_argument(
|
||||
'-o', '--output',
|
||||
required=True,
|
||||
help="*.csv file to write amortized measurements to.")
|
||||
'-o', '--output',
|
||||
required=True,
|
||||
help="*.csv file to write amortized measurements to.")
|
||||
parser.add_argument(
|
||||
'--sum',
|
||||
action='store_true',
|
||||
help="Compute the sum.")
|
||||
'--sum',
|
||||
action='store_true',
|
||||
help="Compute the sum.")
|
||||
parser.add_argument(
|
||||
'--prod',
|
||||
action='store_true',
|
||||
help="Compute the product.")
|
||||
'--prod',
|
||||
action='store_true',
|
||||
help="Compute the product.")
|
||||
parser.add_argument(
|
||||
'--min',
|
||||
action='store_true',
|
||||
help="Compute the min.")
|
||||
'--min',
|
||||
action='store_true',
|
||||
help="Compute the min.")
|
||||
parser.add_argument(
|
||||
'--max',
|
||||
action='store_true',
|
||||
help="Compute the max.")
|
||||
'--max',
|
||||
action='store_true',
|
||||
help="Compute the max.")
|
||||
parser.add_argument(
|
||||
'--bnd',
|
||||
action='store_true',
|
||||
help="Compute the bounds (min+max concatenated).")
|
||||
'--bnd',
|
||||
action='store_true',
|
||||
help="Compute the bounds (min+max concatenated).")
|
||||
parser.add_argument(
|
||||
'--avg', '--mean',
|
||||
action='store_true',
|
||||
help="Compute the average (the default).")
|
||||
'--avg', '--mean',
|
||||
action='store_true',
|
||||
help="Compute the average (the default).")
|
||||
parser.add_argument(
|
||||
'--stddev',
|
||||
action='store_true',
|
||||
help="Compute the standard deviation.")
|
||||
'--stddev',
|
||||
action='store_true',
|
||||
help="Compute the standard deviation.")
|
||||
parser.add_argument(
|
||||
'--gmean',
|
||||
action='store_true',
|
||||
help="Compute the geometric mean.")
|
||||
'--gmean',
|
||||
action='store_true',
|
||||
help="Compute the geometric mean.")
|
||||
parser.add_argument(
|
||||
'--gstddev',
|
||||
action='store_true',
|
||||
help="Compute the geometric standard deviation.")
|
||||
'--gstddev',
|
||||
action='store_true',
|
||||
help="Compute the geometric standard deviation.")
|
||||
parser.add_argument(
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Group by this field. Can rename fields with new_name=old_name.")
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Group by this field. Can rename fields with "
|
||||
"new_name=old_name.")
|
||||
parser.add_argument(
|
||||
'-m', '--meas',
|
||||
help="Optional name of measurement name field. If provided, the name "
|
||||
"will be modified with +amor or +per.")
|
||||
'-m', '--meas',
|
||||
help="Optional name of measurement name field. If provided, the "
|
||||
"name will be modified with +amor or +per.")
|
||||
parser.add_argument(
|
||||
'-s', '--seed',
|
||||
dest='seeds',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Field to ignore when averaging. Can rename fields with "
|
||||
"new_name=old_name.")
|
||||
'-s', '--seed',
|
||||
dest='seeds',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Field to ignore when averaging. Can rename fields with "
|
||||
"new_name=old_name.")
|
||||
parser.add_argument(
|
||||
'-f', '--field',
|
||||
dest='fields',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Field to amortize. Can rename fields with new_name=old_name.")
|
||||
'-f', '--field',
|
||||
dest='fields',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Field to amortize. Can rename fields with "
|
||||
"new_name=old_name.")
|
||||
parser.add_argument(
|
||||
'-D', '--define',
|
||||
dest='defines',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
help="Only include results where this field is this value. May include "
|
||||
"comma-separated options.")
|
||||
'-D', '--define',
|
||||
dest='defines',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
help="Only include results where this field is this value. May "
|
||||
"include comma-separated options.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
|
||||
+508
-501
File diff suppressed because it is too large
Load Diff
+49
-47
@@ -21,6 +21,7 @@ import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
|
||||
GIT_PATH = ['git']
|
||||
|
||||
|
||||
@@ -36,17 +37,17 @@ def openio(path, mode='r', buffering=-1):
|
||||
|
||||
def changeprefix(from_prefix, to_prefix, line):
|
||||
line, count1 = re.subn(
|
||||
'\\b'+from_prefix,
|
||||
to_prefix,
|
||||
line)
|
||||
'\\b'+from_prefix,
|
||||
to_prefix,
|
||||
line)
|
||||
line, count2 = re.subn(
|
||||
'\\b'+from_prefix.upper(),
|
||||
to_prefix.upper(),
|
||||
line)
|
||||
'\\b'+from_prefix.upper(),
|
||||
to_prefix.upper(),
|
||||
line)
|
||||
line, count3 = re.subn(
|
||||
'\\B-D'+from_prefix.upper(),
|
||||
'-D'+to_prefix.upper(),
|
||||
line)
|
||||
'\\B-D'+from_prefix.upper(),
|
||||
'-D'+to_prefix.upper(),
|
||||
line)
|
||||
return line, count1+count2+count3
|
||||
|
||||
def changefile(from_prefix, to_prefix, from_path, to_path, *,
|
||||
@@ -79,8 +80,9 @@ def changefile(from_prefix, to_prefix, from_path, to_path, *,
|
||||
|
||||
# Summary
|
||||
print('%s: %d replacements' % (
|
||||
'%s -> %s' % (from_path, to_path) if not to_path_temp else from_path,
|
||||
count))
|
||||
'%s -> %s' % (from_path, to_path) if not to_path_temp
|
||||
else from_path,
|
||||
count))
|
||||
|
||||
def main(from_prefix, to_prefix, paths=[], *,
|
||||
verbose=False,
|
||||
@@ -111,7 +113,7 @@ def main(from_prefix, to_prefix, paths=[], *,
|
||||
|
||||
# rename contents
|
||||
changefile(from_prefix, to_prefix, from_path, to_path,
|
||||
no_replacements=no_replacements)
|
||||
no_replacements=no_replacements)
|
||||
|
||||
# stage?
|
||||
if git and not no_stage:
|
||||
@@ -130,49 +132,49 @@ if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Change prefixes in files/filenames. Useful for creating "
|
||||
"different versions of a codebase that don't conflict at compile "
|
||||
"time.",
|
||||
allow_abbrev=False)
|
||||
description="Change prefixes in files/filenames. Useful for "
|
||||
"creating different versions of a codebase that don't "
|
||||
"conflict at compile time.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'from_prefix',
|
||||
help="Prefix to replace.")
|
||||
'from_prefix',
|
||||
help="Prefix to replace.")
|
||||
parser.add_argument(
|
||||
'to_prefix',
|
||||
help="Prefix to replace with.")
|
||||
'to_prefix',
|
||||
help="Prefix to replace with.")
|
||||
parser.add_argument(
|
||||
'paths',
|
||||
nargs='*',
|
||||
help="Files to operate on.")
|
||||
'paths',
|
||||
nargs='*',
|
||||
help="Files to operate on.")
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help="Output commands that run behind the scenes.")
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help="Output commands that run behind the scenes.")
|
||||
parser.add_argument(
|
||||
'-o', '--output',
|
||||
help="Output file.")
|
||||
'-o', '--output',
|
||||
help="Output file.")
|
||||
parser.add_argument(
|
||||
'-N', '--no-replacements',
|
||||
action='store_true',
|
||||
help="Don't change prefixes in files")
|
||||
'-N', '--no-replacements',
|
||||
action='store_true',
|
||||
help="Don't change prefixes in files")
|
||||
parser.add_argument(
|
||||
'-R', '--no-renames',
|
||||
action='store_true',
|
||||
help="Don't rename files")
|
||||
'-R', '--no-renames',
|
||||
action='store_true',
|
||||
help="Don't rename files")
|
||||
parser.add_argument(
|
||||
'--git',
|
||||
action='store_true',
|
||||
help="Use git to find/update files.")
|
||||
'--git',
|
||||
action='store_true',
|
||||
help="Use git to find/update files.")
|
||||
parser.add_argument(
|
||||
'--no-stage',
|
||||
action='store_true',
|
||||
help="Don't stage changes with git.")
|
||||
'--no-stage',
|
||||
action='store_true',
|
||||
help="Don't stage changes with git.")
|
||||
parser.add_argument(
|
||||
'--git-path',
|
||||
type=lambda x: x.split(),
|
||||
default=GIT_PATH,
|
||||
help="Path to git executable, may include flags. "
|
||||
"Defaults to %r." % GIT_PATH)
|
||||
'--git-path',
|
||||
type=lambda x: x.split(),
|
||||
default=GIT_PATH,
|
||||
help="Path to git executable, may include flags. "
|
||||
"Defaults to %r." % GIT_PATH)
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+207
-202
@@ -115,11 +115,11 @@ class CodeResult(co.namedtuple('CodeResult', [
|
||||
__slots__ = ()
|
||||
def __new__(cls, file='', function='', size=0):
|
||||
return super().__new__(cls, file, function,
|
||||
RInt(size))
|
||||
RInt(size))
|
||||
|
||||
def __add__(self, other):
|
||||
return CodeResult(self.file, self.function,
|
||||
self.size + other.size)
|
||||
self.size + other.size)
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
@@ -140,18 +140,18 @@ def collect(obj_paths, *,
|
||||
everything=False,
|
||||
**args):
|
||||
size_pattern = re.compile(
|
||||
'^(?P<size>[0-9a-fA-F]+)' +
|
||||
' (?P<type>[%s])' % re.escape(nm_types) +
|
||||
' (?P<func>.+?)$')
|
||||
'^(?P<size>[0-9a-fA-F]+)'
|
||||
+ ' (?P<type>[%s])' % re.escape(nm_types)
|
||||
+ ' (?P<func>.+?)$')
|
||||
line_pattern = re.compile(
|
||||
'^\s+(?P<no>[0-9]+)'
|
||||
'(?:\s+(?P<dir>[0-9]+))?'
|
||||
'\s+.*'
|
||||
'\s+(?P<path>[^\s]+)$')
|
||||
'^\s+(?P<no>[0-9]+)'
|
||||
'(?:\s+(?P<dir>[0-9]+))?'
|
||||
'\s+.*'
|
||||
'\s+(?P<path>[^\s]+)$')
|
||||
info_pattern = re.compile(
|
||||
'^(?:.*(?P<tag>DW_TAG_[a-z_]+).*'
|
||||
'|.*DW_AT_name.*:\s*(?P<name>[^:\s]+)\s*'
|
||||
'|.*DW_AT_decl_file.*:\s*(?P<file>[0-9]+)\s*)$')
|
||||
'^(?:.*(?P<tag>DW_TAG_[a-z_]+).*'
|
||||
'|.*DW_AT_name.*:\s*(?P<name>[^:\s]+)\s*'
|
||||
'|.*DW_AT_decl_file.*:\s*(?P<file>[0-9]+)\s*)$')
|
||||
|
||||
results = []
|
||||
for path in obj_paths:
|
||||
@@ -165,11 +165,11 @@ def collect(obj_paths, *,
|
||||
if args.get('verbose'):
|
||||
print(' '.join(shlex.quote(c) for c in cmd))
|
||||
proc = sp.Popen(cmd,
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
for line in proc.stdout:
|
||||
m = size_pattern.match(line)
|
||||
if m:
|
||||
@@ -178,8 +178,8 @@ def collect(obj_paths, *,
|
||||
if not everything and func.startswith('__'):
|
||||
continue
|
||||
results_.append(CodeResult(
|
||||
file, func,
|
||||
int(m.group('size'), 16)))
|
||||
file, func,
|
||||
int(m.group('size'), 16)))
|
||||
proc.wait()
|
||||
if proc.returncode != 0:
|
||||
if not args.get('verbose'):
|
||||
@@ -196,11 +196,11 @@ def collect(obj_paths, *,
|
||||
if args.get('verbose'):
|
||||
print(' '.join(shlex.quote(c) for c in cmd))
|
||||
proc = sp.Popen(cmd,
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
for line in proc.stdout:
|
||||
# note that files contain references to dirs, which we
|
||||
# dereference as soon as we see them as each file table follows a
|
||||
@@ -215,8 +215,8 @@ def collect(obj_paths, *,
|
||||
dir = int(m.group('dir'))
|
||||
if dir in dirs:
|
||||
files[int(m.group('no'))] = os.path.join(
|
||||
dirs[dir],
|
||||
m.group('path'))
|
||||
dirs[dir],
|
||||
m.group('path'))
|
||||
else:
|
||||
files[int(m.group('no'))] = m.group('path')
|
||||
proc.wait()
|
||||
@@ -241,11 +241,11 @@ def collect(obj_paths, *,
|
||||
if args.get('verbose'):
|
||||
print(' '.join(shlex.quote(c) for c in cmd))
|
||||
proc = sp.Popen(cmd,
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
for line in proc.stdout:
|
||||
# state machine here to find definitions
|
||||
m = info_pattern.match(line)
|
||||
@@ -279,17 +279,16 @@ def collect(obj_paths, *,
|
||||
file = defs[r.function]
|
||||
else:
|
||||
_, file = max(
|
||||
defs.items(),
|
||||
key=lambda d: difflib.SequenceMatcher(None,
|
||||
d[0],
|
||||
r.function, False).ratio())
|
||||
defs.items(),
|
||||
key=lambda d: difflib.SequenceMatcher(None,
|
||||
d[0],
|
||||
r.function, False).ratio())
|
||||
else:
|
||||
file = r.file
|
||||
|
||||
# ignore filtered sources
|
||||
if sources is not None:
|
||||
if not any(
|
||||
os.path.abspath(file) == os.path.abspath(s)
|
||||
if not any(os.path.abspath(file) == os.path.abspath(s)
|
||||
for s in sources):
|
||||
continue
|
||||
else:
|
||||
@@ -319,7 +318,7 @@ def fold(Result, results, by=None, defines=[]):
|
||||
for k in it.chain(by or [], (k for k, _ in defines)):
|
||||
if k not in Result._by and k not in Result._fields:
|
||||
print("error: could not find field %r?" % k,
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
# filter by matching defines
|
||||
@@ -368,52 +367,55 @@ def table(Result, results, diff_results=None, *,
|
||||
|
||||
# organize by name
|
||||
table = {
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in results}
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in results}
|
||||
diff_table = {
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in diff_results or []}
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in diff_results or []}
|
||||
names = [name
|
||||
for name in table.keys() | diff_table.keys()
|
||||
if diff_results is None
|
||||
or all_
|
||||
or any(
|
||||
types[k].ratio(
|
||||
getattr(table.get(name), k, None),
|
||||
getattr(diff_table.get(name), k, None))
|
||||
for k in fields)]
|
||||
for name in table.keys() | diff_table.keys()
|
||||
if diff_results is None
|
||||
or all_
|
||||
or any(
|
||||
types[k].ratio(
|
||||
getattr(table.get(name), k, None),
|
||||
getattr(diff_table.get(name), k, None))
|
||||
for k in fields)]
|
||||
|
||||
# sort again, now with diff info, note that python's sort is stable
|
||||
names.sort()
|
||||
if diff_results is not None:
|
||||
names.sort(key=lambda n: tuple(
|
||||
types[k].ratio(
|
||||
getattr(table.get(n), k, None),
|
||||
getattr(diff_table.get(n), k, None))
|
||||
for k in fields),
|
||||
reverse=True)
|
||||
names.sort(
|
||||
key=lambda n: tuple(
|
||||
types[k].ratio(
|
||||
getattr(table.get(n), k, None),
|
||||
getattr(diff_table.get(n), k, None))
|
||||
for k in fields),
|
||||
reverse=True)
|
||||
if sort:
|
||||
for k, reverse in reversed(sort):
|
||||
names.sort(
|
||||
key=lambda n: tuple(
|
||||
(getattr(table[n], k),)
|
||||
if getattr(table.get(n), k, None) is not None else ()
|
||||
for k in ([k] if k else [
|
||||
k for k in Result._sort if k in fields])),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
key=lambda n: tuple(
|
||||
(getattr(table[n], k),)
|
||||
if getattr(table.get(n), k, None) is not None
|
||||
else ()
|
||||
for k in (
|
||||
[k] if k else [
|
||||
k for k in Result._sort
|
||||
if k in fields])),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
|
||||
|
||||
# build up our lines
|
||||
lines = []
|
||||
|
||||
# header
|
||||
header = [
|
||||
'%s%s' % (
|
||||
','.join(by),
|
||||
' (%d added, %d removed)' % (
|
||||
sum(1 for n in table if n not in diff_table),
|
||||
sum(1 for n in diff_table if n not in table))
|
||||
if diff_results is not None and not percent else '')
|
||||
header = ['%s%s' % (
|
||||
','.join(by),
|
||||
' (%d added, %d removed)' % (
|
||||
sum(1 for n in table if n not in diff_table),
|
||||
sum(1 for n in diff_table if n not in table))
|
||||
if diff_results is not None and not percent else '')
|
||||
if not summary else '']
|
||||
if diff_results is None:
|
||||
for k in fields:
|
||||
@@ -436,43 +438,43 @@ def table(Result, results, diff_results=None, *,
|
||||
if diff_results is None:
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(getattr(r, k).table(),
|
||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
(getattr(r, k).table(),
|
||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
elif percent:
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none,
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none,
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
else:
|
||||
for k in fields:
|
||||
entry.append(getattr(diff_r, k).table()
|
||||
if getattr(diff_r, k, None) is not None
|
||||
else types[k].none)
|
||||
if getattr(diff_r, k, None) is not None
|
||||
else types[k].none)
|
||||
for k in fields:
|
||||
entry.append(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(types[k].diff(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)),
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)] if t
|
||||
else [])(
|
||||
types[k].ratio(
|
||||
(types[k].diff(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
getattr(diff_r, k, None)),
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)] if t
|
||||
else [])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
return entry
|
||||
|
||||
# entries
|
||||
@@ -495,8 +497,8 @@ def table(Result, results, diff_results=None, *,
|
||||
|
||||
# homogenize
|
||||
lines = [
|
||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||
for line in lines]
|
||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||
for line in lines]
|
||||
|
||||
# find the best widths, note that column 0 contains the names and is
|
||||
# handled a bit differently
|
||||
@@ -510,11 +512,11 @@ def table(Result, results, diff_results=None, *,
|
||||
# print our table
|
||||
for line in lines:
|
||||
print('%-*s %s' % (
|
||||
widths[0], line[0][0],
|
||||
' '.join('%*s%-*s' % (
|
||||
widths[i], x[0],
|
||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||
for i, x in enumerate(line[1:], 1))))
|
||||
widths[0], line[0][0],
|
||||
' '.join('%*s%-*s' % (
|
||||
widths[i], x[0],
|
||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||
for i, x in enumerate(line[1:], 1))))
|
||||
|
||||
|
||||
def main(obj_paths, *,
|
||||
@@ -540,10 +542,10 @@ def main(obj_paths, *,
|
||||
continue
|
||||
try:
|
||||
results.append(CodeResult(
|
||||
**{k: r[k] for k in CodeResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k] for k in CodeResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
**{k: r[k] for k in CodeResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k] for k in CodeResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
@@ -555,25 +557,27 @@ def main(obj_paths, *,
|
||||
if sort:
|
||||
for k, reverse in reversed(sort):
|
||||
results.sort(
|
||||
key=lambda r: tuple(
|
||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||
for k in ([k] if k else CodeResult._sort)),
|
||||
reverse=reverse ^ (not k or k in CodeResult._fields))
|
||||
key=lambda r: tuple(
|
||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||
for k in ([k] if k else CodeResult._sort)),
|
||||
reverse=reverse ^ (not k or k in CodeResult._fields))
|
||||
|
||||
# write results to CSV
|
||||
if args.get('output'):
|
||||
with openio(args['output'], 'w') as f:
|
||||
writer = csv.DictWriter(f,
|
||||
(by if by is not None else CodeResult._by)
|
||||
+ [k for k in (
|
||||
fields if fields is not None else CodeResult._fields)])
|
||||
(by if by is not None else CodeResult._by)
|
||||
+ [k for k in (
|
||||
fields if fields is not None
|
||||
else CodeResult._fields)])
|
||||
writer.writeheader()
|
||||
for r in results:
|
||||
writer.writerow(
|
||||
{k: getattr(r, k) for k in (
|
||||
by if by is not None else CodeResult._by)}
|
||||
| {k: getattr(r, k) for k in (
|
||||
fields if fields is not None else CodeResult._fields)})
|
||||
{k: getattr(r, k) for k in (
|
||||
by if by is not None else CodeResult._by)}
|
||||
| {k: getattr(r, k) for k in (
|
||||
fields if fields is not None
|
||||
else CodeResult._fields)})
|
||||
|
||||
# find previous results?
|
||||
if args.get('diff'):
|
||||
@@ -591,10 +595,10 @@ def main(obj_paths, *,
|
||||
continue
|
||||
try:
|
||||
diff_results.append(CodeResult(
|
||||
**{k: r[k] for k in CodeResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k] for k in CodeResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
**{k: r[k] for k in CodeResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k] for k in CodeResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
except TypeError:
|
||||
pass
|
||||
except FileNotFoundError:
|
||||
@@ -606,115 +610,116 @@ def main(obj_paths, *,
|
||||
# print table
|
||||
if not args.get('quiet'):
|
||||
table(CodeResult, results,
|
||||
diff_results if args.get('diff') else None,
|
||||
by=by if by is not None else ['function'],
|
||||
fields=fields,
|
||||
sort=sort,
|
||||
**args)
|
||||
diff_results if args.get('diff') else None,
|
||||
by=by if by is not None else ['function'],
|
||||
fields=fields,
|
||||
sort=sort,
|
||||
**args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Find code size at the function level.",
|
||||
allow_abbrev=False)
|
||||
description="Find code size at the function level.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'obj_paths',
|
||||
nargs='*',
|
||||
help="Input *.o files.")
|
||||
'obj_paths',
|
||||
nargs='*',
|
||||
help="Input *.o files.")
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help="Output commands that run behind the scenes.")
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help="Output commands that run behind the scenes.")
|
||||
parser.add_argument(
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help="Don't show anything, useful with -o.")
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help="Don't show anything, useful with -o.")
|
||||
parser.add_argument(
|
||||
'-o', '--output',
|
||||
help="Specify CSV file to store results.")
|
||||
'-o', '--output',
|
||||
help="Specify CSV file to store results.")
|
||||
parser.add_argument(
|
||||
'-u', '--use',
|
||||
help="Don't parse anything, use this CSV file.")
|
||||
'-u', '--use',
|
||||
help="Don't parse anything, use this CSV file.")
|
||||
parser.add_argument(
|
||||
'-d', '--diff',
|
||||
help="Specify CSV file to diff against.")
|
||||
'-d', '--diff',
|
||||
help="Specify CSV file to diff against.")
|
||||
parser.add_argument(
|
||||
'-a', '--all',
|
||||
action='store_true',
|
||||
help="Show all, not just the ones that changed.")
|
||||
'-a', '--all',
|
||||
action='store_true',
|
||||
help="Show all, not just the ones that changed.")
|
||||
parser.add_argument(
|
||||
'-p', '--percent',
|
||||
action='store_true',
|
||||
help="Only show percentage change, not a full diff.")
|
||||
'-p', '--percent',
|
||||
action='store_true',
|
||||
help="Only show percentage change, not a full diff.")
|
||||
parser.add_argument(
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
choices=CodeResult._by,
|
||||
help="Group by this field.")
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
choices=CodeResult._by,
|
||||
help="Group by this field.")
|
||||
parser.add_argument(
|
||||
'-f', '--field',
|
||||
dest='fields',
|
||||
action='append',
|
||||
choices=CodeResult._fields,
|
||||
help="Show this field.")
|
||||
'-f', '--field',
|
||||
dest='fields',
|
||||
action='append',
|
||||
choices=CodeResult._fields,
|
||||
help="Show this field.")
|
||||
parser.add_argument(
|
||||
'-D', '--define',
|
||||
dest='defines',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
help="Only include results where this field is this value.")
|
||||
'-D', '--define',
|
||||
dest='defines',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
help="Only include results where this field is this value.")
|
||||
class AppendSort(argparse.Action):
|
||||
def __call__(self, parser, namespace, value, option):
|
||||
if namespace.sort is None:
|
||||
namespace.sort = []
|
||||
namespace.sort.append((value, True if option == '-S' else False))
|
||||
parser.add_argument(
|
||||
'-s', '--sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field.")
|
||||
'-s', '--sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field.")
|
||||
parser.add_argument(
|
||||
'-S', '--reverse-sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field, but backwards.")
|
||||
'-S', '--reverse-sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field, but backwards.")
|
||||
parser.add_argument(
|
||||
'-Y', '--summary',
|
||||
action='store_true',
|
||||
help="Only show the total.")
|
||||
'-Y', '--summary',
|
||||
action='store_true',
|
||||
help="Only show the total.")
|
||||
parser.add_argument(
|
||||
'-F', '--source',
|
||||
dest='sources',
|
||||
action='append',
|
||||
help="Only consider definitions in this file. Defaults to anything "
|
||||
"in the current directory.")
|
||||
'-F', '--source',
|
||||
dest='sources',
|
||||
action='append',
|
||||
help="Only consider definitions in this file. Defaults to "
|
||||
"anything in the current directory.")
|
||||
parser.add_argument(
|
||||
'--everything',
|
||||
action='store_true',
|
||||
help="Include builtin and libc specific symbols.")
|
||||
'--everything',
|
||||
action='store_true',
|
||||
help="Include builtin and libc specific symbols.")
|
||||
parser.add_argument(
|
||||
'--nm-types',
|
||||
default=NM_TYPES,
|
||||
help="Type of symbols to report, this uses the same single-character "
|
||||
"type-names emitted by nm. Defaults to %r." % NM_TYPES)
|
||||
'--nm-types',
|
||||
default=NM_TYPES,
|
||||
help="Type of symbols to report, this uses the same "
|
||||
"single-character type-names emitted by nm. Defaults to "
|
||||
"%r." % NM_TYPES)
|
||||
parser.add_argument(
|
||||
'--nm-path',
|
||||
type=lambda x: x.split(),
|
||||
default=NM_PATH,
|
||||
help="Path to the nm executable, may include flags. "
|
||||
"Defaults to %r." % NM_PATH)
|
||||
'--nm-path',
|
||||
type=lambda x: x.split(),
|
||||
default=NM_PATH,
|
||||
help="Path to the nm executable, may include flags. "
|
||||
"Defaults to %r." % NM_PATH)
|
||||
parser.add_argument(
|
||||
'--objdump-path',
|
||||
type=lambda x: x.split(),
|
||||
default=OBJDUMP_PATH,
|
||||
help="Path to the objdump executable, may include flags. "
|
||||
"Defaults to %r." % OBJDUMP_PATH)
|
||||
'--objdump-path',
|
||||
type=lambda x: x.split(),
|
||||
default=OBJDUMP_PATH,
|
||||
help="Path to the objdump executable, may include flags. "
|
||||
"Defaults to %r." % OBJDUMP_PATH)
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+245
-241
@@ -4,8 +4,8 @@
|
||||
#
|
||||
# Example:
|
||||
# ./scripts/cov.py \
|
||||
# lfs.t.a.gcda lfs_util.t.a.gcda \
|
||||
# -Flfs.c -Flfs_util.c -slines
|
||||
# lfs.t.a.gcda lfs_util.t.a.gcda \
|
||||
# -Flfs.c -Flfs_util.c -slines
|
||||
#
|
||||
# Copyright (c) 2022, The littlefs authors.
|
||||
# Copyright (c) 2020, Arm Limited. All rights reserved.
|
||||
@@ -22,6 +22,7 @@ import re
|
||||
import shlex
|
||||
import subprocess as sp
|
||||
|
||||
|
||||
# TODO use explode_asserts to avoid counting assert branches?
|
||||
# TODO use dwarf=info to find functions for inline functions?
|
||||
|
||||
@@ -128,15 +129,15 @@ class RFrac(co.namedtuple('RFrac', 'a,b')):
|
||||
def notes(self):
|
||||
t = self.a.x/self.b.x if self.b.x else 1.0
|
||||
return ['∞%' if t == +mt.inf
|
||||
else '-∞%' if t == -mt.inf
|
||||
else '%.1f%%' % (100*t)]
|
||||
else '-∞%' if t == -mt.inf
|
||||
else '%.1f%%' % (100*t)]
|
||||
|
||||
def diff(self, other):
|
||||
new_a, new_b = self if self else (RInt(0), RInt(0))
|
||||
old_a, old_b = other if other else (RInt(0), RInt(0))
|
||||
return '%11s' % ('%s/%s' % (
|
||||
new_a.diff(old_a).strip(),
|
||||
new_b.diff(old_b).strip()))
|
||||
new_a.diff(old_a).strip(),
|
||||
new_b.diff(old_b).strip()))
|
||||
|
||||
def ratio(self, other):
|
||||
new_a, new_b = self if self else (RInt(0), RInt(0))
|
||||
@@ -184,23 +185,23 @@ class CovResult(co.namedtuple('CovResult', [
|
||||
_fields = ['calls', 'hits', 'funcs', 'lines', 'branches']
|
||||
_sort = ['funcs', 'lines', 'branches', 'hits', 'calls']
|
||||
_types = {
|
||||
'calls': RInt, 'hits': RInt,
|
||||
'funcs': RFrac, 'lines': RFrac, 'branches': RFrac}
|
||||
'calls': RInt, 'hits': RInt,
|
||||
'funcs': RFrac, 'lines': RFrac, 'branches': RFrac}
|
||||
|
||||
__slots__ = ()
|
||||
def __new__(cls, file='', function='', line=0,
|
||||
calls=0, hits=0, funcs=0, lines=0, branches=0):
|
||||
return super().__new__(cls, file, function, int(RInt(line)),
|
||||
RInt(calls), RInt(hits),
|
||||
RFrac(funcs), RFrac(lines), RFrac(branches))
|
||||
RInt(calls), RInt(hits),
|
||||
RFrac(funcs), RFrac(lines), RFrac(branches))
|
||||
|
||||
def __add__(self, other):
|
||||
return CovResult(self.file, self.function, self.line,
|
||||
max(self.calls, other.calls),
|
||||
max(self.hits, other.hits),
|
||||
self.funcs + other.funcs,
|
||||
self.lines + other.lines,
|
||||
self.branches + other.branches)
|
||||
max(self.calls, other.calls),
|
||||
max(self.hits, other.hits),
|
||||
self.funcs + other.funcs,
|
||||
self.lines + other.lines,
|
||||
self.branches + other.branches)
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
@@ -226,11 +227,11 @@ def collect(gcda_paths, *,
|
||||
if args.get('verbose'):
|
||||
print(' '.join(shlex.quote(c) for c in cmd))
|
||||
proc = sp.Popen(cmd,
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
data = json.load(proc.stdout)
|
||||
proc.wait()
|
||||
if proc.returncode != 0:
|
||||
@@ -243,8 +244,7 @@ def collect(gcda_paths, *,
|
||||
for file in data['files']:
|
||||
# ignore filtered sources
|
||||
if sources is not None:
|
||||
if not any(
|
||||
os.path.abspath(file['file']) == os.path.abspath(s)
|
||||
if not any(os.path.abspath(file['file']) == os.path.abspath(s)
|
||||
for s in sources):
|
||||
continue
|
||||
else:
|
||||
@@ -272,11 +272,11 @@ def collect(gcda_paths, *,
|
||||
# go ahead and add functions, later folding will merge this if
|
||||
# there are other hits on this line
|
||||
results.append(CovResult(
|
||||
file_name, func_name, func['start_line'],
|
||||
func['execution_count'], 0,
|
||||
RFrac(1 if func['execution_count'] > 0 else 0, 1),
|
||||
0,
|
||||
0))
|
||||
file_name, func_name, func['start_line'],
|
||||
func['execution_count'], 0,
|
||||
RFrac(1 if func['execution_count'] > 0 else 0, 1),
|
||||
0,
|
||||
0))
|
||||
|
||||
for line in file['lines']:
|
||||
func_name = line.get('function_name', '(inlined)')
|
||||
@@ -288,14 +288,14 @@ def collect(gcda_paths, *,
|
||||
# go ahead and add lines, later folding will merge this if
|
||||
# there are other hits on this line
|
||||
results.append(CovResult(
|
||||
file_name, func_name, line['line_number'],
|
||||
0, line['count'],
|
||||
0,
|
||||
RFrac(1 if line['count'] > 0 else 0, 1),
|
||||
RFrac(
|
||||
sum(1 if branch['count'] > 0 else 0
|
||||
for branch in line['branches']),
|
||||
len(line['branches']))))
|
||||
file_name, func_name, line['line_number'],
|
||||
0, line['count'],
|
||||
0,
|
||||
RFrac(1 if line['count'] > 0 else 0, 1),
|
||||
RFrac(
|
||||
sum(1 if branch['count'] > 0 else 0
|
||||
for branch in line['branches']),
|
||||
len(line['branches']))))
|
||||
|
||||
return results
|
||||
|
||||
@@ -307,7 +307,7 @@ def fold(Result, results, by=None, defines=[]):
|
||||
for k in it.chain(by or [], (k for k, _ in defines)):
|
||||
if k not in Result._by and k not in Result._fields:
|
||||
print("error: could not find field %r?" % k,
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
# filter by matching defines
|
||||
@@ -356,52 +356,55 @@ def table(Result, results, diff_results=None, *,
|
||||
|
||||
# organize by name
|
||||
table = {
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in results}
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in results}
|
||||
diff_table = {
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in diff_results or []}
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in diff_results or []}
|
||||
names = [name
|
||||
for name in table.keys() | diff_table.keys()
|
||||
if diff_results is None
|
||||
or all_
|
||||
or any(
|
||||
types[k].ratio(
|
||||
getattr(table.get(name), k, None),
|
||||
getattr(diff_table.get(name), k, None))
|
||||
for k in fields)]
|
||||
for name in table.keys() | diff_table.keys()
|
||||
if diff_results is None
|
||||
or all_
|
||||
or any(
|
||||
types[k].ratio(
|
||||
getattr(table.get(name), k, None),
|
||||
getattr(diff_table.get(name), k, None))
|
||||
for k in fields)]
|
||||
|
||||
# sort again, now with diff info, note that python's sort is stable
|
||||
names.sort()
|
||||
if diff_results is not None:
|
||||
names.sort(key=lambda n: tuple(
|
||||
types[k].ratio(
|
||||
getattr(table.get(n), k, None),
|
||||
getattr(diff_table.get(n), k, None))
|
||||
for k in fields),
|
||||
reverse=True)
|
||||
names.sort(
|
||||
key=lambda n: tuple(
|
||||
types[k].ratio(
|
||||
getattr(table.get(n), k, None),
|
||||
getattr(diff_table.get(n), k, None))
|
||||
for k in fields),
|
||||
reverse=True)
|
||||
if sort:
|
||||
for k, reverse in reversed(sort):
|
||||
names.sort(
|
||||
key=lambda n: tuple(
|
||||
(getattr(table[n], k),)
|
||||
if getattr(table.get(n), k, None) is not None else ()
|
||||
for k in ([k] if k else [
|
||||
k for k in Result._sort if k in fields])),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
key=lambda n: tuple(
|
||||
(getattr(table[n], k),)
|
||||
if getattr(table.get(n), k, None) is not None
|
||||
else ()
|
||||
for k in (
|
||||
[k] if k else [
|
||||
k for k in Result._sort
|
||||
if k in fields])),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
|
||||
|
||||
# build up our lines
|
||||
lines = []
|
||||
|
||||
# header
|
||||
header = [
|
||||
'%s%s' % (
|
||||
','.join(by),
|
||||
' (%d added, %d removed)' % (
|
||||
sum(1 for n in table if n not in diff_table),
|
||||
sum(1 for n in diff_table if n not in table))
|
||||
if diff_results is not None and not percent else '')
|
||||
header = ['%s%s' % (
|
||||
','.join(by),
|
||||
' (%d added, %d removed)' % (
|
||||
sum(1 for n in table if n not in diff_table),
|
||||
sum(1 for n in diff_table if n not in table))
|
||||
if diff_results is not None and not percent else '')
|
||||
if not summary else '']
|
||||
if diff_results is None:
|
||||
for k in fields:
|
||||
@@ -424,43 +427,43 @@ def table(Result, results, diff_results=None, *,
|
||||
if diff_results is None:
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(getattr(r, k).table(),
|
||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
(getattr(r, k).table(),
|
||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
elif percent:
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none,
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none,
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
else:
|
||||
for k in fields:
|
||||
entry.append(getattr(diff_r, k).table()
|
||||
if getattr(diff_r, k, None) is not None
|
||||
else types[k].none)
|
||||
if getattr(diff_r, k, None) is not None
|
||||
else types[k].none)
|
||||
for k in fields:
|
||||
entry.append(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(types[k].diff(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)),
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)] if t
|
||||
else [])(
|
||||
types[k].ratio(
|
||||
(types[k].diff(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
getattr(diff_r, k, None)),
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)] if t
|
||||
else [])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
return entry
|
||||
|
||||
# entries
|
||||
@@ -483,8 +486,8 @@ def table(Result, results, diff_results=None, *,
|
||||
|
||||
# homogenize
|
||||
lines = [
|
||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||
for line in lines]
|
||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||
for line in lines]
|
||||
|
||||
# find the best widths, note that column 0 contains the names and is
|
||||
# handled a bit differently
|
||||
@@ -498,11 +501,11 @@ def table(Result, results, diff_results=None, *,
|
||||
# print our table
|
||||
for line in lines:
|
||||
print('%-*s %s' % (
|
||||
widths[0], line[0][0],
|
||||
' '.join('%*s%-*s' % (
|
||||
widths[i], x[0],
|
||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||
for i, x in enumerate(line[1:], 1))))
|
||||
widths[0], line[0][0],
|
||||
' '.join('%*s%-*s' % (
|
||||
widths[i], x[0],
|
||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||
for i, x in enumerate(line[1:], 1))))
|
||||
|
||||
|
||||
def annotate(Result, results, *,
|
||||
@@ -529,14 +532,14 @@ def annotate(Result, results, *,
|
||||
or (branches and r.branches.a < r.branches.b)):
|
||||
if last is not None and line - last.stop <= args['context']:
|
||||
last = range(
|
||||
last.start,
|
||||
line+1+args['context'])
|
||||
last.start,
|
||||
line+1+args['context'])
|
||||
else:
|
||||
if last is not None:
|
||||
spans.append((last, func))
|
||||
last = range(
|
||||
line-args['context'],
|
||||
line+1+args['context'])
|
||||
line-args['context'],
|
||||
line+1+args['context'])
|
||||
func = r.function
|
||||
if last is not None:
|
||||
spans.append((last, func))
|
||||
@@ -552,11 +555,11 @@ def annotate(Result, results, *,
|
||||
if skipped:
|
||||
skipped = False
|
||||
print('%s@@ %s:%d: %s @@%s' % (
|
||||
'\x1b[36m' if args['color'] else '',
|
||||
path,
|
||||
i+1,
|
||||
next(iter(f for _, f in spans)),
|
||||
'\x1b[m' if args['color'] else ''))
|
||||
'\x1b[36m' if args['color'] else '',
|
||||
path,
|
||||
i+1,
|
||||
next(iter(f for _, f in spans)),
|
||||
'\x1b[m' if args['color'] else ''))
|
||||
|
||||
# build line
|
||||
if line.endswith('\n'):
|
||||
@@ -565,11 +568,11 @@ def annotate(Result, results, *,
|
||||
if i+1 in table:
|
||||
r = table[i+1]
|
||||
line = '%-*s // %s hits%s' % (
|
||||
args['width'],
|
||||
line,
|
||||
r.hits,
|
||||
', %s branches' % (r.branches,)
|
||||
if int(r.branches.b) else '')
|
||||
args['width'],
|
||||
line,
|
||||
r.hits,
|
||||
', %s branches' % (r.branches,)
|
||||
if int(r.branches.b) else '')
|
||||
|
||||
if args['color']:
|
||||
if lines and int(r.hits) == 0:
|
||||
@@ -612,11 +615,11 @@ def main(gcda_paths, *,
|
||||
continue
|
||||
try:
|
||||
results.append(CovResult(
|
||||
**{k: r[k] for k in CovResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k]
|
||||
for k in CovResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
**{k: r[k] for k in CovResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k]
|
||||
for k in CovResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
@@ -628,25 +631,27 @@ def main(gcda_paths, *,
|
||||
if sort:
|
||||
for k, reverse in reversed(sort):
|
||||
results.sort(
|
||||
key=lambda r: tuple(
|
||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||
for k in ([k] if k else CovResult._sort)),
|
||||
reverse=reverse ^ (not k or k in CovResult._fields))
|
||||
key=lambda r: tuple(
|
||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||
for k in ([k] if k else CovResult._sort)),
|
||||
reverse=reverse ^ (not k or k in CovResult._fields))
|
||||
|
||||
# write results to CSV
|
||||
if args.get('output'):
|
||||
with openio(args['output'], 'w') as f:
|
||||
writer = csv.DictWriter(f,
|
||||
(by if by is not None else CovResult._by)
|
||||
+ [k for k in (
|
||||
fields if fields is not None else CovResult._fields)])
|
||||
(by if by is not None else CovResult._by)
|
||||
+ [k for k in (
|
||||
fields if fields is not None
|
||||
else CovResult._fields)])
|
||||
writer.writeheader()
|
||||
for r in results:
|
||||
writer.writerow(
|
||||
{k: getattr(r, k) for k in (
|
||||
by if by is not None else CovResult._by)}
|
||||
| {k: getattr(r, k) for k in (
|
||||
fields if fields is not None else CovResult._fields)})
|
||||
{k: getattr(r, k) for k in (
|
||||
by if by is not None else CovResult._by)}
|
||||
| {k: getattr(r, k) for k in (
|
||||
fields if fields is not None
|
||||
else CovResult._fields)})
|
||||
|
||||
# find previous results?
|
||||
if args.get('diff'):
|
||||
@@ -664,19 +669,17 @@ def main(gcda_paths, *,
|
||||
continue
|
||||
try:
|
||||
diff_results.append(CovResult(
|
||||
**{k: r[k] for k in CovResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k]
|
||||
for k in CovResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
**{k: r[k] for k in CovResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k] for k in CovResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
except TypeError:
|
||||
pass
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
# fold
|
||||
diff_results = fold(CovResult, diff_results,
|
||||
by=by, defines=defines)
|
||||
diff_results = fold(CovResult, diff_results, by=by, defines=defines)
|
||||
|
||||
# print table
|
||||
if not args.get('quiet'):
|
||||
@@ -688,13 +691,13 @@ def main(gcda_paths, *,
|
||||
else:
|
||||
# print table
|
||||
table(CovResult, results,
|
||||
diff_results if args.get('diff') else None,
|
||||
by=by if by is not None else ['function'],
|
||||
fields=fields if fields is not None
|
||||
else ['lines', 'branches'] if not hits
|
||||
else ['calls', 'hits'],
|
||||
sort=sort,
|
||||
**args)
|
||||
diff_results if args.get('diff') else None,
|
||||
by=by if by is not None else ['function'],
|
||||
fields=fields if fields is not None
|
||||
else ['lines', 'branches'] if not hits
|
||||
else ['calls', 'hits'],
|
||||
sort=sort,
|
||||
**args)
|
||||
|
||||
# catch lack of coverage
|
||||
if args.get('error_on_lines') and any(
|
||||
@@ -709,132 +712,133 @@ if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Find coverage info after running tests.",
|
||||
allow_abbrev=False)
|
||||
description="Find coverage info after running tests.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'gcda_paths',
|
||||
nargs='*',
|
||||
help="Input *.gcda files.")
|
||||
'gcda_paths',
|
||||
nargs='*',
|
||||
help="Input *.gcda files.")
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help="Output commands that run behind the scenes.")
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help="Output commands that run behind the scenes.")
|
||||
parser.add_argument(
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help="Don't show anything, useful with -o.")
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help="Don't show anything, useful with -o.")
|
||||
parser.add_argument(
|
||||
'-o', '--output',
|
||||
help="Specify CSV file to store results.")
|
||||
'-o', '--output',
|
||||
help="Specify CSV file to store results.")
|
||||
parser.add_argument(
|
||||
'-u', '--use',
|
||||
help="Don't parse anything, use this CSV file.")
|
||||
'-u', '--use',
|
||||
help="Don't parse anything, use this CSV file.")
|
||||
parser.add_argument(
|
||||
'-d', '--diff',
|
||||
help="Specify CSV file to diff against.")
|
||||
'-d', '--diff',
|
||||
help="Specify CSV file to diff against.")
|
||||
parser.add_argument(
|
||||
'-a', '--all',
|
||||
action='store_true',
|
||||
help="Show all, not just the ones that changed.")
|
||||
'-a', '--all',
|
||||
action='store_true',
|
||||
help="Show all, not just the ones that changed.")
|
||||
parser.add_argument(
|
||||
'-p', '--percent',
|
||||
action='store_true',
|
||||
help="Only show percentage change, not a full diff.")
|
||||
'-p', '--percent',
|
||||
action='store_true',
|
||||
help="Only show percentage change, not a full diff.")
|
||||
parser.add_argument(
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
choices=CovResult._by,
|
||||
help="Group by this field.")
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
choices=CovResult._by,
|
||||
help="Group by this field.")
|
||||
parser.add_argument(
|
||||
'-f', '--field',
|
||||
dest='fields',
|
||||
action='append',
|
||||
choices=CovResult._fields,
|
||||
help="Show this field.")
|
||||
'-f', '--field',
|
||||
dest='fields',
|
||||
action='append',
|
||||
choices=CovResult._fields,
|
||||
help="Show this field.")
|
||||
parser.add_argument(
|
||||
'-D', '--define',
|
||||
dest='defines',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
help="Only include results where this field is this value.")
|
||||
'-D', '--define',
|
||||
dest='defines',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
help="Only include results where this field is this value.")
|
||||
class AppendSort(argparse.Action):
|
||||
def __call__(self, parser, namespace, value, option):
|
||||
if namespace.sort is None:
|
||||
namespace.sort = []
|
||||
namespace.sort.append((value, True if option == '-S' else False))
|
||||
parser.add_argument(
|
||||
'-s', '--sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field.")
|
||||
'-s', '--sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field.")
|
||||
parser.add_argument(
|
||||
'-S', '--reverse-sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field, but backwards.")
|
||||
'-S', '--reverse-sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field, but backwards.")
|
||||
parser.add_argument(
|
||||
'-Y', '--summary',
|
||||
action='store_true',
|
||||
help="Only show the total.")
|
||||
'-Y', '--summary',
|
||||
action='store_true',
|
||||
help="Only show the total.")
|
||||
parser.add_argument(
|
||||
'-F', '--source',
|
||||
dest='sources',
|
||||
action='append',
|
||||
help="Only consider definitions in this file. Defaults to anything "
|
||||
"in the current directory.")
|
||||
'-F', '--source',
|
||||
dest='sources',
|
||||
action='append',
|
||||
help="Only consider definitions in this file. Defaults to "
|
||||
"anything in the current directory.")
|
||||
parser.add_argument(
|
||||
'--everything',
|
||||
action='store_true',
|
||||
help="Include builtin and libc specific symbols.")
|
||||
'--everything',
|
||||
action='store_true',
|
||||
help="Include builtin and libc specific symbols.")
|
||||
parser.add_argument(
|
||||
'--hits',
|
||||
action='store_true',
|
||||
help="Show total hits instead of coverage.")
|
||||
'--hits',
|
||||
action='store_true',
|
||||
help="Show total hits instead of coverage.")
|
||||
parser.add_argument(
|
||||
'-A', '--annotate',
|
||||
action='store_true',
|
||||
help="Show source files annotated with coverage info.")
|
||||
'-A', '--annotate',
|
||||
action='store_true',
|
||||
help="Show source files annotated with coverage info.")
|
||||
parser.add_argument(
|
||||
'-L', '--lines',
|
||||
action='store_true',
|
||||
help="Show uncovered lines.")
|
||||
'-L', '--lines',
|
||||
action='store_true',
|
||||
help="Show uncovered lines.")
|
||||
parser.add_argument(
|
||||
'-B', '--branches',
|
||||
action='store_true',
|
||||
help="Show uncovered branches.")
|
||||
'-B', '--branches',
|
||||
action='store_true',
|
||||
help="Show uncovered branches.")
|
||||
parser.add_argument(
|
||||
'-C', '--context',
|
||||
type=lambda x: int(x, 0),
|
||||
default=3,
|
||||
help="Show n additional lines of context. Defaults to 3.")
|
||||
'-C', '--context',
|
||||
type=lambda x: int(x, 0),
|
||||
default=3,
|
||||
help="Show n additional lines of context. Defaults to 3.")
|
||||
parser.add_argument(
|
||||
'-W', '--width',
|
||||
type=lambda x: int(x, 0),
|
||||
default=80,
|
||||
help="Assume source is styled with this many columns. Defaults to 80.")
|
||||
'-W', '--width',
|
||||
type=lambda x: int(x, 0),
|
||||
default=80,
|
||||
help="Assume source is styled with this many columns. Defaults "
|
||||
"to 80.")
|
||||
parser.add_argument(
|
||||
'--color',
|
||||
choices=['never', 'always', 'auto'],
|
||||
default='auto',
|
||||
help="When to use terminal colors. Defaults to 'auto'.")
|
||||
'--color',
|
||||
choices=['never', 'always', 'auto'],
|
||||
default='auto',
|
||||
help="When to use terminal colors. Defaults to 'auto'.")
|
||||
parser.add_argument(
|
||||
'-e', '--error-on-lines',
|
||||
action='store_true',
|
||||
help="Error if any lines are not covered.")
|
||||
'-e', '--error-on-lines',
|
||||
action='store_true',
|
||||
help="Error if any lines are not covered.")
|
||||
parser.add_argument(
|
||||
'-E', '--error-on-branches',
|
||||
action='store_true',
|
||||
help="Error if any branches are not covered.")
|
||||
'-E', '--error-on-branches',
|
||||
action='store_true',
|
||||
help="Error if any branches are not covered.")
|
||||
parser.add_argument(
|
||||
'--gcov-path',
|
||||
default=GCOV_PATH,
|
||||
type=lambda x: x.split(),
|
||||
help="Path to the gcov executable, may include paths. "
|
||||
"Defaults to %r." % GCOV_PATH)
|
||||
'--gcov-path',
|
||||
default=GCOV_PATH,
|
||||
type=lambda x: x.split(),
|
||||
help="Path to the gcov executable, may include paths. "
|
||||
"Defaults to %r." % GCOV_PATH)
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+14
-13
@@ -57,24 +57,25 @@ def main(paths, **args):
|
||||
else:
|
||||
print('%08x' % crc)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Calculates crc32cs.",
|
||||
allow_abbrev=False)
|
||||
description="Calculates crc32cs.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'paths',
|
||||
nargs='*',
|
||||
help="Paths to read. Reads stdin by default.")
|
||||
'paths',
|
||||
nargs='*',
|
||||
help="Paths to read. Reads stdin by default.")
|
||||
parser.add_argument(
|
||||
'-x', '--hex',
|
||||
action='store_true',
|
||||
help="Interpret as a sequence of hex bytes.")
|
||||
'-x', '--hex',
|
||||
action='store_true',
|
||||
help="Interpret as a sequence of hex bytes.")
|
||||
parser.add_argument(
|
||||
'-s', '--string',
|
||||
action='store_true',
|
||||
help="Interpret as strings.")
|
||||
'-s', '--string',
|
||||
action='store_true',
|
||||
help="Interpret as strings.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+207
-202
@@ -115,11 +115,11 @@ class DataResult(co.namedtuple('DataResult', [
|
||||
__slots__ = ()
|
||||
def __new__(cls, file='', function='', size=0):
|
||||
return super().__new__(cls, file, function,
|
||||
RInt(size))
|
||||
RInt(size))
|
||||
|
||||
def __add__(self, other):
|
||||
return DataResult(self.file, self.function,
|
||||
self.size + other.size)
|
||||
self.size + other.size)
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
@@ -140,18 +140,18 @@ def collect(obj_paths, *,
|
||||
everything=False,
|
||||
**args):
|
||||
size_pattern = re.compile(
|
||||
'^(?P<size>[0-9a-fA-F]+)' +
|
||||
' (?P<type>[%s])' % re.escape(nm_types) +
|
||||
' (?P<func>.+?)$')
|
||||
'^(?P<size>[0-9a-fA-F]+)'
|
||||
+ ' (?P<type>[%s])' % re.escape(nm_types)
|
||||
+ ' (?P<func>.+?)$')
|
||||
line_pattern = re.compile(
|
||||
'^\s+(?P<no>[0-9]+)'
|
||||
'(?:\s+(?P<dir>[0-9]+))?'
|
||||
'\s+.*'
|
||||
'\s+(?P<path>[^\s]+)$')
|
||||
'^\s+(?P<no>[0-9]+)'
|
||||
'(?:\s+(?P<dir>[0-9]+))?'
|
||||
'\s+.*'
|
||||
'\s+(?P<path>[^\s]+)$')
|
||||
info_pattern = re.compile(
|
||||
'^(?:.*(?P<tag>DW_TAG_[a-z_]+).*'
|
||||
'|.*DW_AT_name.*:\s*(?P<name>[^:\s]+)\s*'
|
||||
'|.*DW_AT_decl_file.*:\s*(?P<file>[0-9]+)\s*)$')
|
||||
'^(?:.*(?P<tag>DW_TAG_[a-z_]+).*'
|
||||
'|.*DW_AT_name.*:\s*(?P<name>[^:\s]+)\s*'
|
||||
'|.*DW_AT_decl_file.*:\s*(?P<file>[0-9]+)\s*)$')
|
||||
|
||||
results = []
|
||||
for path in obj_paths:
|
||||
@@ -165,11 +165,11 @@ def collect(obj_paths, *,
|
||||
if args.get('verbose'):
|
||||
print(' '.join(shlex.quote(c) for c in cmd))
|
||||
proc = sp.Popen(cmd,
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
for line in proc.stdout:
|
||||
m = size_pattern.match(line)
|
||||
if m:
|
||||
@@ -178,8 +178,8 @@ def collect(obj_paths, *,
|
||||
if not everything and func.startswith('__'):
|
||||
continue
|
||||
results_.append(DataResult(
|
||||
file, func,
|
||||
int(m.group('size'), 16)))
|
||||
file, func,
|
||||
int(m.group('size'), 16)))
|
||||
proc.wait()
|
||||
if proc.returncode != 0:
|
||||
if not args.get('verbose'):
|
||||
@@ -196,11 +196,11 @@ def collect(obj_paths, *,
|
||||
if args.get('verbose'):
|
||||
print(' '.join(shlex.quote(c) for c in cmd))
|
||||
proc = sp.Popen(cmd,
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
for line in proc.stdout:
|
||||
# note that files contain references to dirs, which we
|
||||
# dereference as soon as we see them as each file table follows a
|
||||
@@ -215,8 +215,8 @@ def collect(obj_paths, *,
|
||||
dir = int(m.group('dir'))
|
||||
if dir in dirs:
|
||||
files[int(m.group('no'))] = os.path.join(
|
||||
dirs[dir],
|
||||
m.group('path'))
|
||||
dirs[dir],
|
||||
m.group('path'))
|
||||
else:
|
||||
files[int(m.group('no'))] = m.group('path')
|
||||
proc.wait()
|
||||
@@ -241,11 +241,11 @@ def collect(obj_paths, *,
|
||||
if args.get('verbose'):
|
||||
print(' '.join(shlex.quote(c) for c in cmd))
|
||||
proc = sp.Popen(cmd,
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
for line in proc.stdout:
|
||||
# state machine here to find definitions
|
||||
m = info_pattern.match(line)
|
||||
@@ -279,17 +279,16 @@ def collect(obj_paths, *,
|
||||
file = defs[r.function]
|
||||
else:
|
||||
_, file = max(
|
||||
defs.items(),
|
||||
key=lambda d: difflib.SequenceMatcher(None,
|
||||
d[0],
|
||||
r.function, False).ratio())
|
||||
defs.items(),
|
||||
key=lambda d: difflib.SequenceMatcher(None,
|
||||
d[0],
|
||||
r.function, False).ratio())
|
||||
else:
|
||||
file = r.file
|
||||
|
||||
# ignore filtered sources
|
||||
if sources is not None:
|
||||
if not any(
|
||||
os.path.abspath(file) == os.path.abspath(s)
|
||||
if not any(os.path.abspath(file) == os.path.abspath(s)
|
||||
for s in sources):
|
||||
continue
|
||||
else:
|
||||
@@ -319,7 +318,7 @@ def fold(Result, results, by=None, defines=[]):
|
||||
for k in it.chain(by or [], (k for k, _ in defines)):
|
||||
if k not in Result._by and k not in Result._fields:
|
||||
print("error: could not find field %r?" % k,
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
# filter by matching defines
|
||||
@@ -368,52 +367,55 @@ def table(Result, results, diff_results=None, *,
|
||||
|
||||
# organize by name
|
||||
table = {
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in results}
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in results}
|
||||
diff_table = {
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in diff_results or []}
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in diff_results or []}
|
||||
names = [name
|
||||
for name in table.keys() | diff_table.keys()
|
||||
if diff_results is None
|
||||
or all_
|
||||
or any(
|
||||
types[k].ratio(
|
||||
getattr(table.get(name), k, None),
|
||||
getattr(diff_table.get(name), k, None))
|
||||
for k in fields)]
|
||||
for name in table.keys() | diff_table.keys()
|
||||
if diff_results is None
|
||||
or all_
|
||||
or any(
|
||||
types[k].ratio(
|
||||
getattr(table.get(name), k, None),
|
||||
getattr(diff_table.get(name), k, None))
|
||||
for k in fields)]
|
||||
|
||||
# sort again, now with diff info, note that python's sort is stable
|
||||
names.sort()
|
||||
if diff_results is not None:
|
||||
names.sort(key=lambda n: tuple(
|
||||
types[k].ratio(
|
||||
getattr(table.get(n), k, None),
|
||||
getattr(diff_table.get(n), k, None))
|
||||
for k in fields),
|
||||
reverse=True)
|
||||
names.sort(
|
||||
key=lambda n: tuple(
|
||||
types[k].ratio(
|
||||
getattr(table.get(n), k, None),
|
||||
getattr(diff_table.get(n), k, None))
|
||||
for k in fields),
|
||||
reverse=True)
|
||||
if sort:
|
||||
for k, reverse in reversed(sort):
|
||||
names.sort(
|
||||
key=lambda n: tuple(
|
||||
(getattr(table[n], k),)
|
||||
if getattr(table.get(n), k, None) is not None else ()
|
||||
for k in ([k] if k else [
|
||||
k for k in Result._sort if k in fields])),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
key=lambda n: tuple(
|
||||
(getattr(table[n], k),)
|
||||
if getattr(table.get(n), k, None) is not None
|
||||
else ()
|
||||
for k in (
|
||||
[k] if k else [
|
||||
k for k in Result._sort
|
||||
if k in fields])),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
|
||||
|
||||
# build up our lines
|
||||
lines = []
|
||||
|
||||
# header
|
||||
header = [
|
||||
'%s%s' % (
|
||||
','.join(by),
|
||||
' (%d added, %d removed)' % (
|
||||
sum(1 for n in table if n not in diff_table),
|
||||
sum(1 for n in diff_table if n not in table))
|
||||
if diff_results is not None and not percent else '')
|
||||
header = ['%s%s' % (
|
||||
','.join(by),
|
||||
' (%d added, %d removed)' % (
|
||||
sum(1 for n in table if n not in diff_table),
|
||||
sum(1 for n in diff_table if n not in table))
|
||||
if diff_results is not None and not percent else '')
|
||||
if not summary else '']
|
||||
if diff_results is None:
|
||||
for k in fields:
|
||||
@@ -436,43 +438,43 @@ def table(Result, results, diff_results=None, *,
|
||||
if diff_results is None:
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(getattr(r, k).table(),
|
||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
(getattr(r, k).table(),
|
||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
elif percent:
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none,
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none,
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
else:
|
||||
for k in fields:
|
||||
entry.append(getattr(diff_r, k).table()
|
||||
if getattr(diff_r, k, None) is not None
|
||||
else types[k].none)
|
||||
if getattr(diff_r, k, None) is not None
|
||||
else types[k].none)
|
||||
for k in fields:
|
||||
entry.append(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(types[k].diff(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)),
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)] if t
|
||||
else [])(
|
||||
types[k].ratio(
|
||||
(types[k].diff(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
getattr(diff_r, k, None)),
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)] if t
|
||||
else [])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
return entry
|
||||
|
||||
# entries
|
||||
@@ -495,8 +497,8 @@ def table(Result, results, diff_results=None, *,
|
||||
|
||||
# homogenize
|
||||
lines = [
|
||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||
for line in lines]
|
||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||
for line in lines]
|
||||
|
||||
# find the best widths, note that column 0 contains the names and is
|
||||
# handled a bit differently
|
||||
@@ -510,11 +512,11 @@ def table(Result, results, diff_results=None, *,
|
||||
# print our table
|
||||
for line in lines:
|
||||
print('%-*s %s' % (
|
||||
widths[0], line[0][0],
|
||||
' '.join('%*s%-*s' % (
|
||||
widths[i], x[0],
|
||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||
for i, x in enumerate(line[1:], 1))))
|
||||
widths[0], line[0][0],
|
||||
' '.join('%*s%-*s' % (
|
||||
widths[i], x[0],
|
||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||
for i, x in enumerate(line[1:], 1))))
|
||||
|
||||
|
||||
def main(obj_paths, *,
|
||||
@@ -537,10 +539,10 @@ def main(obj_paths, *,
|
||||
|
||||
try:
|
||||
results.append(DataResult(
|
||||
**{k: r[k] for k in DataResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k] for k in DataResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
**{k: r[k] for k in DataResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k] for k in DataResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
@@ -552,25 +554,27 @@ def main(obj_paths, *,
|
||||
if sort:
|
||||
for k, reverse in reversed(sort):
|
||||
results.sort(
|
||||
key=lambda r: tuple(
|
||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||
for k in ([k] if k else DataResult._sort)),
|
||||
reverse=reverse ^ (not k or k in DataResult._fields))
|
||||
key=lambda r: tuple(
|
||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||
for k in ([k] if k else DataResult._sort)),
|
||||
reverse=reverse ^ (not k or k in DataResult._fields))
|
||||
|
||||
# write results to CSV
|
||||
if args.get('output'):
|
||||
with openio(args['output'], 'w') as f:
|
||||
writer = csv.DictWriter(f,
|
||||
(by if by is not None else DataResult._by)
|
||||
+ [k for k in (
|
||||
fields if fields is not None else DataResult._fields)])
|
||||
(by if by is not None else DataResult._by)
|
||||
+ [k for k in (
|
||||
fields if fields is not None
|
||||
else DataResult._fields)])
|
||||
writer.writeheader()
|
||||
for r in results:
|
||||
writer.writerow(
|
||||
{k: getattr(r, k) for k in (
|
||||
by if by is not None else DataResult._by)}
|
||||
| {k: getattr(r, k) for k in (
|
||||
fields if fields is not None else DataResult._fields)})
|
||||
{k: getattr(r, k) for k in (
|
||||
by if by is not None else DataResult._by)}
|
||||
| {k: getattr(r, k) for k in (
|
||||
fields if fields is not None
|
||||
else DataResult._fields)})
|
||||
|
||||
# find previous results?
|
||||
if args.get('diff'):
|
||||
@@ -588,10 +592,10 @@ def main(obj_paths, *,
|
||||
continue
|
||||
try:
|
||||
diff_results.append(DataResult(
|
||||
**{k: r[k] for k in DataResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k] for k in DataResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
**{k: r[k] for k in DataResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k] for k in DataResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
except TypeError:
|
||||
pass
|
||||
except FileNotFoundError:
|
||||
@@ -603,115 +607,116 @@ def main(obj_paths, *,
|
||||
# print table
|
||||
if not args.get('quiet'):
|
||||
table(DataResult, results,
|
||||
diff_results if args.get('diff') else None,
|
||||
by=by if by is not None else ['function'],
|
||||
fields=fields,
|
||||
sort=sort,
|
||||
**args)
|
||||
diff_results if args.get('diff') else None,
|
||||
by=by if by is not None else ['function'],
|
||||
fields=fields,
|
||||
sort=sort,
|
||||
**args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Find data size at the function level.",
|
||||
allow_abbrev=False)
|
||||
description="Find data size at the function level.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'obj_paths',
|
||||
nargs='*',
|
||||
help="Input *.o files.")
|
||||
'obj_paths',
|
||||
nargs='*',
|
||||
help="Input *.o files.")
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help="Output commands that run behind the scenes.")
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help="Output commands that run behind the scenes.")
|
||||
parser.add_argument(
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help="Don't show anything, useful with -o.")
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help="Don't show anything, useful with -o.")
|
||||
parser.add_argument(
|
||||
'-o', '--output',
|
||||
help="Specify CSV file to store results.")
|
||||
'-o', '--output',
|
||||
help="Specify CSV file to store results.")
|
||||
parser.add_argument(
|
||||
'-u', '--use',
|
||||
help="Don't parse anything, use this CSV file.")
|
||||
'-u', '--use',
|
||||
help="Don't parse anything, use this CSV file.")
|
||||
parser.add_argument(
|
||||
'-d', '--diff',
|
||||
help="Specify CSV file to diff against.")
|
||||
'-d', '--diff',
|
||||
help="Specify CSV file to diff against.")
|
||||
parser.add_argument(
|
||||
'-a', '--all',
|
||||
action='store_true',
|
||||
help="Show all, not just the ones that changed.")
|
||||
'-a', '--all',
|
||||
action='store_true',
|
||||
help="Show all, not just the ones that changed.")
|
||||
parser.add_argument(
|
||||
'-p', '--percent',
|
||||
action='store_true',
|
||||
help="Only show percentage change, not a full diff.")
|
||||
'-p', '--percent',
|
||||
action='store_true',
|
||||
help="Only show percentage change, not a full diff.")
|
||||
parser.add_argument(
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
choices=DataResult._by,
|
||||
help="Group by this field.")
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
choices=DataResult._by,
|
||||
help="Group by this field.")
|
||||
parser.add_argument(
|
||||
'-f', '--field',
|
||||
dest='fields',
|
||||
action='append',
|
||||
choices=DataResult._fields,
|
||||
help="Show this field.")
|
||||
'-f', '--field',
|
||||
dest='fields',
|
||||
action='append',
|
||||
choices=DataResult._fields,
|
||||
help="Show this field.")
|
||||
parser.add_argument(
|
||||
'-D', '--define',
|
||||
dest='defines',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
help="Only include results where this field is this value.")
|
||||
'-D', '--define',
|
||||
dest='defines',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
help="Only include results where this field is this value.")
|
||||
class AppendSort(argparse.Action):
|
||||
def __call__(self, parser, namespace, value, option):
|
||||
if namespace.sort is None:
|
||||
namespace.sort = []
|
||||
namespace.sort.append((value, True if option == '-S' else False))
|
||||
parser.add_argument(
|
||||
'-s', '--sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field.")
|
||||
'-s', '--sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field.")
|
||||
parser.add_argument(
|
||||
'-S', '--reverse-sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field, but backwards.")
|
||||
'-S', '--reverse-sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field, but backwards.")
|
||||
parser.add_argument(
|
||||
'-Y', '--summary',
|
||||
action='store_true',
|
||||
help="Only show the total.")
|
||||
'-Y', '--summary',
|
||||
action='store_true',
|
||||
help="Only show the total.")
|
||||
parser.add_argument(
|
||||
'-F', '--source',
|
||||
dest='sources',
|
||||
action='append',
|
||||
help="Only consider definitions in this file. Defaults to anything "
|
||||
"in the current directory.")
|
||||
'-F', '--source',
|
||||
dest='sources',
|
||||
action='append',
|
||||
help="Only consider definitions in this file. Defaults to "
|
||||
"anything in the current directory.")
|
||||
parser.add_argument(
|
||||
'--everything',
|
||||
action='store_true',
|
||||
help="Include builtin and libc specific symbols.")
|
||||
'--everything',
|
||||
action='store_true',
|
||||
help="Include builtin and libc specific symbols.")
|
||||
parser.add_argument(
|
||||
'--nm-types',
|
||||
default=NM_TYPES,
|
||||
help="Type of symbols to report, this uses the same single-character "
|
||||
"type-names emitted by nm. Defaults to %r." % NM_TYPES)
|
||||
'--nm-types',
|
||||
default=NM_TYPES,
|
||||
help="Type of symbols to report, this uses the same "
|
||||
"single-character type-names emitted by nm. Defaults to "
|
||||
"%r." % NM_TYPES)
|
||||
parser.add_argument(
|
||||
'--nm-path',
|
||||
type=lambda x: x.split(),
|
||||
default=NM_PATH,
|
||||
help="Path to the nm executable, may include flags. "
|
||||
"Defaults to %r." % NM_PATH)
|
||||
'--nm-path',
|
||||
type=lambda x: x.split(),
|
||||
default=NM_PATH,
|
||||
help="Path to the nm executable, may include flags. "
|
||||
"Defaults to %r." % NM_PATH)
|
||||
parser.add_argument(
|
||||
'--objdump-path',
|
||||
type=lambda x: x.split(),
|
||||
default=OBJDUMP_PATH,
|
||||
help="Path to the objdump executable, may include flags. "
|
||||
"Defaults to %r." % OBJDUMP_PATH)
|
||||
'--objdump-path',
|
||||
type=lambda x: x.split(),
|
||||
default=OBJDUMP_PATH,
|
||||
help="Path to the objdump executable, may include flags. "
|
||||
"Defaults to %r." % OBJDUMP_PATH)
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+51
-49
@@ -66,12 +66,12 @@ def rbydaddr(s):
|
||||
def xxd(data, width=16):
|
||||
for i in range(0, len(data), width):
|
||||
yield '%-*s %-*s' % (
|
||||
3*width,
|
||||
' '.join('%02x' % b for b in data[i:i+width]),
|
||||
width,
|
||||
''.join(
|
||||
b if b >= ' ' and b <= '~' else '.'
|
||||
for b in map(chr, data[i:i+width])))
|
||||
3*width,
|
||||
' '.join('%02x' % b for b in data[i:i+width]),
|
||||
width,
|
||||
''.join(
|
||||
b if b >= ' ' and b <= '~' else '.'
|
||||
for b in map(chr, data[i:i+width])))
|
||||
|
||||
def crc32c(data, crc=0):
|
||||
crc ^= 0xffffffff
|
||||
@@ -99,7 +99,7 @@ def main(disk, block=None, *,
|
||||
|
||||
if len(block) > 1:
|
||||
print("error: more than one block address?",
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
block = block[0]
|
||||
@@ -112,17 +112,18 @@ def main(disk, block=None, *,
|
||||
|
||||
# block may also encode an offset
|
||||
block, off, size = (
|
||||
block[0] if isinstance(block, tuple) else block,
|
||||
off[0] if isinstance(off, tuple)
|
||||
else off if off is not None
|
||||
else size[0] if isinstance(size, tuple) and len(size) > 1
|
||||
else block[1] if isinstance(block, tuple)
|
||||
else None,
|
||||
size[1] - size[0] if isinstance(size, tuple) and len(size) > 1
|
||||
else size[0] if isinstance(size, tuple)
|
||||
else size if size is not None
|
||||
else off[1] - off[0] if isinstance(off, tuple) and len(off) > 1
|
||||
else block_size)
|
||||
block[0] if isinstance(block, tuple) else block,
|
||||
off[0] if isinstance(off, tuple)
|
||||
else off if off is not None
|
||||
else size[0] if isinstance(size, tuple) and len(size) > 1
|
||||
else block[1] if isinstance(block, tuple)
|
||||
else None,
|
||||
size[1] - size[0] if isinstance(size, tuple) and len(size) > 1
|
||||
else size[0] if isinstance(size, tuple)
|
||||
else size if size is not None
|
||||
else off[1] - off[0]
|
||||
if isinstance(off, tuple) and len(off) > 1
|
||||
else block_size)
|
||||
|
||||
# read the block
|
||||
f.seek((block * block_size) + (off or 0))
|
||||
@@ -133,50 +134,51 @@ def main(disk, block=None, *,
|
||||
|
||||
# print the header
|
||||
print('block %s, size %d, cksum %08x' % (
|
||||
'0x%x.%x' % (block, off)
|
||||
if off is not None
|
||||
else '0x%x' % block,
|
||||
size,
|
||||
cksum))
|
||||
'0x%x.%x' % (block, off)
|
||||
if off is not None
|
||||
else '0x%x' % block,
|
||||
size,
|
||||
cksum))
|
||||
|
||||
# render the hex view
|
||||
for o, line in enumerate(xxd(data)):
|
||||
print('%08x: %s' % ((off or 0) + 16*o, line))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Debug block devices.",
|
||||
allow_abbrev=False)
|
||||
description="Debug block devices.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'disk',
|
||||
help="File containing the block device.")
|
||||
'disk',
|
||||
help="File containing the block device.")
|
||||
parser.add_argument(
|
||||
'block',
|
||||
nargs='?',
|
||||
type=rbydaddr,
|
||||
help="Block address.")
|
||||
'block',
|
||||
nargs='?',
|
||||
type=rbydaddr,
|
||||
help="Block address.")
|
||||
parser.add_argument(
|
||||
'-b', '--block-size',
|
||||
type=bdgeom,
|
||||
help="Block size/geometry in bytes.")
|
||||
'-b', '--block-size',
|
||||
type=bdgeom,
|
||||
help="Block size/geometry in bytes.")
|
||||
parser.add_argument(
|
||||
'--block-count',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Block count in blocks.")
|
||||
'--block-count',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Block count in blocks.")
|
||||
parser.add_argument(
|
||||
'--off',
|
||||
type=lambda x: tuple(
|
||||
int(x, 0) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Show a specific offset, may be a range.")
|
||||
'--off',
|
||||
type=lambda x: tuple(
|
||||
int(x, 0) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Show a specific offset, may be a range.")
|
||||
parser.add_argument(
|
||||
'-n', '--size',
|
||||
type=lambda x: tuple(
|
||||
int(x, 0) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Show this many bytes, may be a range.")
|
||||
'-n', '--size',
|
||||
type=lambda x: tuple(
|
||||
int(x, 0) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Show this many bytes, may be a range.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+229
-220
@@ -58,14 +58,14 @@ COLORS = ['33', '34', '32', '90']
|
||||
|
||||
CHARS_DOTS = " .':"
|
||||
CHARS_BRAILLE = (
|
||||
'⠀⢀⡀⣀⠠⢠⡠⣠⠄⢄⡄⣄⠤⢤⡤⣤' '⠐⢐⡐⣐⠰⢰⡰⣰⠔⢔⡔⣔⠴⢴⡴⣴'
|
||||
'⠂⢂⡂⣂⠢⢢⡢⣢⠆⢆⡆⣆⠦⢦⡦⣦' '⠒⢒⡒⣒⠲⢲⡲⣲⠖⢖⡖⣖⠶⢶⡶⣶'
|
||||
'⠈⢈⡈⣈⠨⢨⡨⣨⠌⢌⡌⣌⠬⢬⡬⣬' '⠘⢘⡘⣘⠸⢸⡸⣸⠜⢜⡜⣜⠼⢼⡼⣼'
|
||||
'⠊⢊⡊⣊⠪⢪⡪⣪⠎⢎⡎⣎⠮⢮⡮⣮' '⠚⢚⡚⣚⠺⢺⡺⣺⠞⢞⡞⣞⠾⢾⡾⣾'
|
||||
'⠁⢁⡁⣁⠡⢡⡡⣡⠅⢅⡅⣅⠥⢥⡥⣥' '⠑⢑⡑⣑⠱⢱⡱⣱⠕⢕⡕⣕⠵⢵⡵⣵'
|
||||
'⠃⢃⡃⣃⠣⢣⡣⣣⠇⢇⡇⣇⠧⢧⡧⣧' '⠓⢓⡓⣓⠳⢳⡳⣳⠗⢗⡗⣗⠷⢷⡷⣷'
|
||||
'⠉⢉⡉⣉⠩⢩⡩⣩⠍⢍⡍⣍⠭⢭⡭⣭' '⠙⢙⡙⣙⠹⢹⡹⣹⠝⢝⡝⣝⠽⢽⡽⣽'
|
||||
'⠋⢋⡋⣋⠫⢫⡫⣫⠏⢏⡏⣏⠯⢯⡯⣯' '⠛⢛⡛⣛⠻⢻⡻⣻⠟⢟⡟⣟⠿⢿⡿⣿')
|
||||
'⠀⢀⡀⣀⠠⢠⡠⣠⠄⢄⡄⣄⠤⢤⡤⣤' '⠐⢐⡐⣐⠰⢰⡰⣰⠔⢔⡔⣔⠴⢴⡴⣴'
|
||||
'⠂⢂⡂⣂⠢⢢⡢⣢⠆⢆⡆⣆⠦⢦⡦⣦' '⠒⢒⡒⣒⠲⢲⡲⣲⠖⢖⡖⣖⠶⢶⡶⣶'
|
||||
'⠈⢈⡈⣈⠨⢨⡨⣨⠌⢌⡌⣌⠬⢬⡬⣬' '⠘⢘⡘⣘⠸⢸⡸⣸⠜⢜⡜⣜⠼⢼⡼⣼'
|
||||
'⠊⢊⡊⣊⠪⢪⡪⣪⠎⢎⡎⣎⠮⢮⡮⣮' '⠚⢚⡚⣚⠺⢺⡺⣺⠞⢞⡞⣞⠾⢾⡾⣾'
|
||||
'⠁⢁⡁⣁⠡⢡⡡⣡⠅⢅⡅⣅⠥⢥⡥⣥' '⠑⢑⡑⣑⠱⢱⡱⣱⠕⢕⡕⣕⠵⢵⡵⣵'
|
||||
'⠃⢃⡃⣃⠣⢣⡣⣣⠇⢇⡇⣇⠧⢧⡧⣧' '⠓⢓⡓⣓⠳⢳⡳⣳⠗⢗⡗⣗⠷⢷⡷⣷'
|
||||
'⠉⢉⡉⣉⠩⢩⡩⣩⠍⢍⡍⣍⠭⢭⡭⣭' '⠙⢙⡙⣙⠹⢹⡹⣹⠝⢝⡝⣝⠽⢽⡽⣽'
|
||||
'⠋⢋⡋⣋⠫⢫⡫⣫⠏⢏⡏⣏⠯⢯⡯⣯' '⠛⢛⡛⣛⠻⢻⡻⣻⠟⢟⡟⣟⠿⢿⡿⣿')
|
||||
|
||||
|
||||
# some ways of block geometry representations
|
||||
@@ -250,8 +250,8 @@ def hilbert_curve(width, height):
|
||||
yield from hilbert_(x, y, b_x_, b_y_, a_x_, a_y_)
|
||||
yield from hilbert_(x+b_x_, y+b_y_, a_x, a_y, b_x-b_x_, b_y-b_y_)
|
||||
yield from hilbert_(
|
||||
x+(a_x-a_dx)+(b_x_-b_dx), y+(a_y-a_dy)+(b_y_-b_dy),
|
||||
-b_x_, -b_y_, -(a_x-a_x_), -(a_y-a_y_))
|
||||
x+(a_x-a_dx)+(b_x_-b_dx), y+(a_y-a_dy)+(b_y_-b_dy),
|
||||
-b_x_, -b_y_, -(a_x-a_x_), -(a_y-a_y_))
|
||||
|
||||
if width >= height:
|
||||
curve = hilbert_(0, 0, +width, 0, 0, +height)
|
||||
@@ -293,10 +293,10 @@ class Pixel(int):
|
||||
btree=False,
|
||||
data=False):
|
||||
return super().__new__(cls,
|
||||
state
|
||||
| (1 if mdir else 0)
|
||||
| (2 if btree else 0)
|
||||
| (4 if data else 0))
|
||||
state
|
||||
| (1 if mdir else 0)
|
||||
| (2 if btree else 0)
|
||||
| (4 if data else 0))
|
||||
|
||||
@property
|
||||
def is_mdir(self):
|
||||
@@ -367,8 +367,8 @@ class Pixel(int):
|
||||
# apply colors
|
||||
if f and color:
|
||||
c = '%s%s\x1b[m' % (
|
||||
''.join('\x1b[%sm' % f_ for f_ in f),
|
||||
c)
|
||||
''.join('\x1b[%sm' % f_ for f_ in f),
|
||||
c)
|
||||
|
||||
return c
|
||||
|
||||
@@ -434,25 +434,25 @@ class Bmap:
|
||||
block -= self._block_window.start
|
||||
|
||||
size = (max(self._off_window.start,
|
||||
min(self._off_window.stop, off+size))
|
||||
- max(self._off_window.start,
|
||||
min(self._off_window.stop, off)))
|
||||
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)
|
||||
min(self._off_window.stop, off))
|
||||
- self._off_window.start)
|
||||
if size == 0:
|
||||
return
|
||||
|
||||
# map to our block space
|
||||
range_ = range(
|
||||
block*len(self._off_window) + off,
|
||||
block*len(self._off_window) + off+size)
|
||||
block*len(self._off_window) + off,
|
||||
block*len(self._off_window) + off+size)
|
||||
range_ = range(
|
||||
(range_.start*len(self.pixels)) // self._window,
|
||||
(range_.stop*len(self.pixels)) // self._window)
|
||||
(range_.start*len(self.pixels)) // self._window,
|
||||
(range_.stop*len(self.pixels)) // self._window)
|
||||
range_ = range(
|
||||
range_.start,
|
||||
max(range_.stop, range_.start+1))
|
||||
range_.start,
|
||||
max(range_.stop, range_.start+1))
|
||||
|
||||
# apply the op
|
||||
for i in range_:
|
||||
@@ -476,9 +476,9 @@ class Bmap:
|
||||
width=None,
|
||||
height=None):
|
||||
block_size = (block_size if block_size is not None
|
||||
else self.block_size)
|
||||
else self.block_size)
|
||||
block_count = (block_count if block_count is not None
|
||||
else self.block_count)
|
||||
else self.block_count)
|
||||
width = width if width is not None else self.width
|
||||
height = height if height is not None else self.height
|
||||
|
||||
@@ -496,17 +496,17 @@ class Bmap:
|
||||
for x in range(width*height):
|
||||
# map into our old bd space
|
||||
range_ = range(
|
||||
(x*self._window) // (width*height),
|
||||
((x+1)*self._window) // (width*height))
|
||||
(x*self._window) // (width*height),
|
||||
((x+1)*self._window) // (width*height))
|
||||
range_ = range(
|
||||
range_.start,
|
||||
max(range_.stop, range_.start+1))
|
||||
range_.start,
|
||||
max(range_.stop, range_.start+1))
|
||||
|
||||
# aggregate state
|
||||
pixels.append(ft.reduce(
|
||||
Pixel.__or__,
|
||||
self.pixels[range_.start:range_.stop],
|
||||
Pixel()))
|
||||
Pixel.__or__,
|
||||
self.pixels[range_.start:range_.stop],
|
||||
Pixel()))
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
@@ -552,12 +552,12 @@ class Bmap:
|
||||
byte_p |= 1 << i
|
||||
|
||||
line.append(best_p.draw(
|
||||
CHARS_BRAILLE[byte_p],
|
||||
braille=True,
|
||||
mdirs=mdirs,
|
||||
btrees=btrees,
|
||||
datas=datas,
|
||||
**args))
|
||||
CHARS_BRAILLE[byte_p],
|
||||
braille=True,
|
||||
mdirs=mdirs,
|
||||
btrees=btrees,
|
||||
datas=datas,
|
||||
**args))
|
||||
elif dots:
|
||||
# encode into a byte
|
||||
for x in range(self.width):
|
||||
@@ -572,19 +572,19 @@ class Bmap:
|
||||
byte_p |= 1 << i
|
||||
|
||||
line.append(best_p.draw(
|
||||
CHARS_DOTS[byte_p],
|
||||
dots=True,
|
||||
mdirs=mdirs,
|
||||
btrees=btrees,
|
||||
datas=datas,
|
||||
**args))
|
||||
CHARS_DOTS[byte_p],
|
||||
dots=True,
|
||||
mdirs=mdirs,
|
||||
btrees=btrees,
|
||||
datas=datas,
|
||||
**args))
|
||||
else:
|
||||
for x in range(self.width):
|
||||
line.append(grid[x + row*self.width].draw(
|
||||
mdirs=mdirs,
|
||||
btrees=btrees,
|
||||
datas=datas,
|
||||
**args))
|
||||
mdirs=mdirs,
|
||||
btrees=btrees,
|
||||
datas=datas,
|
||||
**args))
|
||||
|
||||
return ''.join(line)
|
||||
|
||||
@@ -610,9 +610,9 @@ class Rbyd:
|
||||
return '0x%x.%x' % (self.block, self.trunk)
|
||||
else:
|
||||
return '0x{%x,%s}.%x' % (
|
||||
self.block,
|
||||
','.join('%x' % block for block in self.redund_blocks),
|
||||
self.trunk)
|
||||
self.block,
|
||||
','.join('%x' % block for block in self.redund_blocks),
|
||||
self.trunk)
|
||||
|
||||
@classmethod
|
||||
def fetch(cls, f, block_size, blocks, trunk=None):
|
||||
@@ -621,21 +621,23 @@ class Rbyd:
|
||||
|
||||
if len(blocks) > 1:
|
||||
# fetch all blocks
|
||||
rbyds = [cls.fetch(f, block_size, block, trunk) for block in blocks]
|
||||
rbyds = [cls.fetch(f, block_size, block, trunk)
|
||||
for block in blocks]
|
||||
# determine most recent revision
|
||||
i = 0
|
||||
for i_, rbyd in enumerate(rbyds):
|
||||
# compare with sequence arithmetic
|
||||
if rbyd and (
|
||||
not rbyds[i]
|
||||
or not ((rbyd.rev - rbyds[i].rev) & 0x80000000)
|
||||
or (rbyd.rev == rbyds[i].rev
|
||||
and rbyd.trunk > rbyds[i].trunk)):
|
||||
or not ((rbyd.rev - rbyds[i].rev) & 0x80000000)
|
||||
or (rbyd.rev == rbyds[i].rev
|
||||
and rbyd.trunk > rbyds[i].trunk)):
|
||||
i = i_
|
||||
# keep track of the other blocks
|
||||
rbyd = rbyds[i]
|
||||
rbyd.redund_blocks = [rbyds[(i+1+j) % len(rbyds)].block
|
||||
for j in range(len(rbyds)-1)]
|
||||
rbyd.redund_blocks = [
|
||||
rbyds[(i+1+j) % len(rbyds)].block
|
||||
for j in range(len(rbyds)-1)]
|
||||
return rbyd
|
||||
else:
|
||||
# block may encode a trunk
|
||||
@@ -789,7 +791,9 @@ class Rbyd:
|
||||
|
||||
done = not tag_ or (rid_, tag_) < (rid, tag)
|
||||
|
||||
return done, rid_, tag_, w_, j, d, self.data[j+d:j+d+jump], path
|
||||
return (done, rid_, tag_, w_, j, d,
|
||||
self.data[j+d:j+d+jump],
|
||||
path)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.trunk)
|
||||
@@ -834,7 +838,7 @@ class Rbyd:
|
||||
w = 0
|
||||
for i in it.count():
|
||||
done, rid__, tag, w_, j, d, data, _ = rbyd.lookup(
|
||||
rid_, tag+0x1)
|
||||
rid_, tag+0x1)
|
||||
if done or (i != 0 and rid__ != rid_):
|
||||
break
|
||||
|
||||
@@ -880,14 +884,15 @@ class Rbyd:
|
||||
|
||||
# lookup our mbid
|
||||
done, mbid, mw, rbyd, rid, tags, path = mtree.btree_lookup(
|
||||
f, block_size, mbid)
|
||||
f, block_size, mbid)
|
||||
if done:
|
||||
return True, -1, 0, None
|
||||
|
||||
mdir = next(((tag, j, d, data)
|
||||
for tag, j, d, data in tags
|
||||
if tag == TAG_MDIR),
|
||||
None)
|
||||
mdir = next(
|
||||
((tag, j, d, data)
|
||||
for tag, j, d, data in tags
|
||||
if tag == TAG_MDIR),
|
||||
None)
|
||||
if not mdir:
|
||||
return True, -1, 0, None
|
||||
|
||||
@@ -979,7 +984,7 @@ def main(disk, mroots=None, *,
|
||||
|
||||
if any(isinstance(b, list) and len(b) > 1 for b in block):
|
||||
print("error: more than one block address?",
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
if isinstance(block[0], list):
|
||||
block = (block[0][0], *block[1:])
|
||||
@@ -1034,16 +1039,17 @@ def main(disk, mroots=None, *,
|
||||
|
||||
# create our block device representation
|
||||
bmap = Bmap(
|
||||
block_size=block_size,
|
||||
block_count=block_count,
|
||||
block_window=block_window,
|
||||
off_window=off_window,
|
||||
# scale if we're printing with dots or braille
|
||||
width=2*width_ if braille else width_,
|
||||
height=max(1,
|
||||
4*height_ if braille
|
||||
else 2*height_ if dots
|
||||
else height_))
|
||||
block_size=block_size,
|
||||
block_count=block_count,
|
||||
block_window=block_window,
|
||||
off_window=off_window,
|
||||
# scale if we're printing with dots or braille
|
||||
width=2*width_ if braille else width_,
|
||||
height=max(
|
||||
1,
|
||||
4*height_ if braille
|
||||
else 2*height_ if dots
|
||||
else height_))
|
||||
|
||||
# keep track of how many blocks are in use
|
||||
mdirs_ = 0
|
||||
@@ -1063,16 +1069,16 @@ def main(disk, mroots=None, *,
|
||||
block_size = f.tell()
|
||||
block_count = 1
|
||||
bmap.resize(
|
||||
block_size=block_size,
|
||||
block_count=block_count)
|
||||
block_size=block_size,
|
||||
block_count=block_count)
|
||||
|
||||
# if block_count is omitted, derive the block_count from our file size
|
||||
if block_count is None:
|
||||
f.seek(0, os.SEEK_END)
|
||||
block_count = f.tell() // block_size
|
||||
bmap.resize(
|
||||
block_size=block_size,
|
||||
block_count=block_count)
|
||||
block_size=block_size,
|
||||
block_count=block_count)
|
||||
|
||||
#### traverse the filesystem
|
||||
|
||||
@@ -1090,7 +1096,7 @@ def main(disk, mroots=None, *,
|
||||
# mark mroots in our bmap
|
||||
for block in mroot.blocks:
|
||||
bmap.mdir(block,
|
||||
mroot.eoff if args.get('in_use') else block_size)
|
||||
mroot.eoff if args.get('in_use') else block_size)
|
||||
mdirs_ += 1;
|
||||
|
||||
# find any file btrees in our mroot
|
||||
@@ -1129,7 +1135,8 @@ def main(disk, mroots=None, *,
|
||||
# mark mdir in our bmap
|
||||
for block in mdir.blocks:
|
||||
bmap.mdir(block,
|
||||
mdir.eoff if args.get('in_use') else block_size)
|
||||
mdir.eoff if args.get('in_use')
|
||||
else block_size)
|
||||
mdirs_ += 1
|
||||
|
||||
# find any file btrees in our mdir
|
||||
@@ -1153,8 +1160,8 @@ def main(disk, mroots=None, *,
|
||||
ppath = []
|
||||
while True:
|
||||
done, mbid, mw, rbyd, rid, tags, path = mtree.btree_lookup(
|
||||
f, block_size, mbid+1,
|
||||
depth=args.get('depth', mdepth)-mdepth)
|
||||
f, block_size, mbid+1,
|
||||
depth=args.get('depth', mdepth)-mdepth)
|
||||
if done:
|
||||
break
|
||||
|
||||
@@ -1176,8 +1183,8 @@ def main(disk, mroots=None, *,
|
||||
d, (mid_, w_, rbyd_, rid_, tags_) = x
|
||||
for block in rbyd_.blocks:
|
||||
bmap.btree(block,
|
||||
rbyd_.eoff if args.get('in_use')
|
||||
else block_size)
|
||||
rbyd_.eoff if args.get('in_use')
|
||||
else block_size)
|
||||
btrees_ += 1
|
||||
ppath = path
|
||||
|
||||
@@ -1190,10 +1197,11 @@ def main(disk, mroots=None, *,
|
||||
mdir__ = None
|
||||
if (not args.get('depth')
|
||||
or mdepth+len(path) < args.get('depth')):
|
||||
mdir__ = next(((tag, j, d, data)
|
||||
for tag, j, d, data in tags
|
||||
if tag == TAG_MDIR),
|
||||
None)
|
||||
mdir__ = next(
|
||||
((tag, j, d, data)
|
||||
for tag, j, d, data in tags
|
||||
if tag == TAG_MDIR),
|
||||
None)
|
||||
|
||||
if mdir__:
|
||||
# fetch the mdir
|
||||
@@ -1208,8 +1216,8 @@ def main(disk, mroots=None, *,
|
||||
# mark mdir in our bmap
|
||||
for block in mdir_.blocks:
|
||||
bmap.mdir(block, 0,
|
||||
mdir_.eoff if args.get('in_use')
|
||||
else block_size)
|
||||
mdir_.eoff if args.get('in_use')
|
||||
else block_size)
|
||||
mdirs_ += 1
|
||||
|
||||
# find any file btrees in our mdir
|
||||
@@ -1233,8 +1241,8 @@ def main(disk, mroots=None, *,
|
||||
size, block, off = frombptr(data)
|
||||
# mark block in our bmap
|
||||
bmap.data(block,
|
||||
off if args.get('in_use') else 0,
|
||||
size if args.get('in_use') else block_size)
|
||||
off if args.get('in_use') else 0,
|
||||
size if args.get('in_use') else block_size)
|
||||
datas_ += 1
|
||||
continue
|
||||
|
||||
@@ -1258,9 +1266,9 @@ def main(disk, mroots=None, *,
|
||||
ppath = []
|
||||
while True:
|
||||
(done, bid, w, rbyd, rid, tags, path
|
||||
) = btree.btree_lookup(
|
||||
f, block_size, bid+1,
|
||||
depth=args.get('depth', mdepth)-mdepth)
|
||||
) = btree.btree_lookup(
|
||||
f, block_size, bid+1,
|
||||
depth=args.get('depth', mdepth)-mdepth)
|
||||
if done:
|
||||
break
|
||||
|
||||
@@ -1285,8 +1293,8 @@ def main(disk, mroots=None, *,
|
||||
continue
|
||||
for block in rbyd_.blocks:
|
||||
bmap.btree(block,
|
||||
rbyd_.eoff if args.get('in_use')
|
||||
else block_size)
|
||||
rbyd_.eoff if args.get('in_use')
|
||||
else block_size)
|
||||
btrees_ += 1
|
||||
ppath = path
|
||||
|
||||
@@ -1299,10 +1307,11 @@ def main(disk, mroots=None, *,
|
||||
bptr__ = None
|
||||
if (not args.get('depth')
|
||||
or mdepth+len(path) < args.get('depth')):
|
||||
bptr__ = next(((tag, j, d, data)
|
||||
for tag, j, d, data in tags
|
||||
if tag & 0xfff == TAG_BLOCK),
|
||||
None)
|
||||
bptr__ = next(
|
||||
((tag, j, d, data)
|
||||
for tag, j, d, data in tags
|
||||
if tag & 0xfff == TAG_BLOCK),
|
||||
None)
|
||||
|
||||
if bptr__:
|
||||
# fetch the block
|
||||
@@ -1311,8 +1320,8 @@ def main(disk, mroots=None, *,
|
||||
|
||||
# mark blocks in our bmap
|
||||
bmap.data(block,
|
||||
off if args.get('in_use') else 0,
|
||||
size if args.get('in_use') else block_size)
|
||||
off if args.get('in_use') else 0,
|
||||
size if args.get('in_use') else block_size)
|
||||
datas_ += 1
|
||||
|
||||
#### actual rendering begins here
|
||||
@@ -1320,29 +1329,29 @@ def main(disk, mroots=None, *,
|
||||
# print some information about the bmap
|
||||
if not no_header:
|
||||
print('bd %dx%d%s%s%s' % (
|
||||
block_size, block_count,
|
||||
', %6s mdir' % ('%.1f%%' % (100*mdirs_ / block_count))
|
||||
if mdirs else '',
|
||||
', %6s btree' % ('%.1f%%' % (100*btrees_ / block_count))
|
||||
if btrees else '',
|
||||
', %6s data' % ('%.1f%%' % (100*datas_ / block_count))
|
||||
if datas else ''))
|
||||
block_size, block_count,
|
||||
', %6s mdir' % ('%.1f%%' % (100*mdirs_ / block_count))
|
||||
if mdirs else '',
|
||||
', %6s btree' % ('%.1f%%' % (100*btrees_ / block_count))
|
||||
if btrees else '',
|
||||
', %6s data' % ('%.1f%%' % (100*datas_ / block_count))
|
||||
if datas else ''))
|
||||
|
||||
# and then print the bmap
|
||||
for row in range(
|
||||
mt.ceil(bmap.height/4) if braille
|
||||
else mt.ceil(bmap.height/2) if dots
|
||||
else bmap.height):
|
||||
else mt.ceil(bmap.height/2) if dots
|
||||
else bmap.height):
|
||||
line = bmap.draw(row,
|
||||
mdirs=mdirs,
|
||||
btrees=btrees,
|
||||
datas=datas,
|
||||
color=color,
|
||||
dots=dots,
|
||||
braille=braille,
|
||||
hilbert=hilbert,
|
||||
lebesgue=lebesgue,
|
||||
**args)
|
||||
mdirs=mdirs,
|
||||
btrees=btrees,
|
||||
datas=datas,
|
||||
color=color,
|
||||
dots=dots,
|
||||
braille=braille,
|
||||
hilbert=hilbert,
|
||||
lebesgue=lebesgue,
|
||||
**args)
|
||||
print(line)
|
||||
|
||||
if args.get('error_on_corrupt') and corrupted:
|
||||
@@ -1353,122 +1362,122 @@ if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Render currently used blocks in a littlefs image.",
|
||||
allow_abbrev=False)
|
||||
description="Render currently used blocks in a littlefs image.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'disk',
|
||||
help="File containing the block device.")
|
||||
'disk',
|
||||
help="File containing the block device.")
|
||||
parser.add_argument(
|
||||
'mroots',
|
||||
nargs='*',
|
||||
type=rbydaddr,
|
||||
help="Block address of the mroots. Defaults to 0x{0,1}.")
|
||||
'mroots',
|
||||
nargs='*',
|
||||
type=rbydaddr,
|
||||
help="Block address of the mroots. Defaults to 0x{0,1}.")
|
||||
parser.add_argument(
|
||||
'-b', '--block-size',
|
||||
type=bdgeom,
|
||||
help="Block size/geometry in bytes.")
|
||||
'-b', '--block-size',
|
||||
type=bdgeom,
|
||||
help="Block size/geometry in bytes.")
|
||||
parser.add_argument(
|
||||
'--block-count',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Block count in blocks.")
|
||||
'--block-count',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Block count in blocks.")
|
||||
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.")
|
||||
'-@', '--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(
|
||||
'--off',
|
||||
type=lambda x: tuple(
|
||||
int(x, 0) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Show a specific offset, may be a range.")
|
||||
'--off',
|
||||
type=lambda x: tuple(
|
||||
int(x, 0) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Show a specific offset, may be a range.")
|
||||
parser.add_argument(
|
||||
'--size',
|
||||
type=lambda x: tuple(
|
||||
int(x, 0) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Show this many bytes, may be a range.")
|
||||
'--size',
|
||||
type=lambda x: tuple(
|
||||
int(x, 0) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Show this many bytes, may be a range.")
|
||||
parser.add_argument(
|
||||
'-M', '--mdirs',
|
||||
action='store_true',
|
||||
help="Render mdir blocks.")
|
||||
'-M', '--mdirs',
|
||||
action='store_true',
|
||||
help="Render mdir blocks.")
|
||||
parser.add_argument(
|
||||
'-B', '--btrees',
|
||||
action='store_true',
|
||||
help="Render btree blocks.")
|
||||
'-B', '--btrees',
|
||||
action='store_true',
|
||||
help="Render btree blocks.")
|
||||
parser.add_argument(
|
||||
'-D', '--datas',
|
||||
action='store_true',
|
||||
help="Render data blocks.")
|
||||
'-D', '--datas',
|
||||
action='store_true',
|
||||
help="Render data blocks.")
|
||||
parser.add_argument(
|
||||
'-N', '--no-header',
|
||||
action='store_true',
|
||||
help="Don't show the header.")
|
||||
'-N', '--no-header',
|
||||
action='store_true',
|
||||
help="Don't show the header.")
|
||||
parser.add_argument(
|
||||
'--color',
|
||||
choices=['never', 'always', 'auto'],
|
||||
default='auto',
|
||||
help="When to use terminal colors. Defaults to 'auto'.")
|
||||
'--color',
|
||||
choices=['never', 'always', 'auto'],
|
||||
default='auto',
|
||||
help="When to use terminal colors. Defaults to 'auto'.")
|
||||
parser.add_argument(
|
||||
'-:', '--dots',
|
||||
action='store_true',
|
||||
help="Use 1x2 ascii dot characters.")
|
||||
'-:', '--dots',
|
||||
action='store_true',
|
||||
help="Use 1x2 ascii dot characters.")
|
||||
parser.add_argument(
|
||||
'-⣿', '--braille',
|
||||
action='store_true',
|
||||
help="Use 2x4 unicode braille characters. Note that braille characters "
|
||||
"sometimes suffer from inconsistent widths.")
|
||||
'-⣿', '--braille',
|
||||
action='store_true',
|
||||
help="Use 2x4 unicode braille characters. Note that braille "
|
||||
"characters sometimes suffer from inconsistent widths.")
|
||||
parser.add_argument(
|
||||
'--chars',
|
||||
help="Characters to use for mdir, btree, data, unused blocks.")
|
||||
'--chars',
|
||||
help="Characters to use for mdir, btree, data, unused blocks.")
|
||||
parser.add_argument(
|
||||
'--colors',
|
||||
type=lambda x: [x.strip() for x in x.split(',')],
|
||||
help="Colors to use for mdir, btree, data, unused blocks.")
|
||||
'--colors',
|
||||
type=lambda x: [x.strip() for x in x.split(',')],
|
||||
help="Colors to use for mdir, btree, data, unused blocks.")
|
||||
parser.add_argument(
|
||||
'-W', '--width',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Width in columns. 0 uses the terminal width. Defaults to "
|
||||
"min(terminal, 80).")
|
||||
'-W', '--width',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Width in columns. 0 uses the terminal width. Defaults to "
|
||||
"min(terminal, 80).")
|
||||
parser.add_argument(
|
||||
'-H', '--height',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Height in rows. 0 uses the terminal height. Defaults to 1.")
|
||||
'-H', '--height',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Height in rows. 0 uses the terminal height. Defaults to 1.")
|
||||
parser.add_argument(
|
||||
'-n', '--lines',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Show this many lines of history. 0 uses the terminal height. "
|
||||
"Defaults to 5.")
|
||||
'-n', '--lines',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Show this many lines of history. 0 uses the terminal "
|
||||
"height. Defaults to 5.")
|
||||
parser.add_argument(
|
||||
'-U', '--hilbert',
|
||||
action='store_true',
|
||||
help="Render as a space-filling Hilbert curve.")
|
||||
'-U', '--hilbert',
|
||||
action='store_true',
|
||||
help="Render as a space-filling Hilbert curve.")
|
||||
parser.add_argument(
|
||||
'-Z', '--lebesgue',
|
||||
action='store_true',
|
||||
help="Render as a space-filling Z-curve.")
|
||||
'-Z', '--lebesgue',
|
||||
action='store_true',
|
||||
help="Render as a space-filling Z-curve.")
|
||||
parser.add_argument(
|
||||
'-i', '--in-use',
|
||||
action='store_true',
|
||||
help="Show how much of each block is in use.")
|
||||
'-i', '--in-use',
|
||||
action='store_true',
|
||||
help="Show how much of each block is in use.")
|
||||
parser.add_argument(
|
||||
'-z', '--depth',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Depth of the filesystem tree to parse.")
|
||||
'-z', '--depth',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Depth of the filesystem tree to parse.")
|
||||
parser.add_argument(
|
||||
'-e', '--error-on-corrupt',
|
||||
action='store_true',
|
||||
help="Error if the filesystem is corrupt.")
|
||||
'-e', '--error-on-corrupt',
|
||||
action='store_true',
|
||||
help="Error if the filesystem is corrupt.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+202
-195
@@ -154,108 +154,108 @@ def frombranch(data):
|
||||
def xxd(data, width=16):
|
||||
for i in range(0, len(data), width):
|
||||
yield '%-*s %-*s' % (
|
||||
3*width,
|
||||
' '.join('%02x' % b for b in data[i:i+width]),
|
||||
width,
|
||||
''.join(
|
||||
b if b >= ' ' and b <= '~' else '.'
|
||||
for b in map(chr, data[i:i+width])))
|
||||
3*width,
|
||||
' '.join('%02x' % b for b in data[i:i+width]),
|
||||
width,
|
||||
''.join(
|
||||
b if b >= ' ' and b <= '~' else '.'
|
||||
for b in map(chr, data[i:i+width])))
|
||||
|
||||
def tagrepr(tag, w=None, size=None, off=None):
|
||||
if (tag & 0x6fff) == TAG_NULL:
|
||||
return '%snull%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
' w%d' % w if w else '',
|
||||
' %d' % size if size else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
' w%d' % w if w else '',
|
||||
' %d' % size if size else '')
|
||||
elif (tag & 0x6f00) == TAG_CONFIG:
|
||||
return '%s%s%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'magic' if (tag & 0xfff) == TAG_MAGIC
|
||||
else 'version' if (tag & 0xfff) == TAG_VERSION
|
||||
else 'rcompat' if (tag & 0xfff) == TAG_RCOMPAT
|
||||
else 'wcompat' if (tag & 0xfff) == TAG_WCOMPAT
|
||||
else 'ocompat' if (tag & 0xfff) == TAG_OCOMPAT
|
||||
else 'geometry' if (tag & 0xfff) == TAG_GEOMETRY
|
||||
else 'namelimit' if (tag & 0xfff) == TAG_NAMELIMIT
|
||||
else 'filelimit' if (tag & 0xfff) == TAG_FILELIMIT
|
||||
else 'config 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'magic' if (tag & 0xfff) == TAG_MAGIC
|
||||
else 'version' if (tag & 0xfff) == TAG_VERSION
|
||||
else 'rcompat' if (tag & 0xfff) == TAG_RCOMPAT
|
||||
else 'wcompat' if (tag & 0xfff) == TAG_WCOMPAT
|
||||
else 'ocompat' if (tag & 0xfff) == TAG_OCOMPAT
|
||||
else 'geometry' if (tag & 0xfff) == TAG_GEOMETRY
|
||||
else 'namelimit' if (tag & 0xfff) == TAG_NAMELIMIT
|
||||
else 'filelimit' if (tag & 0xfff) == TAG_FILELIMIT
|
||||
else 'config 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x6f00) == TAG_GDELTA:
|
||||
return '%s%s%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'grmdelta' if (tag & 0xfff) == TAG_GRMDELTA
|
||||
else 'gdelta 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'grmdelta' if (tag & 0xfff) == TAG_GRMDELTA
|
||||
else 'gdelta 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x6f00) == TAG_NAME:
|
||||
return '%s%s%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'name' if (tag & 0xfff) == TAG_NAME
|
||||
else 'reg' if (tag & 0xfff) == TAG_REG
|
||||
else 'dir' if (tag & 0xfff) == TAG_DIR
|
||||
else 'orphan' if (tag & 0xfff) == TAG_ORPHAN
|
||||
else 'bookmark' if (tag & 0xfff) == TAG_BOOKMARK
|
||||
else 'name 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'name' if (tag & 0xfff) == TAG_NAME
|
||||
else 'reg' if (tag & 0xfff) == TAG_REG
|
||||
else 'dir' if (tag & 0xfff) == TAG_DIR
|
||||
else 'orphan' if (tag & 0xfff) == TAG_ORPHAN
|
||||
else 'bookmark' if (tag & 0xfff) == TAG_BOOKMARK
|
||||
else 'name 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x6f00) == TAG_STRUCT:
|
||||
return '%s%s%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'data' if (tag & 0xfff) == TAG_DATA
|
||||
else 'block' if (tag & 0xfff) == TAG_BLOCK
|
||||
else 'bshrub' if (tag & 0xfff) == TAG_BSHRUB
|
||||
else 'btree' if (tag & 0xfff) == TAG_BTREE
|
||||
else 'mroot' if (tag & 0xfff) == TAG_MROOT
|
||||
else 'mdir' if (tag & 0xfff) == TAG_MDIR
|
||||
else 'mtree' if (tag & 0xfff) == TAG_MTREE
|
||||
else 'did' if (tag & 0xfff) == TAG_DID
|
||||
else 'branch' if (tag & 0xfff) == TAG_BRANCH
|
||||
else 'struct 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'data' if (tag & 0xfff) == TAG_DATA
|
||||
else 'block' if (tag & 0xfff) == TAG_BLOCK
|
||||
else 'bshrub' if (tag & 0xfff) == TAG_BSHRUB
|
||||
else 'btree' if (tag & 0xfff) == TAG_BTREE
|
||||
else 'mroot' if (tag & 0xfff) == TAG_MROOT
|
||||
else 'mdir' if (tag & 0xfff) == TAG_MDIR
|
||||
else 'mtree' if (tag & 0xfff) == TAG_MTREE
|
||||
else 'did' if (tag & 0xfff) == TAG_DID
|
||||
else 'branch' if (tag & 0xfff) == TAG_BRANCH
|
||||
else 'struct 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x6e00) == TAG_ATTR:
|
||||
return '%s%sattr 0x%02x%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
's' if tag & 0x100 else 'u',
|
||||
((tag & 0x100) >> 1) ^ (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
's' if tag & 0x100 else 'u',
|
||||
((tag & 0x100) >> 1) ^ (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif tag & TAG_ALT:
|
||||
return 'alt%s%s%s%s%s' % (
|
||||
'r' if tag & TAG_R else 'b',
|
||||
'a' if tag & 0x0fff == 0 and tag & TAG_GT
|
||||
else 'n' if tag & 0x0fff == 0
|
||||
else 'gt' if tag & TAG_GT
|
||||
else 'le',
|
||||
' 0x%x' % (tag & 0x0fff) if tag & 0x0fff != 0 else '',
|
||||
' w%d' % w if w is not None else '',
|
||||
' 0x%x' % (0xffffffff & (off-size))
|
||||
if size and off is not None
|
||||
else ' -%d' % size if size
|
||||
else '')
|
||||
'r' if tag & TAG_R else 'b',
|
||||
'a' if tag & 0x0fff == 0 and tag & TAG_GT
|
||||
else 'n' if tag & 0x0fff == 0
|
||||
else 'gt' if tag & TAG_GT
|
||||
else 'le',
|
||||
' 0x%x' % (tag & 0x0fff) if tag & 0x0fff != 0 else '',
|
||||
' w%d' % w if w is not None else '',
|
||||
' 0x%x' % (0xffffffff & (off-size))
|
||||
if size and off is not None
|
||||
else ' -%d' % size if size
|
||||
else '')
|
||||
elif (tag & 0x7f00) == TAG_CKSUM:
|
||||
return 'cksum%s%s%s%s%s' % (
|
||||
'q' if not tag & 0xfc and tag & TAG_Q else '',
|
||||
'p' if not tag & 0xfc and tag & TAG_P else '',
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xfc else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'q' if not tag & 0xfc and tag & TAG_Q else '',
|
||||
'p' if not tag & 0xfc and tag & TAG_P else '',
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xfc else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x7f00) == TAG_NOTE:
|
||||
return 'note%s%s%s' % (
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x7f00) == TAG_ECKSUM:
|
||||
return 'ecksum%s%s%s' % (
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
else:
|
||||
return '0x%04x%s%s' % (
|
||||
tag,
|
||||
' w%d' % w if w is not None else '',
|
||||
' %d' % size if size is not None else '')
|
||||
tag,
|
||||
' w%d' % w if w is not None else '',
|
||||
' %d' % size if size is not None else '')
|
||||
|
||||
|
||||
# this type is used for tree representations
|
||||
@@ -278,9 +278,9 @@ class Rbyd:
|
||||
return '0x%x.%x' % (self.block, self.trunk)
|
||||
else:
|
||||
return '0x{%x,%s}.%x' % (
|
||||
self.block,
|
||||
','.join('%x' % block for block in self.redund_blocks),
|
||||
self.trunk)
|
||||
self.block,
|
||||
','.join('%x' % block for block in self.redund_blocks),
|
||||
self.trunk)
|
||||
|
||||
@classmethod
|
||||
def fetch(cls, f, block_size, blocks, trunk=None):
|
||||
@@ -289,21 +289,23 @@ class Rbyd:
|
||||
|
||||
if len(blocks) > 1:
|
||||
# fetch all blocks
|
||||
rbyds = [cls.fetch(f, block_size, block, trunk) for block in blocks]
|
||||
rbyds = [cls.fetch(f, block_size, block, trunk)
|
||||
for block in blocks]
|
||||
# determine most recent revision
|
||||
i = 0
|
||||
for i_, rbyd in enumerate(rbyds):
|
||||
# compare with sequence arithmetic
|
||||
if rbyd and (
|
||||
not rbyds[i]
|
||||
or not ((rbyd.rev - rbyds[i].rev) & 0x80000000)
|
||||
or (rbyd.rev == rbyds[i].rev
|
||||
and rbyd.trunk > rbyds[i].trunk)):
|
||||
or not ((rbyd.rev - rbyds[i].rev) & 0x80000000)
|
||||
or (rbyd.rev == rbyds[i].rev
|
||||
and rbyd.trunk > rbyds[i].trunk)):
|
||||
i = i_
|
||||
# keep track of the other blocks
|
||||
rbyd = rbyds[i]
|
||||
rbyd.redund_blocks = [rbyds[(i+1+j) % len(rbyds)].block
|
||||
for j in range(len(rbyds)-1)]
|
||||
rbyd.redund_blocks = [
|
||||
rbyds[(i+1+j) % len(rbyds)].block
|
||||
for j in range(len(rbyds)-1)]
|
||||
return rbyd
|
||||
else:
|
||||
# block may encode a trunk
|
||||
@@ -457,7 +459,9 @@ class Rbyd:
|
||||
|
||||
done = not tag_ or (rid_, tag_) < (rid, tag)
|
||||
|
||||
return done, rid_, tag_, w_, j, d, self.data[j+d:j+d+jump], path
|
||||
return (done, rid_, tag_, w_, j, d,
|
||||
self.data[j+d:j+d+jump],
|
||||
path)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.trunk)
|
||||
@@ -549,8 +553,8 @@ class Rbyd:
|
||||
else:
|
||||
if 'h' not in alts[j_]:
|
||||
alts[j_]['h'] = max(
|
||||
rec_height(alts[j_]['f']),
|
||||
rec_height(alts[j_]['nf'])) + 1
|
||||
rec_height(alts[j_]['f']),
|
||||
rec_height(alts[j_]['nf'])) + 1
|
||||
return alts[j_]['h']
|
||||
|
||||
for j_ in alts.keys():
|
||||
@@ -614,10 +618,10 @@ def main(disk, roots=None, *,
|
||||
# fetch the root
|
||||
btree = Rbyd.fetch(f, block_size, roots, trunk)
|
||||
print('btree %s w%d, rev %08x, cksum %08x' % (
|
||||
btree.addr(),
|
||||
btree.weight,
|
||||
btree.rev,
|
||||
btree.cksum))
|
||||
btree.addr(),
|
||||
btree.weight,
|
||||
btree.rev,
|
||||
btree.cksum))
|
||||
|
||||
# look up a bid, while keeping track of the search path
|
||||
def btree_lookup(bid, *,
|
||||
@@ -642,7 +646,7 @@ def main(disk, roots=None, *,
|
||||
w = 0
|
||||
for i in it.count():
|
||||
done, rid__, tag, w_, j, d, data, _ = rbyd.lookup(
|
||||
rid_, tag+0x1)
|
||||
rid_, tag+0x1)
|
||||
if done or (i != 0 and rid__ != rid_):
|
||||
break
|
||||
|
||||
@@ -683,7 +687,7 @@ def main(disk, roots=None, *,
|
||||
bid = -1
|
||||
while True:
|
||||
done, bid, w, rbyd, rid, tags, path = btree_lookup(
|
||||
bid+1, depth=args.get('depth'))
|
||||
bid+1, depth=args.get('depth'))
|
||||
if done:
|
||||
break
|
||||
|
||||
@@ -698,7 +702,7 @@ def main(disk, roots=None, *,
|
||||
bid = -1
|
||||
while True:
|
||||
done, bid, w, rbyd, rid, tags, path = btree_lookup(
|
||||
bid+1, depth=args.get('depth'))
|
||||
bid+1, depth=args.get('depth'))
|
||||
if done:
|
||||
break
|
||||
|
||||
@@ -731,8 +735,8 @@ def main(disk, roots=None, *,
|
||||
# connect our branch to the rbyd's root
|
||||
if leaf is not None:
|
||||
root = min(rtree,
|
||||
key=lambda branch: branch.d,
|
||||
default=None)
|
||||
key=lambda branch: branch.d,
|
||||
default=None)
|
||||
|
||||
if root is not None:
|
||||
r_rid, r_tag = root.a
|
||||
@@ -758,9 +762,10 @@ def main(disk, roots=None, *,
|
||||
|
||||
d_ += max(bdepths.get(d, 0), 1)
|
||||
leaf = (bid-(w-1), d, rid-(w-1),
|
||||
next((tag for tag, _, _, _ in tags
|
||||
if tag & 0xfff == TAG_BRANCH),
|
||||
TAG_BRANCH))
|
||||
next(
|
||||
(tag for tag, _, _, _ in tags
|
||||
if tag & 0xfff == TAG_BRANCH),
|
||||
TAG_BRANCH))
|
||||
|
||||
# remap branches to leaves if we aren't showing inner branches
|
||||
if not args.get('inner'):
|
||||
@@ -813,7 +818,7 @@ def main(disk, roots=None, *,
|
||||
bid = -1
|
||||
while True:
|
||||
done, bid, w, rbyd, rid, tags, path = btree_lookup(
|
||||
bid+1, depth=args.get('depth'))
|
||||
bid+1, depth=args.get('depth'))
|
||||
if done:
|
||||
break
|
||||
|
||||
@@ -836,7 +841,7 @@ def main(disk, roots=None, *,
|
||||
continue
|
||||
|
||||
b = (bid-(w-1), d, rid-(w-1),
|
||||
(name if name else tags[0])[0])
|
||||
(name if name else tags[0])[0])
|
||||
|
||||
# remap branches to leaves if we aren't showing
|
||||
# inner branches
|
||||
@@ -846,8 +851,8 @@ def main(disk, roots=None, *,
|
||||
if not tags:
|
||||
continue
|
||||
branches[b] = (
|
||||
bid-(w-1), len(path)-1, rid-(w-1),
|
||||
(name if name else tags[0])[0])
|
||||
bid-(w-1), len(path)-1, rid-(w-1),
|
||||
(name if name else tags[0])[0])
|
||||
b = branches[b]
|
||||
|
||||
# found entry point?
|
||||
@@ -905,16 +910,16 @@ def main(disk, roots=None, *,
|
||||
was = None
|
||||
for d in range(t_depth):
|
||||
t, c, was = branchrepr(
|
||||
(bid-(w-1), bd, rid-(w-1), tag), d, was)
|
||||
(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 ''))
|
||||
'\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)
|
||||
|
||||
@@ -931,39 +936,41 @@ def main(disk, roots=None, *,
|
||||
# show human-readable representation
|
||||
for i, (tag, j, d, data) in enumerate(tags):
|
||||
print('%10s %s%*s %-*s %s' % (
|
||||
'%04x.%04x:' % (rbyd.block, rbyd.trunk)
|
||||
if prbyd is None or rbyd != prbyd
|
||||
else '',
|
||||
treerepr(bid, w, bd, rid, tag)
|
||||
if args.get('tree')
|
||||
or args.get('rbyd')
|
||||
or args.get('btree') else '',
|
||||
2*w_width+1, '' if i != 0
|
||||
else '%d-%d' % (bid-(w-1), bid) if w > 1
|
||||
else bid if w > 0
|
||||
else '',
|
||||
21+w_width, tagrepr(
|
||||
tag, w if i == 0 else 0, len(data), None),
|
||||
next(xxd(data, 8), '')
|
||||
if not args.get('raw') and not args.get('no_truncate')
|
||||
else ''))
|
||||
'%04x.%04x:' % (rbyd.block, rbyd.trunk)
|
||||
if prbyd is None or rbyd != prbyd
|
||||
else '',
|
||||
treerepr(bid, w, bd, rid, tag)
|
||||
if args.get('tree')
|
||||
or args.get('rbyd')
|
||||
or args.get('btree')
|
||||
else '',
|
||||
2*w_width+1, '' if i != 0
|
||||
else '%d-%d' % (bid-(w-1), bid) if w > 1
|
||||
else bid if w > 0
|
||||
else '',
|
||||
21+w_width, tagrepr(
|
||||
tag, w if i == 0 else 0, len(data), None),
|
||||
next(xxd(data, 8), '')
|
||||
if not args.get('raw')
|
||||
and not args.get('no_truncate')
|
||||
else ''))
|
||||
prbyd = rbyd
|
||||
|
||||
# show on-disk encoding of tags/data
|
||||
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, '',
|
||||
2*w_width+1, '',
|
||||
line))
|
||||
'%04x' % (j + o*16),
|
||||
t_width, '',
|
||||
2*w_width+1, '',
|
||||
line))
|
||||
if args.get('raw') or args.get('no_truncate'):
|
||||
for o, line in enumerate(xxd(data)):
|
||||
print('%9s: %*s%*s %s' % (
|
||||
'%04x' % (j+d + o*16),
|
||||
t_width, '',
|
||||
2*w_width+1, '',
|
||||
line))
|
||||
'%04x' % (j+d + o*16),
|
||||
t_width, '',
|
||||
2*w_width+1, '',
|
||||
line))
|
||||
|
||||
|
||||
# traverse and print entries
|
||||
@@ -973,7 +980,7 @@ def main(disk, roots=None, *,
|
||||
corrupted = False
|
||||
while True:
|
||||
done, bid, w, rbyd, rid, tags, path = btree_lookup(
|
||||
bid+1, depth=args.get('depth'))
|
||||
bid+1, depth=args.get('depth'))
|
||||
if done:
|
||||
break
|
||||
|
||||
@@ -997,11 +1004,11 @@ def main(disk, roots=None, *,
|
||||
# corrupted? try to keep printing the tree
|
||||
if not rbyd:
|
||||
print('%04x.%04x: %*s%s%s%s' % (
|
||||
rbyd.block, rbyd.trunk,
|
||||
t_width, '',
|
||||
'\x1b[31m' if color else '',
|
||||
'(corrupted rbyd %s)' % rbyd.addr(),
|
||||
'\x1b[m' if color else ''))
|
||||
rbyd.block, rbyd.trunk,
|
||||
t_width, '',
|
||||
'\x1b[31m' if color else '',
|
||||
'(corrupted rbyd %s)' % rbyd.addr(),
|
||||
'\x1b[m' if color else ''))
|
||||
prbyd = rbyd
|
||||
corrupted = True
|
||||
continue
|
||||
@@ -1020,8 +1027,8 @@ def main(disk, roots=None, *,
|
||||
|
||||
if name is not None:
|
||||
tags = [name] + [(tag, j, d, data)
|
||||
for tag, j, d, data in tags
|
||||
if tag & 0x7f00 != TAG_NAME]
|
||||
for tag, j, d, data in tags
|
||||
if tag & 0x7f00 != TAG_NAME]
|
||||
|
||||
# show the branch
|
||||
dbg_branch(bid, w, rbyd, rid, tags, len(path)-1)
|
||||
@@ -1034,67 +1041,67 @@ if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Debug rbyd B-trees.",
|
||||
allow_abbrev=False)
|
||||
description="Debug rbyd B-trees.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'disk',
|
||||
help="File containing the block device.")
|
||||
'disk',
|
||||
help="File containing the block device.")
|
||||
parser.add_argument(
|
||||
'roots',
|
||||
nargs='*',
|
||||
type=rbydaddr,
|
||||
help="Block address of the roots of the tree.")
|
||||
'roots',
|
||||
nargs='*',
|
||||
type=rbydaddr,
|
||||
help="Block address of the roots of the tree.")
|
||||
parser.add_argument(
|
||||
'-b', '--block-size',
|
||||
type=bdgeom,
|
||||
help="Block size/geometry in bytes.")
|
||||
'-b', '--block-size',
|
||||
type=bdgeom,
|
||||
help="Block size/geometry in bytes.")
|
||||
parser.add_argument(
|
||||
'--block-count',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Block count in blocks.")
|
||||
'--block-count',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Block count in blocks.")
|
||||
parser.add_argument(
|
||||
'--trunk',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Use this offset as the trunk of the tree.")
|
||||
'--trunk',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Use this offset as the trunk of the tree.")
|
||||
parser.add_argument(
|
||||
'--color',
|
||||
choices=['never', 'always', 'auto'],
|
||||
default='auto',
|
||||
help="When to use terminal colors. Defaults to 'auto'.")
|
||||
'--color',
|
||||
choices=['never', 'always', 'auto'],
|
||||
default='auto',
|
||||
help="When to use terminal colors. Defaults to 'auto'.")
|
||||
parser.add_argument(
|
||||
'-r', '--raw',
|
||||
action='store_true',
|
||||
help="Show the raw data including tag encodings.")
|
||||
'-r', '--raw',
|
||||
action='store_true',
|
||||
help="Show the raw data including tag encodings.")
|
||||
parser.add_argument(
|
||||
'-T', '--no-truncate',
|
||||
action='store_true',
|
||||
help="Don't truncate, show the full contents.")
|
||||
'-T', '--no-truncate',
|
||||
action='store_true',
|
||||
help="Don't truncate, show the full contents.")
|
||||
parser.add_argument(
|
||||
'-t', '--tree',
|
||||
action='store_true',
|
||||
help="Show the underlying rbyd trees.")
|
||||
'-t', '--tree',
|
||||
action='store_true',
|
||||
help="Show the underlying rbyd trees.")
|
||||
parser.add_argument(
|
||||
'-B', '--btree',
|
||||
action='store_true',
|
||||
help="Show the B-tree.")
|
||||
'-B', '--btree',
|
||||
action='store_true',
|
||||
help="Show the B-tree.")
|
||||
parser.add_argument(
|
||||
'-R', '--rbyd',
|
||||
action='store_true',
|
||||
help="Show the full underlying rbyd trees.")
|
||||
'-R', '--rbyd',
|
||||
action='store_true',
|
||||
help="Show the full underlying rbyd trees.")
|
||||
parser.add_argument(
|
||||
'-i', '--inner',
|
||||
action='store_true',
|
||||
help="Show inner branches.")
|
||||
'-i', '--inner',
|
||||
action='store_true',
|
||||
help="Show inner branches.")
|
||||
parser.add_argument(
|
||||
'-z', '--depth',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Depth of tree to show.")
|
||||
'-z', '--depth',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Depth of tree to show.")
|
||||
parser.add_argument(
|
||||
'-e', '--error-on-corrupt',
|
||||
action='store_true',
|
||||
help="Error if B-tree is corrupt.")
|
||||
'-e', '--error-on-corrupt',
|
||||
action='store_true',
|
||||
help="Error if B-tree is corrupt.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+48
-45
@@ -66,12 +66,12 @@ def rbydaddr(s):
|
||||
def xxd(data, width=16):
|
||||
for i in range(0, len(data), width):
|
||||
yield '%-*s %-*s' % (
|
||||
3*width,
|
||||
' '.join('%02x' % b for b in data[i:i+width]),
|
||||
width,
|
||||
''.join(
|
||||
b if b >= ' ' and b <= '~' else '.'
|
||||
for b in map(chr, data[i:i+width])))
|
||||
3*width,
|
||||
' '.join('%02x' % b for b in data[i:i+width]),
|
||||
width,
|
||||
''.join(
|
||||
b if b >= ' ' and b <= '~' else '.'
|
||||
for b in map(chr, data[i:i+width])))
|
||||
|
||||
def crc32c(data, crc=0):
|
||||
crc ^= 0xffffffff
|
||||
@@ -105,19 +105,21 @@ def main(disk, blocks=None, *,
|
||||
|
||||
# blocks may also encode offsets
|
||||
blocks, offs, size = (
|
||||
[block[0] if isinstance(block, tuple) else block
|
||||
for block in blocks],
|
||||
[off[0] if isinstance(off, tuple)
|
||||
else off if off is not None
|
||||
else size[0] if isinstance(size, tuple) and len(size) > 1
|
||||
else block[1] if isinstance(block, tuple)
|
||||
else None
|
||||
for block in blocks],
|
||||
size[1] - size[0] if isinstance(size, tuple) and len(size) > 1
|
||||
else size[0] if isinstance(size, tuple)
|
||||
else size if size is not None
|
||||
else off[1] - off[0] if isinstance(off, tuple) and len(off) > 1
|
||||
else block_size)
|
||||
[block[0] if isinstance(block, tuple) else block
|
||||
for block in blocks],
|
||||
[off[0] if isinstance(off, tuple)
|
||||
else off if off is not None
|
||||
else size[0]
|
||||
if isinstance(size, tuple) and len(size) > 1
|
||||
else block[1] if isinstance(block, tuple)
|
||||
else None
|
||||
for block in blocks],
|
||||
size[1] - size[0] if isinstance(size, tuple) and len(size) > 1
|
||||
else size[0] if isinstance(size, tuple)
|
||||
else size if size is not None
|
||||
else off[1] - off[0]
|
||||
if isinstance(off, tuple) and len(off) > 1
|
||||
else block_size)
|
||||
|
||||
# cat the blocks
|
||||
for block, off in zip(blocks, offs):
|
||||
@@ -126,40 +128,41 @@ def main(disk, blocks=None, *,
|
||||
sys.stdout.buffer.write(data)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Cat data from a block device.",
|
||||
allow_abbrev=False)
|
||||
description="Cat data from a block device.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'disk',
|
||||
help="File containing the block device.")
|
||||
'disk',
|
||||
help="File containing the block device.")
|
||||
parser.add_argument(
|
||||
'blocks',
|
||||
nargs='*',
|
||||
type=rbydaddr,
|
||||
help="Block address.")
|
||||
'blocks',
|
||||
nargs='*',
|
||||
type=rbydaddr,
|
||||
help="Block address.")
|
||||
parser.add_argument(
|
||||
'-b', '--block-size',
|
||||
type=bdgeom,
|
||||
help="Block size/geometry in bytes.")
|
||||
'-b', '--block-size',
|
||||
type=bdgeom,
|
||||
help="Block size/geometry in bytes.")
|
||||
parser.add_argument(
|
||||
'--block-count',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Block count in blocks.")
|
||||
'--block-count',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Block count in blocks.")
|
||||
parser.add_argument(
|
||||
'--off',
|
||||
type=lambda x: tuple(
|
||||
int(x, 0) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Show a specific offset, may be a range.")
|
||||
'--off',
|
||||
type=lambda x: tuple(
|
||||
int(x, 0) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Show a specific offset, may be a range.")
|
||||
parser.add_argument(
|
||||
'-n', '--size',
|
||||
type=lambda x: tuple(
|
||||
int(x, 0) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Show this many bytes, may be a range.")
|
||||
'-n', '--size',
|
||||
type=lambda x: tuple(
|
||||
int(x, 0) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Show this many bytes, may be a range.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+14
-13
@@ -37,9 +37,9 @@ def main(errs, *,
|
||||
# print
|
||||
for n, e, h in ERRS:
|
||||
print('%-*s %-*s %s' % (
|
||||
w[0], 'LFS_ERR_'+n,
|
||||
w[1], e,
|
||||
h))
|
||||
w[0], 'LFS_ERR_'+n,
|
||||
w[1], e,
|
||||
h))
|
||||
|
||||
# find these errors
|
||||
else:
|
||||
@@ -77,20 +77,21 @@ def main(errs, *,
|
||||
except KeyError:
|
||||
print('%s ?' % err)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Decode littlefs error codes.",
|
||||
allow_abbrev=False)
|
||||
description="Decode littlefs error codes.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'errs',
|
||||
nargs='*',
|
||||
help="Error codes or error names to decode.")
|
||||
'errs',
|
||||
nargs='*',
|
||||
help="Error codes or error names to decode.")
|
||||
parser.add_argument(
|
||||
'-l', '--list',
|
||||
action='store_true',
|
||||
help="List all known error codes.")
|
||||
'-l', '--list',
|
||||
action='store_true',
|
||||
help="List all known error codes.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+483
-459
File diff suppressed because it is too large
Load Diff
+313
-293
@@ -169,108 +169,108 @@ def frombtree(data):
|
||||
def xxd(data, width=16):
|
||||
for i in range(0, len(data), width):
|
||||
yield '%-*s %-*s' % (
|
||||
3*width,
|
||||
' '.join('%02x' % b for b in data[i:i+width]),
|
||||
width,
|
||||
''.join(
|
||||
b if b >= ' ' and b <= '~' else '.'
|
||||
for b in map(chr, data[i:i+width])))
|
||||
3*width,
|
||||
' '.join('%02x' % b for b in data[i:i+width]),
|
||||
width,
|
||||
''.join(
|
||||
b if b >= ' ' and b <= '~' else '.'
|
||||
for b in map(chr, data[i:i+width])))
|
||||
|
||||
def tagrepr(tag, w=None, size=None, off=None):
|
||||
if (tag & 0x6fff) == TAG_NULL:
|
||||
return '%snull%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
' w%d' % w if w else '',
|
||||
' %d' % size if size else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
' w%d' % w if w else '',
|
||||
' %d' % size if size else '')
|
||||
elif (tag & 0x6f00) == TAG_CONFIG:
|
||||
return '%s%s%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'magic' if (tag & 0xfff) == TAG_MAGIC
|
||||
else 'version' if (tag & 0xfff) == TAG_VERSION
|
||||
else 'rcompat' if (tag & 0xfff) == TAG_RCOMPAT
|
||||
else 'wcompat' if (tag & 0xfff) == TAG_WCOMPAT
|
||||
else 'ocompat' if (tag & 0xfff) == TAG_OCOMPAT
|
||||
else 'geometry' if (tag & 0xfff) == TAG_GEOMETRY
|
||||
else 'namelimit' if (tag & 0xfff) == TAG_NAMELIMIT
|
||||
else 'filelimit' if (tag & 0xfff) == TAG_FILELIMIT
|
||||
else 'config 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'magic' if (tag & 0xfff) == TAG_MAGIC
|
||||
else 'version' if (tag & 0xfff) == TAG_VERSION
|
||||
else 'rcompat' if (tag & 0xfff) == TAG_RCOMPAT
|
||||
else 'wcompat' if (tag & 0xfff) == TAG_WCOMPAT
|
||||
else 'ocompat' if (tag & 0xfff) == TAG_OCOMPAT
|
||||
else 'geometry' if (tag & 0xfff) == TAG_GEOMETRY
|
||||
else 'namelimit' if (tag & 0xfff) == TAG_NAMELIMIT
|
||||
else 'filelimit' if (tag & 0xfff) == TAG_FILELIMIT
|
||||
else 'config 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x6f00) == TAG_GDELTA:
|
||||
return '%s%s%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'grmdelta' if (tag & 0xfff) == TAG_GRMDELTA
|
||||
else 'gdelta 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'grmdelta' if (tag & 0xfff) == TAG_GRMDELTA
|
||||
else 'gdelta 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x6f00) == TAG_NAME:
|
||||
return '%s%s%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'name' if (tag & 0xfff) == TAG_NAME
|
||||
else 'reg' if (tag & 0xfff) == TAG_REG
|
||||
else 'dir' if (tag & 0xfff) == TAG_DIR
|
||||
else 'orphan' if (tag & 0xfff) == TAG_ORPHAN
|
||||
else 'bookmark' if (tag & 0xfff) == TAG_BOOKMARK
|
||||
else 'name 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'name' if (tag & 0xfff) == TAG_NAME
|
||||
else 'reg' if (tag & 0xfff) == TAG_REG
|
||||
else 'dir' if (tag & 0xfff) == TAG_DIR
|
||||
else 'orphan' if (tag & 0xfff) == TAG_ORPHAN
|
||||
else 'bookmark' if (tag & 0xfff) == TAG_BOOKMARK
|
||||
else 'name 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x6f00) == TAG_STRUCT:
|
||||
return '%s%s%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'data' if (tag & 0xfff) == TAG_DATA
|
||||
else 'block' if (tag & 0xfff) == TAG_BLOCK
|
||||
else 'bshrub' if (tag & 0xfff) == TAG_BSHRUB
|
||||
else 'btree' if (tag & 0xfff) == TAG_BTREE
|
||||
else 'mroot' if (tag & 0xfff) == TAG_MROOT
|
||||
else 'mdir' if (tag & 0xfff) == TAG_MDIR
|
||||
else 'mtree' if (tag & 0xfff) == TAG_MTREE
|
||||
else 'did' if (tag & 0xfff) == TAG_DID
|
||||
else 'branch' if (tag & 0xfff) == TAG_BRANCH
|
||||
else 'struct 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'data' if (tag & 0xfff) == TAG_DATA
|
||||
else 'block' if (tag & 0xfff) == TAG_BLOCK
|
||||
else 'bshrub' if (tag & 0xfff) == TAG_BSHRUB
|
||||
else 'btree' if (tag & 0xfff) == TAG_BTREE
|
||||
else 'mroot' if (tag & 0xfff) == TAG_MROOT
|
||||
else 'mdir' if (tag & 0xfff) == TAG_MDIR
|
||||
else 'mtree' if (tag & 0xfff) == TAG_MTREE
|
||||
else 'did' if (tag & 0xfff) == TAG_DID
|
||||
else 'branch' if (tag & 0xfff) == TAG_BRANCH
|
||||
else 'struct 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x6e00) == TAG_ATTR:
|
||||
return '%s%sattr 0x%02x%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
's' if tag & 0x100 else 'u',
|
||||
((tag & 0x100) >> 1) ^ (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
's' if tag & 0x100 else 'u',
|
||||
((tag & 0x100) >> 1) ^ (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif tag & TAG_ALT:
|
||||
return 'alt%s%s%s%s%s' % (
|
||||
'r' if tag & TAG_R else 'b',
|
||||
'a' if tag & 0x0fff == 0 and tag & TAG_GT
|
||||
else 'n' if tag & 0x0fff == 0
|
||||
else 'gt' if tag & TAG_GT
|
||||
else 'le',
|
||||
' 0x%x' % (tag & 0x0fff) if tag & 0x0fff != 0 else '',
|
||||
' w%d' % w if w is not None else '',
|
||||
' 0x%x' % (0xffffffff & (off-size))
|
||||
if size and off is not None
|
||||
else ' -%d' % size if size
|
||||
else '')
|
||||
'r' if tag & TAG_R else 'b',
|
||||
'a' if tag & 0x0fff == 0 and tag & TAG_GT
|
||||
else 'n' if tag & 0x0fff == 0
|
||||
else 'gt' if tag & TAG_GT
|
||||
else 'le',
|
||||
' 0x%x' % (tag & 0x0fff) if tag & 0x0fff != 0 else '',
|
||||
' w%d' % w if w is not None else '',
|
||||
' 0x%x' % (0xffffffff & (off-size))
|
||||
if size and off is not None
|
||||
else ' -%d' % size if size
|
||||
else '')
|
||||
elif (tag & 0x7f00) == TAG_CKSUM:
|
||||
return 'cksum%s%s%s%s%s' % (
|
||||
'q' if not tag & 0xfc and tag & TAG_Q else '',
|
||||
'p' if not tag & 0xfc and tag & TAG_P else '',
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xfc else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'q' if not tag & 0xfc and tag & TAG_Q else '',
|
||||
'p' if not tag & 0xfc and tag & TAG_P else '',
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xfc else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x7f00) == TAG_NOTE:
|
||||
return 'note%s%s%s' % (
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x7f00) == TAG_ECKSUM:
|
||||
return 'ecksum%s%s%s' % (
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
else:
|
||||
return '0x%04x%s%s' % (
|
||||
tag,
|
||||
' w%d' % w if w is not None else '',
|
||||
' %d' % size if size is not None else '')
|
||||
tag,
|
||||
' w%d' % w if w is not None else '',
|
||||
' %d' % size if size is not None else '')
|
||||
|
||||
|
||||
# this type is used for tree representations
|
||||
@@ -293,9 +293,9 @@ class Rbyd:
|
||||
return '0x%x.%x' % (self.block, self.trunk)
|
||||
else:
|
||||
return '0x{%x,%s}.%x' % (
|
||||
self.block,
|
||||
','.join('%x' % block for block in self.redund_blocks),
|
||||
self.trunk)
|
||||
self.block,
|
||||
','.join('%x' % block for block in self.redund_blocks),
|
||||
self.trunk)
|
||||
|
||||
@classmethod
|
||||
def fetch(cls, f, block_size, blocks, trunk=None):
|
||||
@@ -304,21 +304,23 @@ class Rbyd:
|
||||
|
||||
if len(blocks) > 1:
|
||||
# fetch all blocks
|
||||
rbyds = [cls.fetch(f, block_size, block, trunk) for block in blocks]
|
||||
rbyds = [cls.fetch(f, block_size, block, trunk)
|
||||
for block in blocks]
|
||||
# determine most recent revision
|
||||
i = 0
|
||||
for i_, rbyd in enumerate(rbyds):
|
||||
# compare with sequence arithmetic
|
||||
if rbyd and (
|
||||
not rbyds[i]
|
||||
or not ((rbyd.rev - rbyds[i].rev) & 0x80000000)
|
||||
or (rbyd.rev == rbyds[i].rev
|
||||
and rbyd.trunk > rbyds[i].trunk)):
|
||||
or not ((rbyd.rev - rbyds[i].rev) & 0x80000000)
|
||||
or (rbyd.rev == rbyds[i].rev
|
||||
and rbyd.trunk > rbyds[i].trunk)):
|
||||
i = i_
|
||||
# keep track of the other blocks
|
||||
rbyd = rbyds[i]
|
||||
rbyd.redund_blocks = [rbyds[(i+1+j) % len(rbyds)].block
|
||||
for j in range(len(rbyds)-1)]
|
||||
rbyd.redund_blocks = [
|
||||
rbyds[(i+1+j) % len(rbyds)].block
|
||||
for j in range(len(rbyds)-1)]
|
||||
return rbyd
|
||||
else:
|
||||
# block may encode a trunk
|
||||
@@ -472,7 +474,9 @@ class Rbyd:
|
||||
|
||||
done = not tag_ or (rid_, tag_) < (rid, tag)
|
||||
|
||||
return done, rid_, tag_, w_, j, d, self.data[j+d:j+d+jump], path
|
||||
return (done, rid_, tag_, w_, j, d,
|
||||
self.data[j+d:j+d+jump],
|
||||
path)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.trunk)
|
||||
@@ -564,8 +568,8 @@ class Rbyd:
|
||||
else:
|
||||
if 'h' not in alts[j_]:
|
||||
alts[j_]['h'] = max(
|
||||
rec_height(alts[j_]['f']),
|
||||
rec_height(alts[j_]['nf'])) + 1
|
||||
rec_height(alts[j_]['f']),
|
||||
rec_height(alts[j_]['nf'])) + 1
|
||||
return alts[j_]['h']
|
||||
|
||||
for j_ in alts.keys():
|
||||
@@ -616,7 +620,7 @@ class Rbyd:
|
||||
w = 0
|
||||
for i in it.count():
|
||||
done, rid__, tag, w_, j, d, data, _ = rbyd.lookup(
|
||||
rid_, tag+0x1)
|
||||
rid_, tag+0x1)
|
||||
if done or (i != 0 and rid__ != rid_):
|
||||
break
|
||||
|
||||
@@ -659,7 +663,7 @@ class Rbyd:
|
||||
bid = -1
|
||||
while True:
|
||||
done, bid, w, rbyd_, rid, tags, path = self.btree_lookup(
|
||||
f, block_size, bid+1, depth=depth)
|
||||
f, block_size, bid+1, depth=depth)
|
||||
if done:
|
||||
break
|
||||
|
||||
@@ -674,7 +678,7 @@ class Rbyd:
|
||||
bid = -1
|
||||
while True:
|
||||
done, bid, w, rbyd_, rid, tags, path = self.btree_lookup(
|
||||
f, block_size, bid+1, depth=depth)
|
||||
f, block_size, bid+1, depth=depth)
|
||||
if done:
|
||||
break
|
||||
|
||||
@@ -707,8 +711,8 @@ class Rbyd:
|
||||
# connect our branch to the rbyd's root
|
||||
if leaf is not None:
|
||||
root = min(rtree,
|
||||
key=lambda branch: branch.d,
|
||||
default=None)
|
||||
key=lambda branch: branch.d,
|
||||
default=None)
|
||||
|
||||
if root is not None:
|
||||
r_rid, r_tag = root.a
|
||||
@@ -734,9 +738,10 @@ class Rbyd:
|
||||
|
||||
d_ += max(bdepths.get(d, 0), 1)
|
||||
leaf = (bid-(w-1), d, rid-(w-1),
|
||||
next((tag for tag, _, _, _ in tags
|
||||
if tag & 0xfff == TAG_BRANCH),
|
||||
TAG_BRANCH))
|
||||
next(
|
||||
(tag for tag, _, _, _ in tags
|
||||
if tag & 0xfff == TAG_BRANCH),
|
||||
TAG_BRANCH))
|
||||
|
||||
# remap branches to leaves if we aren't showing inner branches
|
||||
if not inner:
|
||||
@@ -793,7 +798,7 @@ class Rbyd:
|
||||
bid = -1
|
||||
while True:
|
||||
done, bid, w, rbyd, rid, tags, path = self.btree_lookup(
|
||||
f, block_size, bid+1, depth=depth)
|
||||
f, block_size, bid+1, depth=depth)
|
||||
if done:
|
||||
break
|
||||
|
||||
@@ -816,7 +821,7 @@ class Rbyd:
|
||||
continue
|
||||
|
||||
b = (bid-(w-1), d, rid-(w-1),
|
||||
(name if name else tags[0])[0])
|
||||
(name if name else tags[0])[0])
|
||||
|
||||
# remap branches to leaves if we aren't showing
|
||||
# inner branches
|
||||
@@ -826,8 +831,8 @@ class Rbyd:
|
||||
if not tags:
|
||||
continue
|
||||
branches[b] = (
|
||||
bid-(w-1), len(path)-1, rid-(w-1),
|
||||
(name if name else tags[0])[0])
|
||||
bid-(w-1), len(path)-1, rid-(w-1),
|
||||
(name if name else tags[0])[0])
|
||||
b = branches[b]
|
||||
|
||||
# found entry point?
|
||||
@@ -935,8 +940,8 @@ def main(disk, mroots=None, *,
|
||||
mbid = -1
|
||||
while True:
|
||||
done, mbid, mw, rbyd, rid, tags, path = mtree.btree_lookup(
|
||||
f, block_size, mbid+1,
|
||||
depth=args.get('depth', mdepth)-mdepth)
|
||||
f, block_size, mbid+1,
|
||||
depth=args.get('depth', mdepth)-mdepth)
|
||||
if done:
|
||||
break
|
||||
|
||||
@@ -947,10 +952,11 @@ def main(disk, mroots=None, *,
|
||||
mdir__ = None
|
||||
if (not args.get('depth')
|
||||
or mdepth+len(path) < args.get('depth')):
|
||||
mdir__ = next(((tag, j, d, data)
|
||||
for tag, j, d, data in tags
|
||||
if tag == TAG_MDIR),
|
||||
None)
|
||||
mdir__ = next(
|
||||
((tag, j, d, data)
|
||||
for tag, j, d, data in tags
|
||||
if tag == TAG_MDIR),
|
||||
None)
|
||||
|
||||
if mdir__:
|
||||
# fetch the mdir
|
||||
@@ -981,8 +987,8 @@ def main(disk, mroots=None, *,
|
||||
# connect branch to our root
|
||||
if d > 0:
|
||||
root = min(rtree,
|
||||
key=lambda branch: branch.d,
|
||||
default=None)
|
||||
key=lambda branch: branch.d,
|
||||
default=None)
|
||||
|
||||
if root:
|
||||
r_rid, r_tag = root.a
|
||||
@@ -1026,8 +1032,8 @@ def main(disk, mroots=None, *,
|
||||
|
||||
# connect branch to our root
|
||||
root = min(rtree,
|
||||
key=lambda branch: branch.d,
|
||||
default=None)
|
||||
key=lambda branch: branch.d,
|
||||
default=None)
|
||||
|
||||
if root:
|
||||
r_rid, r_tag = root.a
|
||||
@@ -1054,10 +1060,10 @@ def main(disk, mroots=None, *,
|
||||
# compute the mtree's rbyd-tree if there is one
|
||||
if mtree:
|
||||
tree_, tdepth = mtree.btree_tree(
|
||||
f, block_size,
|
||||
depth=args.get('depth', mdepth)-mdepth,
|
||||
inner=args.get('inner'),
|
||||
rbyd=args.get('rbyd'))
|
||||
f, block_size,
|
||||
depth=args.get('depth', mdepth)-mdepth,
|
||||
inner=args.get('inner'),
|
||||
rbyd=args.get('rbyd'))
|
||||
|
||||
# connect a branch to the root of the tree
|
||||
root = min(tree_, key=lambda branch: branch.d, default=None)
|
||||
@@ -1086,8 +1092,8 @@ def main(disk, mroots=None, *,
|
||||
mbid = -1
|
||||
while True:
|
||||
done, mbid, mw, rbyd, rid, tags, path = mtree.btree_lookup(
|
||||
f, block_size, mbid+1,
|
||||
depth=args.get('depth', mdepth)-mdepth)
|
||||
f, block_size, mbid+1,
|
||||
depth=args.get('depth', mdepth)-mdepth)
|
||||
if done:
|
||||
break
|
||||
|
||||
@@ -1098,10 +1104,11 @@ def main(disk, mroots=None, *,
|
||||
mdir__ = None
|
||||
if (not args.get('depth')
|
||||
or mdepth+len(path) < args.get('depth')):
|
||||
mdir__ = next(((tag, j, d, data)
|
||||
for tag, j, d, data in tags
|
||||
if tag == TAG_MDIR),
|
||||
None)
|
||||
mdir__ = next(
|
||||
((tag, j, d, data)
|
||||
for tag, j, d, data in tags
|
||||
if tag == TAG_MDIR),
|
||||
None)
|
||||
|
||||
if mdir__:
|
||||
# fetch the mdir
|
||||
@@ -1116,8 +1123,8 @@ def main(disk, mroots=None, *,
|
||||
mbid = -1
|
||||
while True:
|
||||
done, mbid, mw, rbyd, rid, tags, path = mtree.btree_lookup(
|
||||
f, block_size, mbid+1,
|
||||
depth=args.get('depth', mdepth)-mdepth)
|
||||
f, block_size, mbid+1,
|
||||
depth=args.get('depth', mdepth)-mdepth)
|
||||
if done:
|
||||
break
|
||||
|
||||
@@ -1128,10 +1135,11 @@ def main(disk, mroots=None, *,
|
||||
mdir__ = None
|
||||
if (not args.get('depth')
|
||||
or mdepth+len(path) < args.get('depth')):
|
||||
mdir__ = next(((tag, j, d, data)
|
||||
for tag, j, d, data in tags
|
||||
if tag == TAG_MDIR),
|
||||
None)
|
||||
mdir__ = next(
|
||||
((tag, j, d, data)
|
||||
for tag, j, d, data in tags
|
||||
if tag == TAG_MDIR),
|
||||
None)
|
||||
|
||||
if mdir__:
|
||||
# fetch the mdir
|
||||
@@ -1143,19 +1151,19 @@ def main(disk, mroots=None, *,
|
||||
|
||||
# connect the root to the mtree
|
||||
branch = max(
|
||||
(branch for branch in tree
|
||||
if branch.b[0] == mbid-(mw-1)),
|
||||
key=lambda branch: branch.d,
|
||||
default=None)
|
||||
if branch:
|
||||
root = min(rtree,
|
||||
(branch for branch in tree
|
||||
if branch.b[0] == mbid-(mw-1)),
|
||||
key=lambda branch: branch.d,
|
||||
default=None)
|
||||
if branch:
|
||||
root = min(rtree,
|
||||
key=lambda branch: branch.d,
|
||||
default=None)
|
||||
if root:
|
||||
r_rid, r_tag = root.a
|
||||
else:
|
||||
_, r_rid, r_tag, _, _, _, _, _ = (
|
||||
mdir_.lookup(-1, 0x1))
|
||||
mdir_.lookup(-1, 0x1))
|
||||
tree.add(TBranch(
|
||||
a=branch.b,
|
||||
b=(mbid-(mw-1), len(path), 0, r_rid, r_tag),
|
||||
@@ -1183,7 +1191,7 @@ def main(disk, mroots=None, *,
|
||||
# keep track of the original bids, unfortunately because we
|
||||
# store the bids in the branches we overwrite these
|
||||
tree = {(branch.b[0] - branch.b[2], branch)
|
||||
for branch in tree}
|
||||
for branch in tree}
|
||||
|
||||
for bd in reversed(range(b_depth-1)):
|
||||
# find leaf-roots at this level
|
||||
@@ -1274,9 +1282,9 @@ def main(disk, mroots=None, *,
|
||||
# compute the mtree's B-tree if there is one
|
||||
if mtree:
|
||||
tree_, tdepth = mtree.btree_btree(
|
||||
f, block_size,
|
||||
depth=args.get('depth', mdepth)-mdepth,
|
||||
inner=args.get('inner'))
|
||||
f, block_size,
|
||||
depth=args.get('depth', mdepth)-mdepth,
|
||||
inner=args.get('inner'))
|
||||
|
||||
# connect a branch to the root of the tree
|
||||
root = min(tree_, key=lambda branch: branch.d, default=None)
|
||||
@@ -1306,8 +1314,8 @@ def main(disk, mroots=None, *,
|
||||
while True:
|
||||
done, mbid, mw, rbyd, rid, tags, path = (
|
||||
mtree.btree_lookup(
|
||||
f, block_size, mbid+1,
|
||||
depth=args.get('depth', mdepth)-mdepth))
|
||||
f, block_size, mbid+1,
|
||||
depth=args.get('depth', mdepth)-mdepth))
|
||||
if done:
|
||||
break
|
||||
|
||||
@@ -1318,10 +1326,11 @@ def main(disk, mroots=None, *,
|
||||
mdir__ = None
|
||||
if (not args.get('depth')
|
||||
or mdepth+len(path) < args.get('depth')):
|
||||
mdir__ = next(((tag, j, d, data)
|
||||
for tag, j, d, data in tags
|
||||
if tag == TAG_MDIR),
|
||||
None)
|
||||
mdir__ = next(
|
||||
((tag, j, d, data)
|
||||
for tag, j, d, data in tags
|
||||
if tag == TAG_MDIR),
|
||||
None)
|
||||
|
||||
if mdir__:
|
||||
# fetch the mdir
|
||||
@@ -1332,7 +1341,7 @@ def main(disk, mroots=None, *,
|
||||
# find the first entry in the mdir, map branches
|
||||
# to this entry
|
||||
done, rid, tag, _, j, d, data, _ = (
|
||||
mdir_.lookup(-1, 0x1))
|
||||
mdir_.lookup(-1, 0x1))
|
||||
|
||||
tree_ = set()
|
||||
for branch in tree:
|
||||
@@ -1373,8 +1382,8 @@ def main(disk, mroots=None, *,
|
||||
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)
|
||||
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:
|
||||
@@ -1397,17 +1406,18 @@ def main(disk, mroots=None, *,
|
||||
was = None
|
||||
for d in range(t_depth):
|
||||
t, c, was = branchrepr(
|
||||
(mbid-max(mw-1, 0), md, mrid-max(mw-1, 0), rid, tag),
|
||||
d, was)
|
||||
(mbid-max(mw-1, 0), md,
|
||||
mrid-max(mw-1, 0), rid, 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 ''))
|
||||
'\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)
|
||||
|
||||
@@ -1416,41 +1426,45 @@ def main(disk, mroots=None, *,
|
||||
for i, (rid, tag, w, j, d, data) in enumerate(mdir):
|
||||
# show human-readable tag representation
|
||||
print('%12s %s%s' % (
|
||||
'{%s}:' % ','.join('%04x' % block
|
||||
for block in it.chain([mdir.block],
|
||||
mdir.redund_blocks))
|
||||
if i == 0 else '',
|
||||
treerepr(mbid-max(mw-1, 0), 0, md, 0, rid, tag)
|
||||
if args.get('tree')
|
||||
or args.get('rbyd')
|
||||
or args.get('btree') else '',
|
||||
'%*s %-*s%s' % (
|
||||
2*w_width+1, '%d.%d-%d' % (
|
||||
mbid//mleaf_weight, rid-(w-1), rid)
|
||||
if w > 1 else '%d.%d' % (mbid//mleaf_weight, rid)
|
||||
if w > 0 or i == 0 else '',
|
||||
21+w_width, tagrepr(tag, w, len(data), j),
|
||||
' %s' % next(xxd(data, 8), '')
|
||||
if not args.get('raw')
|
||||
and not args.get('no_truncate')
|
||||
else '')))
|
||||
'{%s}:' % ','.join('%04x' % block
|
||||
for block in it.chain(
|
||||
[mdir.block],
|
||||
mdir.redund_blocks))
|
||||
if i == 0 else '',
|
||||
treerepr(mbid-max(mw-1, 0), 0, md, 0, rid, tag)
|
||||
if args.get('tree')
|
||||
or args.get('rbyd')
|
||||
or args.get('btree')
|
||||
else '',
|
||||
'%*s %-*s%s' % (
|
||||
2*w_width+1, '%d.%d-%d' % (
|
||||
mbid//mleaf_weight, rid-(w-1), rid)
|
||||
if w > 1
|
||||
else '%d.%d' % (mbid//mleaf_weight, rid)
|
||||
if w > 0 or i == 0
|
||||
else '',
|
||||
21+w_width, tagrepr(tag, w, len(data), j),
|
||||
' %s' % next(xxd(data, 8), '')
|
||||
if not args.get('raw')
|
||||
and not args.get('no_truncate')
|
||||
else '')))
|
||||
|
||||
# show on-disk encoding of tags
|
||||
if args.get('raw'):
|
||||
for o, line in enumerate(xxd(mdir.data[j:j+d])):
|
||||
print('%11s: %*s%*s %s' % (
|
||||
'%04x' % (j + o*16),
|
||||
t_width, '',
|
||||
2*w_width+1, '',
|
||||
line))
|
||||
'%04x' % (j + o*16),
|
||||
t_width, '',
|
||||
2*w_width+1, '',
|
||||
line))
|
||||
if args.get('raw') or args.get('no_truncate'):
|
||||
if not tag & TAG_ALT:
|
||||
for o, line in enumerate(xxd(data)):
|
||||
print('%11s: %*s%*s %s' % (
|
||||
'%04x' % (j+d + o*16),
|
||||
t_width, '',
|
||||
2*w_width+1, '',
|
||||
line))
|
||||
'%04x' % (j+d + o*16),
|
||||
t_width, '',
|
||||
2*w_width+1, '',
|
||||
line))
|
||||
|
||||
# prbyd here means the last rendered rbyd, we update
|
||||
# in dbg_branch to always print interleaved addresses
|
||||
@@ -1461,59 +1475,61 @@ def main(disk, mroots=None, *,
|
||||
# show human-readable representation
|
||||
for i, (tag, j, d, data) in enumerate(tags):
|
||||
print('%12s %s%*s %-*s %s' % (
|
||||
'%04x.%04x:' % (rbyd.block, rbyd.trunk)
|
||||
if prbyd is None or rbyd != prbyd
|
||||
else '',
|
||||
treerepr(bid, w, bd, rid, 0, tag)
|
||||
if args.get('tree')
|
||||
or args.get('rbyd')
|
||||
or args.get('btree') else '',
|
||||
2*w_width+1, '' if i != 0
|
||||
else '%d-%d' % (
|
||||
'%04x.%04x:' % (rbyd.block, rbyd.trunk)
|
||||
if prbyd is None or rbyd != prbyd
|
||||
else '',
|
||||
treerepr(bid, w, bd, rid, 0, tag)
|
||||
if args.get('tree')
|
||||
or args.get('rbyd')
|
||||
or args.get('btree')
|
||||
else '',
|
||||
2*w_width+1, '' if i != 0
|
||||
else '%d-%d' % (
|
||||
(bid-(w-1))//mleaf_weight,
|
||||
bid//mleaf_weight)
|
||||
if (w//mleaf_weight) > 1
|
||||
else bid//mleaf_weight if w > 0
|
||||
else '',
|
||||
21+w_width, tagrepr(
|
||||
tag, w if i == 0 else 0, len(data), None),
|
||||
next(xxd(data, 8), '')
|
||||
if not args.get('raw') and not args.get('no_truncate')
|
||||
else ''))
|
||||
else bid//mleaf_weight if w > 0
|
||||
else '',
|
||||
21+w_width, tagrepr(
|
||||
tag, w if i == 0 else 0, len(data), None),
|
||||
next(xxd(data, 8), '')
|
||||
if not args.get('raw')
|
||||
and not args.get('no_truncate')
|
||||
else ''))
|
||||
prbyd = rbyd
|
||||
|
||||
# show on-disk encoding of tags/data
|
||||
if args.get('raw'):
|
||||
for o, line in enumerate(xxd(rbyd.data[j:j+d])):
|
||||
print('%11s: %*s%*s %s' % (
|
||||
'%04x' % (j + o*16),
|
||||
t_width, '',
|
||||
2*w_width+1, '',
|
||||
line))
|
||||
'%04x' % (j + o*16),
|
||||
t_width, '',
|
||||
2*w_width+1, '',
|
||||
line))
|
||||
if args.get('raw') or args.get('no_truncate'):
|
||||
for o, line in enumerate(xxd(data)):
|
||||
print('%11s: %*s%*s %s' % (
|
||||
'%04x' % (j+d + o*16),
|
||||
t_width, '',
|
||||
2*w_width+1, '',
|
||||
line))
|
||||
'%04x' % (j+d + o*16),
|
||||
t_width, '',
|
||||
2*w_width+1, '',
|
||||
line))
|
||||
|
||||
|
||||
#### actual debugging begins here
|
||||
|
||||
# print some information about the mtree
|
||||
print('mtree %s w%d.%d, rev %08x, cksum %08x' % (
|
||||
mroot.addr(),
|
||||
bweight//mleaf_weight, 1*mleaf_weight,
|
||||
mroot.rev,
|
||||
mroot.cksum))
|
||||
mroot.addr(),
|
||||
bweight//mleaf_weight, 1*mleaf_weight,
|
||||
mroot.rev,
|
||||
mroot.cksum))
|
||||
|
||||
# dynamically size the id field
|
||||
w_width = max(
|
||||
mt.ceil(mt.log10(max(1, bweight//mleaf_weight)+1)),
|
||||
mt.ceil(mt.log10(max(1, rweight)+1)),
|
||||
# in case of -1.-1
|
||||
2)
|
||||
mt.ceil(mt.log10(max(1, bweight//mleaf_weight)+1)),
|
||||
mt.ceil(mt.log10(max(1, rweight)+1)),
|
||||
# in case of -1.-1
|
||||
2)
|
||||
|
||||
# show each mroot
|
||||
prbyd = None
|
||||
@@ -1525,12 +1541,13 @@ def main(disk, mroots=None, *,
|
||||
# corrupted?
|
||||
if not mroot:
|
||||
print('{%s}: %s%s%s' % (
|
||||
','.join('%04x' % block
|
||||
for block in it.chain([mroot.block],
|
||||
mroot.redund_blocks)),
|
||||
'\x1b[31m' if color else '',
|
||||
'(corrupted mroot %s)' % mroot.addr(),
|
||||
'\x1b[m' if color else ''))
|
||||
','.join('%04x' % block
|
||||
for block in it.chain(
|
||||
[mroot.block],
|
||||
mroot.redund_blocks)),
|
||||
'\x1b[31m' if color else '',
|
||||
'(corrupted mroot %s)' % mroot.addr(),
|
||||
'\x1b[m' if color else ''))
|
||||
corrupted = True
|
||||
break
|
||||
else:
|
||||
@@ -1560,12 +1577,13 @@ def main(disk, mroots=None, *,
|
||||
# corrupted?
|
||||
if not mdir:
|
||||
print('{%s}: %s%s%s' % (
|
||||
','.join('%04x' % block
|
||||
for block in it.chain([mdir.block],
|
||||
mdir.redund_blocks)),
|
||||
'\x1b[31m' if color else '',
|
||||
'(corrupted mdir %s)' % mdir.addr(),
|
||||
'\x1b[m' if color else ''))
|
||||
','.join('%04x' % block
|
||||
for block in it.chain(
|
||||
[mdir.block],
|
||||
mdir.redund_blocks)),
|
||||
'\x1b[31m' if color else '',
|
||||
'(corrupted mdir %s)' % mdir.addr(),
|
||||
'\x1b[m' if color else ''))
|
||||
corrupted = True
|
||||
else:
|
||||
# show the mdir
|
||||
@@ -1582,8 +1600,8 @@ def main(disk, mroots=None, *,
|
||||
mbid = -1
|
||||
while True:
|
||||
done, mbid, mw, rbyd, rid, tags, path = mtree.btree_lookup(
|
||||
f, block_size, mbid+1,
|
||||
depth=args.get('depth', mdepth)-mdepth)
|
||||
f, block_size, mbid+1,
|
||||
depth=args.get('depth', mdepth)-mdepth)
|
||||
if done:
|
||||
break
|
||||
|
||||
@@ -1607,11 +1625,11 @@ def main(disk, mroots=None, *,
|
||||
# corrupted? try to keep printing the tree
|
||||
if not rbyd:
|
||||
print('%11s: %*s%s%s%s' % (
|
||||
'%04x.%04x' % (rbyd.block, rbyd.trunk),
|
||||
t_width, '',
|
||||
'\x1b[31m' if color else '',
|
||||
'(corrupted rbyd %s)' % rbyd.addr(),
|
||||
'\x1b[m' if color else ''))
|
||||
'%04x.%04x' % (rbyd.block, rbyd.trunk),
|
||||
t_width, '',
|
||||
'\x1b[31m' if color else '',
|
||||
'(corrupted rbyd %s)' % rbyd.addr(),
|
||||
'\x1b[m' if color else ''))
|
||||
prbyd = rbyd
|
||||
corrupted = True
|
||||
continue
|
||||
@@ -1630,17 +1648,18 @@ def main(disk, mroots=None, *,
|
||||
|
||||
if name is not None:
|
||||
tags = [name] + [(tag, j, d, data)
|
||||
for tag, j, d, data in tags
|
||||
if tag & 0x7f00 != TAG_NAME]
|
||||
for tag, j, d, data in tags
|
||||
if tag & 0x7f00 != TAG_NAME]
|
||||
|
||||
# found an mdir in the tags?
|
||||
mdir__ = None
|
||||
if (not args.get('depth')
|
||||
or mdepth+len(path) < args.get('depth')):
|
||||
mdir__ = next(((tag, j, d, data)
|
||||
for tag, j, d, data in tags
|
||||
if tag == TAG_MDIR),
|
||||
None)
|
||||
mdir__ = next(
|
||||
((tag, j, d, data)
|
||||
for tag, j, d, data in tags
|
||||
if tag == TAG_MDIR),
|
||||
None)
|
||||
|
||||
# show other btree entries in certain cases
|
||||
if args.get('inner') or not mdir__:
|
||||
@@ -1657,13 +1676,14 @@ def main(disk, mroots=None, *,
|
||||
# corrupted?
|
||||
if not mdir_:
|
||||
print('{%s}: %*s%s%s%s' % (
|
||||
','.join('%04x' % block
|
||||
for block in it.chain([mdir_.block],
|
||||
mdir_.redund_blocks)),
|
||||
t_width, '',
|
||||
'\x1b[31m' if color else '',
|
||||
'(corrupted mdir %s)' % mdir_.addr(),
|
||||
'\x1b[m' if color else ''))
|
||||
','.join('%04x' % block
|
||||
for block in it.chain(
|
||||
[mdir_.block],
|
||||
mdir_.redund_blocks)),
|
||||
t_width, '',
|
||||
'\x1b[31m' if color else '',
|
||||
'(corrupted mdir %s)' % mdir_.addr(),
|
||||
'\x1b[m' if color else ''))
|
||||
corrupted = True
|
||||
else:
|
||||
# show the mdir
|
||||
@@ -1680,63 +1700,63 @@ if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Debug littlefs's metadata tree.",
|
||||
allow_abbrev=False)
|
||||
description="Debug littlefs's metadata tree.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'disk',
|
||||
help="File containing the block device.")
|
||||
'disk',
|
||||
help="File containing the block device.")
|
||||
parser.add_argument(
|
||||
'mroots',
|
||||
nargs='*',
|
||||
type=rbydaddr,
|
||||
help="Block address of the mroots. Defaults to 0x{0,1}.")
|
||||
'mroots',
|
||||
nargs='*',
|
||||
type=rbydaddr,
|
||||
help="Block address of the mroots. Defaults to 0x{0,1}.")
|
||||
parser.add_argument(
|
||||
'-b', '--block-size',
|
||||
type=bdgeom,
|
||||
help="Block size/geometry in bytes.")
|
||||
'-b', '--block-size',
|
||||
type=bdgeom,
|
||||
help="Block size/geometry in bytes.")
|
||||
parser.add_argument(
|
||||
'--block-count',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Block count in blocks.")
|
||||
'--block-count',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Block count in blocks.")
|
||||
parser.add_argument(
|
||||
'--color',
|
||||
choices=['never', 'always', 'auto'],
|
||||
default='auto',
|
||||
help="When to use terminal colors. Defaults to 'auto'.")
|
||||
'--color',
|
||||
choices=['never', 'always', 'auto'],
|
||||
default='auto',
|
||||
help="When to use terminal colors. Defaults to 'auto'.")
|
||||
parser.add_argument(
|
||||
'-r', '--raw',
|
||||
action='store_true',
|
||||
help="Show the raw data including tag encodings.")
|
||||
'-r', '--raw',
|
||||
action='store_true',
|
||||
help="Show the raw data including tag encodings.")
|
||||
parser.add_argument(
|
||||
'-T', '--no-truncate',
|
||||
action='store_true',
|
||||
help="Don't truncate, show the full contents.")
|
||||
'-T', '--no-truncate',
|
||||
action='store_true',
|
||||
help="Don't truncate, show the full contents.")
|
||||
parser.add_argument(
|
||||
'-t', '--tree',
|
||||
action='store_true',
|
||||
help="Show the underlying rbyd trees.")
|
||||
'-t', '--tree',
|
||||
action='store_true',
|
||||
help="Show the underlying rbyd trees.")
|
||||
parser.add_argument(
|
||||
'-B', '--btree',
|
||||
action='store_true',
|
||||
help="Show the underlying B-trees.")
|
||||
'-B', '--btree',
|
||||
action='store_true',
|
||||
help="Show the underlying B-trees.")
|
||||
parser.add_argument(
|
||||
'-R', '--rbyd',
|
||||
action='store_true',
|
||||
help="Show the full underlying rbyd trees.")
|
||||
'-R', '--rbyd',
|
||||
action='store_true',
|
||||
help="Show the full underlying rbyd trees.")
|
||||
parser.add_argument(
|
||||
'-i', '--inner',
|
||||
action='store_true',
|
||||
help="Show inner branches.")
|
||||
'-i', '--inner',
|
||||
action='store_true',
|
||||
help="Show inner branches.")
|
||||
parser.add_argument(
|
||||
'-z', '--depth',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Depth of tree to show.")
|
||||
'-z', '--depth',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Depth of tree to show.")
|
||||
parser.add_argument(
|
||||
'-e', '--error-on-corrupt',
|
||||
action='store_true',
|
||||
help="Error if the filesystem is corrupt.")
|
||||
'-e', '--error-on-corrupt',
|
||||
action='store_true',
|
||||
help="Error if the filesystem is corrupt.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+248
-239
@@ -7,6 +7,7 @@ import math as mt
|
||||
import os
|
||||
import struct
|
||||
|
||||
|
||||
COLORS = [
|
||||
'34', # blue
|
||||
'31', # red
|
||||
@@ -156,108 +157,108 @@ def fromtag(data):
|
||||
def xxd(data, width=16):
|
||||
for i in range(0, len(data), width):
|
||||
yield '%-*s %-*s' % (
|
||||
3*width,
|
||||
' '.join('%02x' % b for b in data[i:i+width]),
|
||||
width,
|
||||
''.join(
|
||||
b if b >= ' ' and b <= '~' else '.'
|
||||
for b in map(chr, data[i:i+width])))
|
||||
3*width,
|
||||
' '.join('%02x' % b for b in data[i:i+width]),
|
||||
width,
|
||||
''.join(
|
||||
b if b >= ' ' and b <= '~' else '.'
|
||||
for b in map(chr, data[i:i+width])))
|
||||
|
||||
def tagrepr(tag, w=None, size=None, off=None):
|
||||
if (tag & 0x6fff) == TAG_NULL:
|
||||
return '%snull%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
' w%d' % w if w else '',
|
||||
' %d' % size if size else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
' w%d' % w if w else '',
|
||||
' %d' % size if size else '')
|
||||
elif (tag & 0x6f00) == TAG_CONFIG:
|
||||
return '%s%s%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'magic' if (tag & 0xfff) == TAG_MAGIC
|
||||
else 'version' if (tag & 0xfff) == TAG_VERSION
|
||||
else 'rcompat' if (tag & 0xfff) == TAG_RCOMPAT
|
||||
else 'wcompat' if (tag & 0xfff) == TAG_WCOMPAT
|
||||
else 'ocompat' if (tag & 0xfff) == TAG_OCOMPAT
|
||||
else 'geometry' if (tag & 0xfff) == TAG_GEOMETRY
|
||||
else 'namelimit' if (tag & 0xfff) == TAG_NAMELIMIT
|
||||
else 'filelimit' if (tag & 0xfff) == TAG_FILELIMIT
|
||||
else 'config 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'magic' if (tag & 0xfff) == TAG_MAGIC
|
||||
else 'version' if (tag & 0xfff) == TAG_VERSION
|
||||
else 'rcompat' if (tag & 0xfff) == TAG_RCOMPAT
|
||||
else 'wcompat' if (tag & 0xfff) == TAG_WCOMPAT
|
||||
else 'ocompat' if (tag & 0xfff) == TAG_OCOMPAT
|
||||
else 'geometry' if (tag & 0xfff) == TAG_GEOMETRY
|
||||
else 'namelimit' if (tag & 0xfff) == TAG_NAMELIMIT
|
||||
else 'filelimit' if (tag & 0xfff) == TAG_FILELIMIT
|
||||
else 'config 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x6f00) == TAG_GDELTA:
|
||||
return '%s%s%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'grmdelta' if (tag & 0xfff) == TAG_GRMDELTA
|
||||
else 'gdelta 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'grmdelta' if (tag & 0xfff) == TAG_GRMDELTA
|
||||
else 'gdelta 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x6f00) == TAG_NAME:
|
||||
return '%s%s%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'name' if (tag & 0xfff) == TAG_NAME
|
||||
else 'reg' if (tag & 0xfff) == TAG_REG
|
||||
else 'dir' if (tag & 0xfff) == TAG_DIR
|
||||
else 'orphan' if (tag & 0xfff) == TAG_ORPHAN
|
||||
else 'bookmark' if (tag & 0xfff) == TAG_BOOKMARK
|
||||
else 'name 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'name' if (tag & 0xfff) == TAG_NAME
|
||||
else 'reg' if (tag & 0xfff) == TAG_REG
|
||||
else 'dir' if (tag & 0xfff) == TAG_DIR
|
||||
else 'orphan' if (tag & 0xfff) == TAG_ORPHAN
|
||||
else 'bookmark' if (tag & 0xfff) == TAG_BOOKMARK
|
||||
else 'name 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x6f00) == TAG_STRUCT:
|
||||
return '%s%s%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'data' if (tag & 0xfff) == TAG_DATA
|
||||
else 'block' if (tag & 0xfff) == TAG_BLOCK
|
||||
else 'bshrub' if (tag & 0xfff) == TAG_BSHRUB
|
||||
else 'btree' if (tag & 0xfff) == TAG_BTREE
|
||||
else 'mroot' if (tag & 0xfff) == TAG_MROOT
|
||||
else 'mdir' if (tag & 0xfff) == TAG_MDIR
|
||||
else 'mtree' if (tag & 0xfff) == TAG_MTREE
|
||||
else 'did' if (tag & 0xfff) == TAG_DID
|
||||
else 'branch' if (tag & 0xfff) == TAG_BRANCH
|
||||
else 'struct 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'data' if (tag & 0xfff) == TAG_DATA
|
||||
else 'block' if (tag & 0xfff) == TAG_BLOCK
|
||||
else 'bshrub' if (tag & 0xfff) == TAG_BSHRUB
|
||||
else 'btree' if (tag & 0xfff) == TAG_BTREE
|
||||
else 'mroot' if (tag & 0xfff) == TAG_MROOT
|
||||
else 'mdir' if (tag & 0xfff) == TAG_MDIR
|
||||
else 'mtree' if (tag & 0xfff) == TAG_MTREE
|
||||
else 'did' if (tag & 0xfff) == TAG_DID
|
||||
else 'branch' if (tag & 0xfff) == TAG_BRANCH
|
||||
else 'struct 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x6e00) == TAG_ATTR:
|
||||
return '%s%sattr 0x%02x%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
's' if tag & 0x100 else 'u',
|
||||
((tag & 0x100) >> 1) ^ (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
's' if tag & 0x100 else 'u',
|
||||
((tag & 0x100) >> 1) ^ (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif tag & TAG_ALT:
|
||||
return 'alt%s%s%s%s%s' % (
|
||||
'r' if tag & TAG_R else 'b',
|
||||
'a' if tag & 0x0fff == 0 and tag & TAG_GT
|
||||
else 'n' if tag & 0x0fff == 0
|
||||
else 'gt' if tag & TAG_GT
|
||||
else 'le',
|
||||
' 0x%x' % (tag & 0x0fff) if tag & 0x0fff != 0 else '',
|
||||
' w%d' % w if w is not None else '',
|
||||
' 0x%x' % (0xffffffff & (off-size))
|
||||
if size and off is not None
|
||||
else ' -%d' % size if size
|
||||
else '')
|
||||
'r' if tag & TAG_R else 'b',
|
||||
'a' if tag & 0x0fff == 0 and tag & TAG_GT
|
||||
else 'n' if tag & 0x0fff == 0
|
||||
else 'gt' if tag & TAG_GT
|
||||
else 'le',
|
||||
' 0x%x' % (tag & 0x0fff) if tag & 0x0fff != 0 else '',
|
||||
' w%d' % w if w is not None else '',
|
||||
' 0x%x' % (0xffffffff & (off-size))
|
||||
if size and off is not None
|
||||
else ' -%d' % size if size
|
||||
else '')
|
||||
elif (tag & 0x7f00) == TAG_CKSUM:
|
||||
return 'cksum%s%s%s%s%s' % (
|
||||
'q' if not tag & 0xfc and tag & TAG_Q else '',
|
||||
'p' if not tag & 0xfc and tag & TAG_P else '',
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xfc else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'q' if not tag & 0xfc and tag & TAG_Q else '',
|
||||
'p' if not tag & 0xfc and tag & TAG_P else '',
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xfc else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x7f00) == TAG_NOTE:
|
||||
return 'note%s%s%s' % (
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x7f00) == TAG_ECKSUM:
|
||||
return 'ecksum%s%s%s' % (
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
else:
|
||||
return '0x%04x%s%s' % (
|
||||
tag,
|
||||
' w%d' % w if w is not None else '',
|
||||
' %d' % size if size is not None else '')
|
||||
tag,
|
||||
' w%d' % w if w is not None else '',
|
||||
' %d' % size if size is not None else '')
|
||||
|
||||
|
||||
def dbg_log(data, block_size, rev, eoff, weight, *,
|
||||
@@ -291,9 +292,9 @@ def dbg_log(data, block_size, rev, eoff, weight, *,
|
||||
x = 0
|
||||
while any(
|
||||
max(a, b) >= min(a_, b_)
|
||||
and max(a_, b_) >= min(a, b)
|
||||
and x == x_
|
||||
for a_, b_, x_, _ in jumps[:j]):
|
||||
and max(a_, b_) >= min(a, b)
|
||||
and x == x_
|
||||
for a_, b_, x_, _ in jumps[:j]):
|
||||
x += 1
|
||||
jumps[j] = a, b, x, c
|
||||
|
||||
@@ -303,9 +304,9 @@ def dbg_log(data, block_size, rev, eoff, weight, *,
|
||||
for a, b, x, c in jumps:
|
||||
c_start = (
|
||||
'\x1b[33m' if color and c == 'y'
|
||||
else '\x1b[31m' if color and c == 'r'
|
||||
else '\x1b[90m' if color
|
||||
else '')
|
||||
else '\x1b[31m' if color and c == 'r'
|
||||
else '\x1b[90m' if color
|
||||
else '')
|
||||
c_stop = '\x1b[m' if color else ''
|
||||
|
||||
if j == a:
|
||||
@@ -321,7 +322,7 @@ def dbg_log(data, block_size, rev, eoff, weight, *,
|
||||
chars[2*x+1] = '%s|%s' % (c_start, c_stop)
|
||||
|
||||
return ''.join(chars.get(x, ' ')
|
||||
for x in range(max(chars.keys(), default=0)+1))
|
||||
for x in range(max(chars.keys(), default=0)+1))
|
||||
|
||||
# preprocess lifetimes
|
||||
lifetime_width = 0
|
||||
@@ -333,7 +334,7 @@ def dbg_log(data, block_size, rev, eoff, weight, *,
|
||||
self.tags = set()
|
||||
self.color = COLORS[self.__class__.color_i]
|
||||
self.__class__.color_i = (
|
||||
self.__class__.color_i + 1) % len(COLORS)
|
||||
self.__class__.color_i + 1) % len(COLORS)
|
||||
|
||||
def add(self, j):
|
||||
self.tags.add(j)
|
||||
@@ -357,8 +358,8 @@ def dbg_log(data, block_size, rev, eoff, weight, *,
|
||||
def checkpoint(j, weights, lifetimes, grows, shrinks, tags):
|
||||
checkpoint_js.append(j)
|
||||
checkpoints.append((
|
||||
weights.copy(), lifetimes.copy(),
|
||||
grows, shrinks, tags))
|
||||
weights.copy(), lifetimes.copy(),
|
||||
grows, shrinks, tags))
|
||||
|
||||
lower_, upper_ = 0, 0
|
||||
weight_ = 0
|
||||
@@ -397,7 +398,7 @@ def dbg_log(data, block_size, rev, eoff, weight, *,
|
||||
if rid_ > 0:
|
||||
weights[i:i+1] = [rid_, delta, weights[i]-rid_]
|
||||
lifetimes[i:i+1] = [
|
||||
lifetimes[i], Lifetime(j), lifetimes[i]]
|
||||
lifetimes[i], Lifetime(j), lifetimes[i]]
|
||||
else:
|
||||
weights[i:i] = [delta]
|
||||
lifetimes[i:i] = [Lifetime(j)]
|
||||
@@ -438,9 +439,9 @@ def dbg_log(data, block_size, rev, eoff, weight, *,
|
||||
checkpoint(j, weights, lifetimes, set(), set(), {i})
|
||||
|
||||
lifetime_width = 2*max((
|
||||
sum(1 for lifetime in lifetimes if lifetime)
|
||||
for _, lifetimes, _, _, _ in checkpoints),
|
||||
default=0)
|
||||
sum(1 for lifetime in lifetimes if lifetime)
|
||||
for _, lifetimes, _, _, _ in checkpoints),
|
||||
default=0)
|
||||
|
||||
def lifetimerepr(j):
|
||||
x = bisect.bisect(checkpoint_js, j)-1
|
||||
@@ -476,12 +477,12 @@ def dbg_log(data, block_size, rev, eoff, weight, *,
|
||||
colors.append(lifetime.color)
|
||||
|
||||
return '%s%*s' % (
|
||||
''.join('%s%s%s' % (
|
||||
'\x1b[%sm' % c if color else '',
|
||||
r,
|
||||
'\x1b[m' if color else '')
|
||||
for r, c in zip(reprs, colors)),
|
||||
lifetime_width - sum(len(r) for r in reprs), '')
|
||||
''.join('%s%s%s' % (
|
||||
'\x1b[%sm' % c if color else '',
|
||||
r,
|
||||
'\x1b[m' if color else '')
|
||||
for r, c in zip(reprs, colors)),
|
||||
lifetime_width - sum(len(r) for r in reprs), '')
|
||||
|
||||
|
||||
# dynamically size the id field
|
||||
@@ -518,10 +519,10 @@ def dbg_log(data, block_size, rev, eoff, weight, *,
|
||||
# print revision count
|
||||
if args.get('raw'):
|
||||
print('%8s: %*s%*s %s' % (
|
||||
'%04x' % 0,
|
||||
lifetime_width, '',
|
||||
2*w_width+1, '',
|
||||
next(xxd(data[0:4]))))
|
||||
'%04x' % 0,
|
||||
lifetime_width, '',
|
||||
2*w_width+1, '',
|
||||
next(xxd(data[0:4]))))
|
||||
|
||||
# print tags
|
||||
cksum = crc32c(data[0:4])
|
||||
@@ -582,44 +583,49 @@ def dbg_log(data, block_size, rev, eoff, weight, *,
|
||||
|
||||
# show human-readable tag representation
|
||||
print('%s%08x:%s %*s%s%*s %-*s%s%s%s' % (
|
||||
'\x1b[90m' if color and j >= eoff else '',
|
||||
j,
|
||||
'\x1b[m' if color and j >= eoff else '',
|
||||
lifetime_width, lifetimerepr(j) if args.get('lifetimes') else '',
|
||||
'\x1b[90m' if color and j >= eoff else '',
|
||||
2*w_width+1, '' if (tag & 0xe000) != 0x0000
|
||||
else '%d-%d' % (rid-(w-1), rid) if w > 1
|
||||
else rid,
|
||||
56+w_width, '%-*s %s' % (
|
||||
21+w_width, tagrepr(tag, w, size, j),
|
||||
next(xxd(data[j+d:j+d+min(size, 8)], 8), '')
|
||||
if not args.get('raw') and not args.get('no_truncate')
|
||||
and not tag & TAG_ALT else ''),
|
||||
' (%s)' % ', '.join(notes) if notes else '',
|
||||
'\x1b[m' if color and j >= eoff else '',
|
||||
' %s' % jumprepr(j)
|
||||
if args.get('jumps') and not notes else ''))
|
||||
'\x1b[90m' if color and j >= eoff else '',
|
||||
j,
|
||||
'\x1b[m' if color and j >= eoff else '',
|
||||
lifetime_width, lifetimerepr(j)
|
||||
if args.get('lifetimes')
|
||||
else '',
|
||||
'\x1b[90m' if color and j >= eoff else '',
|
||||
2*w_width+1, '' if (tag & 0xe000) != 0x0000
|
||||
else '%d-%d' % (rid-(w-1), rid) if w > 1
|
||||
else rid,
|
||||
56+w_width, '%-*s %s' % (
|
||||
21+w_width, tagrepr(tag, w, size, j),
|
||||
next(xxd(data[j+d:j+d+min(size, 8)], 8), '')
|
||||
if not args.get('raw')
|
||||
and not args.get('no_truncate')
|
||||
and not tag & TAG_ALT
|
||||
else ''),
|
||||
' (%s)' % ', '.join(notes) if notes else '',
|
||||
'\x1b[m' if color and j >= eoff else '',
|
||||
' %s' % jumprepr(j)
|
||||
if args.get('jumps') and not notes
|
||||
else ''))
|
||||
|
||||
# show on-disk encoding of tags
|
||||
if args.get('raw'):
|
||||
for o, line in enumerate(xxd(data[j:j+d])):
|
||||
print('%s%8s: %*s%*s %s%s' % (
|
||||
'\x1b[90m' if color and j >= eoff else '',
|
||||
'%04x' % (j + o*16),
|
||||
lifetime_width, '',
|
||||
2*w_width+1, '',
|
||||
line,
|
||||
'\x1b[m' if color and j >= eoff else ''))
|
||||
if args.get('raw') or args.get('no_truncate'):
|
||||
if not tag & TAG_ALT:
|
||||
for o, line in enumerate(xxd(data[j+d:j+d+size])):
|
||||
print('%s%8s: %*s%*s %s%s' % (
|
||||
'\x1b[90m' if color and j >= eoff else '',
|
||||
'%04x' % (j+d + o*16),
|
||||
'%04x' % (j + o*16),
|
||||
lifetime_width, '',
|
||||
2*w_width+1, '',
|
||||
line,
|
||||
'\x1b[m' if color and j >= eoff else ''))
|
||||
if args.get('raw') or args.get('no_truncate'):
|
||||
if not tag & TAG_ALT:
|
||||
for o, line in enumerate(xxd(data[j+d:j+d+size])):
|
||||
print('%s%8s: %*s%*s %s%s' % (
|
||||
'\x1b[90m' if color and j >= eoff else '',
|
||||
'%04x' % (j+d + o*16),
|
||||
lifetime_width, '',
|
||||
2*w_width+1, '',
|
||||
line,
|
||||
'\x1b[m' if color and j >= eoff else ''))
|
||||
|
||||
|
||||
def dbg_tree(data, block_size, rev, trunk, weight, *,
|
||||
@@ -757,8 +763,8 @@ def dbg_tree(data, block_size, rev, trunk, weight, *,
|
||||
else:
|
||||
if 'h' not in alts[j_]:
|
||||
alts[j_]['h'] = max(
|
||||
rec_height(alts[j_]['f']),
|
||||
rec_height(alts[j_]['nf'])) + 1
|
||||
rec_height(alts[j_]['f']),
|
||||
rec_height(alts[j_]['nf'])) + 1
|
||||
return alts[j_]['h']
|
||||
|
||||
for j_ in alts.keys():
|
||||
@@ -801,8 +807,8 @@ def dbg_tree(data, block_size, rev, trunk, weight, *,
|
||||
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)
|
||||
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:
|
||||
@@ -827,13 +833,13 @@ def dbg_tree(data, block_size, rev, trunk, weight, *,
|
||||
t, c, was = branchrepr((rid, 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 ''))
|
||||
'\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)
|
||||
|
||||
@@ -850,33 +856,36 @@ def dbg_tree(data, block_size, rev, trunk, weight, *,
|
||||
|
||||
# show human-readable tag representation
|
||||
print('%08x: %s%*s %-*s %s' % (
|
||||
j,
|
||||
treerepr(rid, tag)
|
||||
if args.get('tree') or args.get('rbyd') else '',
|
||||
2*w_width+1, '%d-%d' % (rid-(w-1), rid)
|
||||
if w > 1 else rid
|
||||
if w > 0 or i == 0 else '',
|
||||
21+w_width, tagrepr(tag, w, size, j),
|
||||
next(xxd(data[j+d:j+d+min(size, 8)], 8), '')
|
||||
if not args.get('raw') and not args.get('no_truncate')
|
||||
and not tag & TAG_ALT else ''))
|
||||
j,
|
||||
treerepr(rid, tag)
|
||||
if args.get('tree') or args.get('rbyd')
|
||||
else '',
|
||||
2*w_width+1, '%d-%d' % (rid-(w-1), rid) if w > 1
|
||||
else rid if w > 0 or i == 0
|
||||
else '',
|
||||
21+w_width, tagrepr(tag, w, size, j),
|
||||
next(xxd(data[j+d:j+d+min(size, 8)], 8), '')
|
||||
if not args.get('raw')
|
||||
and not args.get('no_truncate')
|
||||
and not tag & TAG_ALT
|
||||
else ''))
|
||||
|
||||
# show on-disk encoding of tags
|
||||
if args.get('raw'):
|
||||
for o, line in enumerate(xxd(data[j:j+d])):
|
||||
print('%8s: %*s%*s %s' % (
|
||||
'%04x' % (j + o*16),
|
||||
t_width, '',
|
||||
2*w_width+1, '',
|
||||
line))
|
||||
'%04x' % (j + o*16),
|
||||
t_width, '',
|
||||
2*w_width+1, '',
|
||||
line))
|
||||
if args.get('raw') or args.get('no_truncate'):
|
||||
if not tag & TAG_ALT:
|
||||
for o, line in enumerate(xxd(data[j+d:j+d+size])):
|
||||
print('%8s: %*s%*s %s' % (
|
||||
'%04x' % (j+d + o*16),
|
||||
t_width, '',
|
||||
2*w_width+1, '',
|
||||
line))
|
||||
'%04x' % (j+d + o*16),
|
||||
t_width, '',
|
||||
2*w_width+1, '',
|
||||
line))
|
||||
|
||||
|
||||
def main(disk, blocks=None, *,
|
||||
@@ -912,12 +921,12 @@ def main(disk, blocks=None, *,
|
||||
|
||||
# blocks may also encode trunks
|
||||
blocks, trunks = (
|
||||
[block[0] if isinstance(block, tuple) else block
|
||||
for block in blocks],
|
||||
[trunk if trunk is not None
|
||||
else block[1] if isinstance(block, tuple)
|
||||
else None
|
||||
for block in blocks])
|
||||
[block[0] if isinstance(block, tuple) else block
|
||||
for block in blocks],
|
||||
[trunk if trunk is not None
|
||||
else block[1] if isinstance(block, tuple)
|
||||
else None
|
||||
for block in blocks])
|
||||
|
||||
# read each block
|
||||
datas = []
|
||||
@@ -1020,41 +1029,41 @@ def main(disk, blocks=None, *,
|
||||
# compare with sequence arithmetic
|
||||
if trunk_ and (
|
||||
not trunks_[i]
|
||||
or not ((rev - revs[i]) & 0x80000000)
|
||||
or (rev == revs[i] and trunk_ > trunks_[i])):
|
||||
or not ((rev - revs[i]) & 0x80000000)
|
||||
or (rev == revs[i] and trunk_ > trunks_[i])):
|
||||
i = i_
|
||||
|
||||
# print contents of the winning metadata block
|
||||
block, data, rev, eoff, trunk_, weight, cksum = (
|
||||
blocks[i],
|
||||
datas[i],
|
||||
revs[i],
|
||||
eoffs[i],
|
||||
trunks_[i],
|
||||
weights[i],
|
||||
cksums[i])
|
||||
blocks[i],
|
||||
datas[i],
|
||||
revs[i],
|
||||
eoffs[i],
|
||||
trunks_[i],
|
||||
weights[i],
|
||||
cksums[i])
|
||||
|
||||
print('rbyd %s w%d, rev %08x, size %d, cksum %08x' % (
|
||||
'0x%x.%x' % (block, trunk_)
|
||||
if len(blocks) == 1
|
||||
else '0x{%x,%s}.%x' % (
|
||||
block,
|
||||
','.join('%x' % blocks[(i+1+j) % len(blocks)]
|
||||
for j in range(len(blocks)-1)),
|
||||
trunk_),
|
||||
weight,
|
||||
rev,
|
||||
eoff,
|
||||
cksum))
|
||||
'0x%x.%x' % (block, trunk_)
|
||||
if len(blocks) == 1
|
||||
else '0x{%x,%s}.%x' % (
|
||||
block,
|
||||
','.join('%x' % blocks[(i+1+j) % len(blocks)]
|
||||
for j in range(len(blocks)-1)),
|
||||
trunk_),
|
||||
weight,
|
||||
rev,
|
||||
eoff,
|
||||
cksum))
|
||||
|
||||
if args.get('log'):
|
||||
dbg_log(data, block_size, rev, eoff, weight,
|
||||
color=color,
|
||||
**args)
|
||||
color=color,
|
||||
**args)
|
||||
else:
|
||||
dbg_tree(data, block_size, rev, trunk_, weight,
|
||||
color=color,
|
||||
**args)
|
||||
color=color,
|
||||
**args)
|
||||
|
||||
if args.get('error_on_corrupt') and eoff == 0:
|
||||
sys.exit(2)
|
||||
@@ -1064,69 +1073,69 @@ if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Debug rbyd metadata.",
|
||||
allow_abbrev=False)
|
||||
description="Debug rbyd metadata.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'disk',
|
||||
help="File containing the block device.")
|
||||
'disk',
|
||||
help="File containing the block device.")
|
||||
parser.add_argument(
|
||||
'blocks',
|
||||
nargs='*',
|
||||
type=rbydaddr,
|
||||
help="Block address of metadata blocks.")
|
||||
'blocks',
|
||||
nargs='*',
|
||||
type=rbydaddr,
|
||||
help="Block address of metadata blocks.")
|
||||
parser.add_argument(
|
||||
'-b', '--block-size',
|
||||
type=bdgeom,
|
||||
help="Block size/geometry in bytes.")
|
||||
'-b', '--block-size',
|
||||
type=bdgeom,
|
||||
help="Block size/geometry in bytes.")
|
||||
parser.add_argument(
|
||||
'--block-count',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Block count in blocks.")
|
||||
'--block-count',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Block count in blocks.")
|
||||
parser.add_argument(
|
||||
'--trunk',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Use this offset as the trunk of the tree.")
|
||||
'--trunk',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Use this offset as the trunk of the tree.")
|
||||
parser.add_argument(
|
||||
'--color',
|
||||
choices=['never', 'always', 'auto'],
|
||||
default='auto',
|
||||
help="When to use terminal colors. Defaults to 'auto'.")
|
||||
'--color',
|
||||
choices=['never', 'always', 'auto'],
|
||||
default='auto',
|
||||
help="When to use terminal colors. Defaults to 'auto'.")
|
||||
parser.add_argument(
|
||||
'-a', '--all',
|
||||
action='store_true',
|
||||
help="Don't stop parsing on bad commits.")
|
||||
'-a', '--all',
|
||||
action='store_true',
|
||||
help="Don't stop parsing on bad commits.")
|
||||
parser.add_argument(
|
||||
'-l', '--log',
|
||||
action='store_true',
|
||||
help="Show the raw tags as they appear in the log.")
|
||||
'-l', '--log',
|
||||
action='store_true',
|
||||
help="Show the raw tags as they appear in the log.")
|
||||
parser.add_argument(
|
||||
'-r', '--raw',
|
||||
action='store_true',
|
||||
help="Show the raw data including tag encodings.")
|
||||
'-r', '--raw',
|
||||
action='store_true',
|
||||
help="Show the raw data including tag encodings.")
|
||||
parser.add_argument(
|
||||
'-T', '--no-truncate',
|
||||
action='store_true',
|
||||
help="Don't truncate, show the full contents.")
|
||||
'-T', '--no-truncate',
|
||||
action='store_true',
|
||||
help="Don't truncate, show the full contents.")
|
||||
parser.add_argument(
|
||||
'-t', '--tree',
|
||||
action='store_true',
|
||||
help="Show the rbyd tree.")
|
||||
'-t', '--tree',
|
||||
action='store_true',
|
||||
help="Show the rbyd tree.")
|
||||
parser.add_argument(
|
||||
'-R', '--rbyd',
|
||||
action='store_true',
|
||||
help="Show the full rbyd tree.")
|
||||
'-R', '--rbyd',
|
||||
action='store_true',
|
||||
help="Show the full rbyd tree.")
|
||||
parser.add_argument(
|
||||
'-j', '--jumps',
|
||||
action='store_true',
|
||||
help="Show alt pointer jumps in the margin.")
|
||||
'-j', '--jumps',
|
||||
action='store_true',
|
||||
help="Show alt pointer jumps in the margin.")
|
||||
parser.add_argument(
|
||||
'-g', '--lifetimes',
|
||||
action='store_true',
|
||||
help="Show inserts/deletes of ids in the margin.")
|
||||
'-g', '--lifetimes',
|
||||
action='store_true',
|
||||
help="Show inserts/deletes of ids in the margin.")
|
||||
parser.add_argument(
|
||||
'-e', '--error-on-corrupt',
|
||||
action='store_true',
|
||||
help="Error if no valid commit is found.")
|
||||
'-e', '--error-on-corrupt',
|
||||
action='store_true',
|
||||
help="Error if no valid commit is found.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+107
-106
@@ -121,98 +121,98 @@ def fromleb128(data):
|
||||
def tagrepr(tag, w=None, size=None, off=None):
|
||||
if (tag & 0x6fff) == TAG_NULL:
|
||||
return '%snull%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
' w%d' % w if w else '',
|
||||
' %d' % size if size else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
' w%d' % w if w else '',
|
||||
' %d' % size if size else '')
|
||||
elif (tag & 0x6f00) == TAG_CONFIG:
|
||||
return '%s%s%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'magic' if (tag & 0xfff) == TAG_MAGIC
|
||||
else 'version' if (tag & 0xfff) == TAG_VERSION
|
||||
else 'rcompat' if (tag & 0xfff) == TAG_RCOMPAT
|
||||
else 'wcompat' if (tag & 0xfff) == TAG_WCOMPAT
|
||||
else 'ocompat' if (tag & 0xfff) == TAG_OCOMPAT
|
||||
else 'geometry' if (tag & 0xfff) == TAG_GEOMETRY
|
||||
else 'namelimit' if (tag & 0xfff) == TAG_NAMELIMIT
|
||||
else 'filelimit' if (tag & 0xfff) == TAG_FILELIMIT
|
||||
else 'config 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'magic' if (tag & 0xfff) == TAG_MAGIC
|
||||
else 'version' if (tag & 0xfff) == TAG_VERSION
|
||||
else 'rcompat' if (tag & 0xfff) == TAG_RCOMPAT
|
||||
else 'wcompat' if (tag & 0xfff) == TAG_WCOMPAT
|
||||
else 'ocompat' if (tag & 0xfff) == TAG_OCOMPAT
|
||||
else 'geometry' if (tag & 0xfff) == TAG_GEOMETRY
|
||||
else 'namelimit' if (tag & 0xfff) == TAG_NAMELIMIT
|
||||
else 'filelimit' if (tag & 0xfff) == TAG_FILELIMIT
|
||||
else 'config 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x6f00) == TAG_GDELTA:
|
||||
return '%s%s%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'grmdelta' if (tag & 0xfff) == TAG_GRMDELTA
|
||||
else 'gdelta 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'grmdelta' if (tag & 0xfff) == TAG_GRMDELTA
|
||||
else 'gdelta 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x6f00) == TAG_NAME:
|
||||
return '%s%s%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'name' if (tag & 0xfff) == TAG_NAME
|
||||
else 'reg' if (tag & 0xfff) == TAG_REG
|
||||
else 'dir' if (tag & 0xfff) == TAG_DIR
|
||||
else 'orphan' if (tag & 0xfff) == TAG_ORPHAN
|
||||
else 'bookmark' if (tag & 0xfff) == TAG_BOOKMARK
|
||||
else 'name 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'name' if (tag & 0xfff) == TAG_NAME
|
||||
else 'reg' if (tag & 0xfff) == TAG_REG
|
||||
else 'dir' if (tag & 0xfff) == TAG_DIR
|
||||
else 'orphan' if (tag & 0xfff) == TAG_ORPHAN
|
||||
else 'bookmark' if (tag & 0xfff) == TAG_BOOKMARK
|
||||
else 'name 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x6f00) == TAG_STRUCT:
|
||||
return '%s%s%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'data' if (tag & 0xfff) == TAG_DATA
|
||||
else 'block' if (tag & 0xfff) == TAG_BLOCK
|
||||
else 'bshrub' if (tag & 0xfff) == TAG_BSHRUB
|
||||
else 'btree' if (tag & 0xfff) == TAG_BTREE
|
||||
else 'mroot' if (tag & 0xfff) == TAG_MROOT
|
||||
else 'mdir' if (tag & 0xfff) == TAG_MDIR
|
||||
else 'mtree' if (tag & 0xfff) == TAG_MTREE
|
||||
else 'did' if (tag & 0xfff) == TAG_DID
|
||||
else 'branch' if (tag & 0xfff) == TAG_BRANCH
|
||||
else 'struct 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
'data' if (tag & 0xfff) == TAG_DATA
|
||||
else 'block' if (tag & 0xfff) == TAG_BLOCK
|
||||
else 'bshrub' if (tag & 0xfff) == TAG_BSHRUB
|
||||
else 'btree' if (tag & 0xfff) == TAG_BTREE
|
||||
else 'mroot' if (tag & 0xfff) == TAG_MROOT
|
||||
else 'mdir' if (tag & 0xfff) == TAG_MDIR
|
||||
else 'mtree' if (tag & 0xfff) == TAG_MTREE
|
||||
else 'did' if (tag & 0xfff) == TAG_DID
|
||||
else 'branch' if (tag & 0xfff) == TAG_BRANCH
|
||||
else 'struct 0x%02x' % (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x6e00) == TAG_ATTR:
|
||||
return '%s%sattr 0x%02x%s%s' % (
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
's' if tag & 0x100 else 'u',
|
||||
((tag & 0x100) >> 1) ^ (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'shrub' if tag & TAG_SHRUB else '',
|
||||
's' if tag & 0x100 else 'u',
|
||||
((tag & 0x100) >> 1) ^ (tag & 0xff),
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif tag & TAG_ALT:
|
||||
return 'alt%s%s%s%s%s' % (
|
||||
'r' if tag & TAG_R else 'b',
|
||||
'a' if tag & 0x0fff == 0 and tag & TAG_GT
|
||||
else 'n' if tag & 0x0fff == 0
|
||||
else 'gt' if tag & TAG_GT
|
||||
else 'le',
|
||||
' 0x%x' % (tag & 0x0fff) if tag & 0x0fff != 0 else '',
|
||||
' w%d' % w if w is not None else '',
|
||||
' 0x%x' % (0xffffffff & (off-size))
|
||||
if size and off is not None
|
||||
else ' -%d' % size if size
|
||||
else '')
|
||||
'r' if tag & TAG_R else 'b',
|
||||
'a' if tag & 0x0fff == 0 and tag & TAG_GT
|
||||
else 'n' if tag & 0x0fff == 0
|
||||
else 'gt' if tag & TAG_GT
|
||||
else 'le',
|
||||
' 0x%x' % (tag & 0x0fff) if tag & 0x0fff != 0 else '',
|
||||
' w%d' % w if w is not None else '',
|
||||
' 0x%x' % (0xffffffff & (off-size))
|
||||
if size and off is not None
|
||||
else ' -%d' % size if size
|
||||
else '')
|
||||
elif (tag & 0x7f00) == TAG_CKSUM:
|
||||
return 'cksum%s%s%s%s%s' % (
|
||||
'q' if not tag & 0xfc and tag & TAG_Q else '',
|
||||
'p' if not tag & 0xfc and tag & TAG_P else '',
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xfc else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
'q' if not tag & 0xfc and tag & TAG_Q else '',
|
||||
'p' if not tag & 0xfc and tag & TAG_P else '',
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xfc else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x7f00) == TAG_NOTE:
|
||||
return 'note%s%s%s' % (
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
elif (tag & 0x7f00) == TAG_ECKSUM:
|
||||
return 'ecksum%s%s%s' % (
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||
' w%d' % w if w else '',
|
||||
' %s' % size if size is not None else '')
|
||||
else:
|
||||
return '0x%04x%s%s' % (
|
||||
tag,
|
||||
' w%d' % w if w is not None else '',
|
||||
' %d' % size if size is not None else '')
|
||||
tag,
|
||||
' w%d' % w if w is not None else '',
|
||||
' %d' % size if size is not None else '')
|
||||
|
||||
|
||||
def list_tags():
|
||||
@@ -221,7 +221,7 @@ def list_tags():
|
||||
import re
|
||||
tags = []
|
||||
tag_pattern = re.compile(
|
||||
'^(?P<name>TAG_[^ ]*) *= *(?P<tag>[^ #]+) *#+ *(?P<comment>.*)$')
|
||||
'^(?P<name>TAG_[^ ]*) *= *(?P<tag>[^ #]+) *#+ *(?P<comment>.*)$')
|
||||
for line in inspect.getsourcelines(
|
||||
inspect.getmodule(inspect.currentframe()))[0]:
|
||||
m = tag_pattern.match(line)
|
||||
@@ -236,8 +236,8 @@ def list_tags():
|
||||
# print
|
||||
for n, t, c in tags:
|
||||
print('%-*s %s' % (
|
||||
w[0], 'LFSR_'+n,
|
||||
c))
|
||||
w[0], 'LFSR_'+n,
|
||||
c))
|
||||
|
||||
def dbg_tag(data):
|
||||
if isinstance(data, int):
|
||||
@@ -305,12 +305,12 @@ def main(tags, *,
|
||||
|
||||
# blocks may also encode offsets
|
||||
blocks, offs = (
|
||||
[block[0] if isinstance(block, tuple) else block
|
||||
for block in blocks],
|
||||
[off if off is not None
|
||||
else block[1] if isinstance(block, tuple)
|
||||
else None
|
||||
for block in blocks])
|
||||
[block[0] if isinstance(block, tuple) else block
|
||||
for block in blocks],
|
||||
[off if off is not None
|
||||
else block[1] if isinstance(block, tuple)
|
||||
else None
|
||||
for block in blocks])
|
||||
|
||||
# read each tag
|
||||
for block, off in zip(blocks, offs):
|
||||
@@ -319,40 +319,41 @@ def main(tags, *,
|
||||
data = f.read(2+5+5)
|
||||
dbg_tag(data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Decode littlefs tags.",
|
||||
allow_abbrev=False)
|
||||
description="Decode littlefs tags.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'tags',
|
||||
nargs='*',
|
||||
help="Tags to decode.")
|
||||
'tags',
|
||||
nargs='*',
|
||||
help="Tags to decode.")
|
||||
parser.add_argument(
|
||||
'-l', '--list',
|
||||
action='store_true',
|
||||
help="List all known tags.")
|
||||
'-l', '--list',
|
||||
action='store_true',
|
||||
help="List all known tags.")
|
||||
parser.add_argument(
|
||||
'-x', '--hex',
|
||||
action='store_true',
|
||||
help="Interpret as a sequence of hex bytes.")
|
||||
'-x', '--hex',
|
||||
action='store_true',
|
||||
help="Interpret as a sequence of hex bytes.")
|
||||
parser.add_argument(
|
||||
'-s', '--string',
|
||||
action='store_true',
|
||||
help="Interpret as strings.")
|
||||
'-s', '--string',
|
||||
action='store_true',
|
||||
help="Interpret as strings.")
|
||||
parser.add_argument(
|
||||
'-b', '--block-size',
|
||||
type=bdgeom,
|
||||
help="Block size/geometry in bytes.")
|
||||
'-b', '--block-size',
|
||||
type=bdgeom,
|
||||
help="Block size/geometry in bytes.")
|
||||
parser.add_argument(
|
||||
'--block-count',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Block count in blocks.")
|
||||
'--block-count',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Block count in blocks.")
|
||||
parser.add_argument(
|
||||
'--off',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Use this offset.")
|
||||
'--off',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Use this offset.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+20
-19
@@ -29,17 +29,17 @@ def main(paths, **args):
|
||||
# interpret as sequence of hex bytes
|
||||
if args.get('hex'):
|
||||
print('%01x' % parity(ft.reduce(
|
||||
op.xor,
|
||||
bytes(int(path, 16) for path in paths),
|
||||
0)))
|
||||
op.xor,
|
||||
bytes(int(path, 16) for path in paths),
|
||||
0)))
|
||||
|
||||
# interpret as strings
|
||||
elif args.get('string'):
|
||||
for path in paths:
|
||||
print('%01x' % parity(ft.reduce(
|
||||
op.xor,
|
||||
path.encode('utf8'),
|
||||
0)))
|
||||
op.xor,
|
||||
path.encode('utf8'),
|
||||
0)))
|
||||
|
||||
# default to interpreting as paths
|
||||
else:
|
||||
@@ -63,24 +63,25 @@ def main(paths, **args):
|
||||
else:
|
||||
print('%01x' % parity(xor))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Calculates parity.",
|
||||
allow_abbrev=False)
|
||||
description="Calculates parity.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'paths',
|
||||
nargs='*',
|
||||
help="Paths to read. Reads stdin by default.")
|
||||
'paths',
|
||||
nargs='*',
|
||||
help="Paths to read. Reads stdin by default.")
|
||||
parser.add_argument(
|
||||
'-x', '--hex',
|
||||
action='store_true',
|
||||
help="Interpret as a sequence of hex bytes.")
|
||||
'-x', '--hex',
|
||||
action='store_true',
|
||||
help="Interpret as a sequence of hex bytes.")
|
||||
parser.add_argument(
|
||||
'-s', '--string',
|
||||
action='store_true',
|
||||
help="Interpret as strings.")
|
||||
'-s', '--string',
|
||||
action='store_true',
|
||||
help="Interpret as strings.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+384
-372
File diff suppressed because it is too large
Load Diff
+357
-344
@@ -118,15 +118,15 @@ class PerfBdResult(co.namedtuple('PerfBdResult', [
|
||||
readed=0, proged=0, erased=0,
|
||||
children=[]):
|
||||
return super().__new__(cls, file, function, int(RInt(line)),
|
||||
RInt(readed), RInt(proged), RInt(erased),
|
||||
children)
|
||||
RInt(readed), RInt(proged), RInt(erased),
|
||||
children)
|
||||
|
||||
def __add__(self, other):
|
||||
return PerfBdResult(self.file, self.function, self.line,
|
||||
self.readed + other.readed,
|
||||
self.proged + other.proged,
|
||||
self.erased + other.erased,
|
||||
self.children + other.children)
|
||||
self.readed + other.readed,
|
||||
self.proged + other.proged,
|
||||
self.erased + other.erased,
|
||||
self.children + other.children)
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
@@ -143,27 +143,27 @@ def collect_syms_and_lines(obj_path, *,
|
||||
objdump_path=None,
|
||||
**args):
|
||||
symbol_pattern = re.compile(
|
||||
'^(?P<addr>[0-9a-fA-F]+)'
|
||||
'\s+.*'
|
||||
'\s+(?P<size>[0-9a-fA-F]+)'
|
||||
'\s+(?P<name>[^\s]+)\s*$')
|
||||
line_pattern = re.compile(
|
||||
'^\s+(?:'
|
||||
# matches dir/file table
|
||||
'(?P<no>[0-9]+)'
|
||||
'(?:\s+(?P<dir>[0-9]+))?'
|
||||
'^(?P<addr>[0-9a-fA-F]+)'
|
||||
'\s+.*'
|
||||
'\s+(?P<path>[^\s]+)'
|
||||
# matches line opcodes
|
||||
'|' '\[[^\]]*\]\s+'
|
||||
'(?:'
|
||||
'\s+(?P<size>[0-9a-fA-F]+)'
|
||||
'\s+(?P<name>[^\s]+)\s*$')
|
||||
line_pattern = re.compile(
|
||||
'^\s+(?:'
|
||||
# matches dir/file table
|
||||
'(?P<no>[0-9]+)'
|
||||
'(?:\s+(?P<dir>[0-9]+))?'
|
||||
'\s+.*'
|
||||
'\s+(?P<path>[^\s]+)'
|
||||
# matches line opcodes
|
||||
'|' '\[[^\]]*\]\s+' '(?:'
|
||||
'(?P<op_special>Special)'
|
||||
'|' '(?P<op_copy>Copy)'
|
||||
'|' '(?P<op_end>End of Sequence)'
|
||||
'|' 'File .*?to (?:entry )?(?P<op_file>\d+)'
|
||||
'|' 'Line .*?to (?P<op_line>[0-9]+)'
|
||||
'|' '(?:Address|PC) .*?to (?P<op_addr>[0x0-9a-fA-F]+)'
|
||||
'|' '.' ')*'
|
||||
'|' '.'
|
||||
')*'
|
||||
')$', re.IGNORECASE)
|
||||
|
||||
# figure out symbol addresses
|
||||
@@ -173,11 +173,11 @@ def collect_syms_and_lines(obj_path, *,
|
||||
if args.get('verbose'):
|
||||
print(' '.join(shlex.quote(c) for c in cmd))
|
||||
proc = sp.Popen(cmd,
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
for line in proc.stdout:
|
||||
m = symbol_pattern.match(line)
|
||||
if m:
|
||||
@@ -222,11 +222,11 @@ def collect_syms_and_lines(obj_path, *,
|
||||
if args.get('verbose'):
|
||||
print(' '.join(shlex.quote(c) for c in cmd))
|
||||
proc = sp.Popen(cmd,
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
for line in proc.stdout:
|
||||
m = line_pattern.match(line)
|
||||
if m:
|
||||
@@ -238,8 +238,8 @@ def collect_syms_and_lines(obj_path, *,
|
||||
dir = int(m.group('dir'))
|
||||
if dir in dirs:
|
||||
files[int(m.group('no'))] = os.path.join(
|
||||
dirs[dir],
|
||||
m.group('path'))
|
||||
dirs[dir],
|
||||
m.group('path'))
|
||||
else:
|
||||
files[int(m.group('no'))] = m.group('path')
|
||||
else:
|
||||
@@ -296,25 +296,27 @@ def collect_job(path, start, stop, syms, sym_at, lines, line_at, *,
|
||||
depth=1,
|
||||
**args):
|
||||
trace_pattern = re.compile(
|
||||
'^(?P<file>[^:]*):(?P<line>[0-9]+):trace:\s*(?P<prefix>[^\s]*?bd_)(?:'
|
||||
'(?P<read>read)\('
|
||||
'\s*(?P<read_ctx>\w+)' '\s*,'
|
||||
'\s*(?P<read_block>\w+)' '\s*,'
|
||||
'\s*(?P<read_off>\w+)' '\s*,'
|
||||
'\s*(?P<read_buffer>\w+)' '\s*,'
|
||||
'\s*(?P<read_size>\w+)' '\s*\)'
|
||||
'|' '(?P<prog>prog)\('
|
||||
'\s*(?P<prog_ctx>\w+)' '\s*,'
|
||||
'\s*(?P<prog_block>\w+)' '\s*,'
|
||||
'\s*(?P<prog_off>\w+)' '\s*,'
|
||||
'\s*(?P<prog_buffer>\w+)' '\s*,'
|
||||
'\s*(?P<prog_size>\w+)' '\s*\)'
|
||||
'|' '(?P<erase>erase)\('
|
||||
'\s*(?P<erase_ctx>\w+)' '\s*,'
|
||||
'\s*(?P<erase_block>\w+)'
|
||||
'\s*\(\s*(?P<erase_size>\w+)\s*\)' '\s*\)' ')\s*$')
|
||||
'^(?P<file>[^:]*):(?P<line>[0-9]+):trace:\s*'
|
||||
'(?P<prefix>[^\s]*?bd_)(?:'
|
||||
'(?P<read>read)\('
|
||||
'\s*(?P<read_ctx>\w+)' '\s*,'
|
||||
'\s*(?P<read_block>\w+)' '\s*,'
|
||||
'\s*(?P<read_off>\w+)' '\s*,'
|
||||
'\s*(?P<read_buffer>\w+)' '\s*,'
|
||||
'\s*(?P<read_size>\w+)' '\s*\)'
|
||||
'|' '(?P<prog>prog)\('
|
||||
'\s*(?P<prog_ctx>\w+)' '\s*,'
|
||||
'\s*(?P<prog_block>\w+)' '\s*,'
|
||||
'\s*(?P<prog_off>\w+)' '\s*,'
|
||||
'\s*(?P<prog_buffer>\w+)' '\s*,'
|
||||
'\s*(?P<prog_size>\w+)' '\s*\)'
|
||||
'|' '(?P<erase>erase)\('
|
||||
'\s*(?P<erase_ctx>\w+)' '\s*,'
|
||||
'\s*(?P<erase_block>\w+)'
|
||||
'\s*\(\s*(?P<erase_size>\w+)\s*\)' '\s*\)'
|
||||
')\s*$')
|
||||
frame_pattern = re.compile(
|
||||
'^\s+at (?P<addr>\w+)\s*$')
|
||||
'^\s+at (?P<addr>\w+)\s*$')
|
||||
|
||||
# parse all of the trace files for read/prog/erase operations
|
||||
last_filtered = False
|
||||
@@ -338,9 +340,7 @@ def collect_job(path, start, stop, syms, sym_at, lines, line_at, *,
|
||||
|
||||
# ignore filtered sources
|
||||
if sources is not None:
|
||||
if not any(
|
||||
os.path.abspath(file)
|
||||
== os.path.abspath(s)
|
||||
if not any(os.path.abspath(file) == os.path.abspath(s)
|
||||
for s in sources):
|
||||
return
|
||||
else:
|
||||
@@ -359,10 +359,10 @@ def collect_job(path, start, stop, syms, sym_at, lines, line_at, *,
|
||||
file = os.path.abspath(file)
|
||||
|
||||
results[(file, sym, line)] = (
|
||||
last_readed,
|
||||
last_proged,
|
||||
last_erased,
|
||||
{})
|
||||
last_readed,
|
||||
last_proged,
|
||||
last_erased,
|
||||
{})
|
||||
else:
|
||||
# tail-recursively propagate measurements
|
||||
for i in range(len(last_stack)):
|
||||
@@ -378,10 +378,10 @@ def collect_job(path, start, stop, syms, sym_at, lines, line_at, *,
|
||||
else:
|
||||
r, p, e, children = 0, 0, 0, {}
|
||||
results_[name] = (
|
||||
r+last_readed,
|
||||
p+last_proged,
|
||||
e+last_erased,
|
||||
children)
|
||||
r+last_readed,
|
||||
p+last_proged,
|
||||
e+last_erased,
|
||||
children)
|
||||
|
||||
# recurse
|
||||
results_ = results_[name][-1]
|
||||
@@ -444,7 +444,7 @@ def collect_job(path, start, stop, syms, sym_at, lines, line_at, *,
|
||||
# of reference
|
||||
if last_delta is None:
|
||||
i = bisect.bisect(lines, (last_file, last_line),
|
||||
key=lambda x: (x[0], x[1]))
|
||||
key=lambda x: (x[0], x[1]))
|
||||
if i > 0:
|
||||
last_delta = lines[i-1][2] - addr_
|
||||
else:
|
||||
@@ -474,9 +474,9 @@ def collect_job(path, start, stop, syms, sym_at, lines, line_at, *,
|
||||
# filter out internal/unknown functions
|
||||
if not everything and (
|
||||
sym.startswith('__')
|
||||
or sym.startswith('0')
|
||||
or sym.startswith('-')
|
||||
or sym == '_start'):
|
||||
or sym.startswith('0')
|
||||
or sym.startswith('-')
|
||||
or sym == '_start'):
|
||||
at_cache[addr] = None
|
||||
continue
|
||||
|
||||
@@ -492,9 +492,8 @@ def collect_job(path, start, stop, syms, sym_at, lines, line_at, *,
|
||||
# ignore filtered sources
|
||||
if sources is not None:
|
||||
if not any(
|
||||
os.path.abspath(file)
|
||||
== os.path.abspath(s)
|
||||
for s in sources):
|
||||
os.path.abspath(file) == os.path.abspath(s)
|
||||
for s in sources):
|
||||
at_cache[addr] = None
|
||||
continue
|
||||
else:
|
||||
@@ -529,8 +528,8 @@ def collect_job(path, start, stop, syms, sym_at, lines, line_at, *,
|
||||
results_ = []
|
||||
for name, (r, p, e, children) in results.items():
|
||||
results_.append(PerfBdResult(*name,
|
||||
r, p, e,
|
||||
children=to_results(children)))
|
||||
r, p, e,
|
||||
children=to_results(children)))
|
||||
return results_
|
||||
|
||||
return to_results(results)
|
||||
@@ -573,9 +572,10 @@ def collect(obj_path, trace_paths, *,
|
||||
with mp.Pool(jobs) as p:
|
||||
for results_ in p.imap_unordered(
|
||||
starapply,
|
||||
((collect_job, (path, start, stop,
|
||||
syms, sym_at, lines, line_at),
|
||||
args)
|
||||
((collect_job,
|
||||
(path, start, stop,
|
||||
syms, sym_at, lines, line_at),
|
||||
args)
|
||||
for path, ranges in zip(trace_paths, trace_ranges)
|
||||
for start, stop in ranges)):
|
||||
results.extend(results_)
|
||||
@@ -583,9 +583,10 @@ def collect(obj_path, trace_paths, *,
|
||||
else:
|
||||
results = []
|
||||
for path in trace_paths:
|
||||
results.extend(collect_job(path, None, None,
|
||||
syms, sym_at, lines, line_at,
|
||||
**args))
|
||||
results.extend(collect_job(
|
||||
path, None, None,
|
||||
syms, sym_at, lines, line_at,
|
||||
**args))
|
||||
|
||||
return results
|
||||
|
||||
@@ -597,7 +598,7 @@ def fold(Result, results, by=None, defines=[]):
|
||||
for k in it.chain(by or [], (k for k, _ in defines)):
|
||||
if k not in Result._by and k not in Result._fields:
|
||||
print("error: could not find field %r?" % k,
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
# filter by matching defines
|
||||
@@ -653,74 +654,78 @@ def table(Result, results, diff_results=None, *,
|
||||
return []
|
||||
|
||||
r = max(results_,
|
||||
key=lambda r: tuple(
|
||||
tuple(
|
||||
(getattr(r, k),)
|
||||
if getattr(r, k, None) is not None
|
||||
else ()
|
||||
for k in ([k] if k else [
|
||||
k for k in Result._sort if k in fields])
|
||||
if k in fields)
|
||||
for k in it.chain(hot, [None])))
|
||||
key=lambda r: tuple(
|
||||
tuple((getattr(r, k),)
|
||||
if getattr(r, k, None) is not None
|
||||
else ()
|
||||
for k in (
|
||||
[k] if k else [
|
||||
k for k in Result._sort
|
||||
if k in fields])
|
||||
if k in fields)
|
||||
for k in it.chain(hot, [None])))
|
||||
|
||||
# found a cycle?
|
||||
if tuple(getattr(r, k) for k in Result._by) in seen:
|
||||
return []
|
||||
|
||||
return [r._replace(children=[])] + rec_hot(
|
||||
r.children,
|
||||
seen | {tuple(getattr(r, k) for k in Result._by)})
|
||||
r.children,
|
||||
seen | {tuple(getattr(r, k) for k in Result._by)})
|
||||
|
||||
results = [r._replace(children=rec_hot(r.children)) for r in results]
|
||||
|
||||
# organize by name
|
||||
table = {
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in results}
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in results}
|
||||
diff_table = {
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in diff_results or []}
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in diff_results or []}
|
||||
names = [name
|
||||
for name in table.keys() | diff_table.keys()
|
||||
if diff_results is None
|
||||
or all_
|
||||
or any(
|
||||
types[k].ratio(
|
||||
getattr(table.get(name), k, None),
|
||||
getattr(diff_table.get(name), k, None))
|
||||
for k in fields)]
|
||||
for name in table.keys() | diff_table.keys()
|
||||
if diff_results is None
|
||||
or all_
|
||||
or any(
|
||||
types[k].ratio(
|
||||
getattr(table.get(name), k, None),
|
||||
getattr(diff_table.get(name), k, None))
|
||||
for k in fields)]
|
||||
|
||||
# sort again, now with diff info, note that python's sort is stable
|
||||
names.sort()
|
||||
if diff_results is not None:
|
||||
names.sort(key=lambda n: tuple(
|
||||
types[k].ratio(
|
||||
getattr(table.get(n), k, None),
|
||||
getattr(diff_table.get(n), k, None))
|
||||
for k in fields),
|
||||
reverse=True)
|
||||
names.sort(
|
||||
key=lambda n: tuple(
|
||||
types[k].ratio(
|
||||
getattr(table.get(n), k, None),
|
||||
getattr(diff_table.get(n), k, None))
|
||||
for k in fields),
|
||||
reverse=True)
|
||||
if sort:
|
||||
for k, reverse in reversed(sort):
|
||||
names.sort(
|
||||
key=lambda n: tuple(
|
||||
(getattr(table[n], k),)
|
||||
if getattr(table.get(n), k, None) is not None else ()
|
||||
for k in ([k] if k else [
|
||||
k for k in Result._sort if k in fields])),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
key=lambda n: tuple(
|
||||
(getattr(table[n], k),)
|
||||
if getattr(table.get(n), k, None) is not None
|
||||
else ()
|
||||
for k in (
|
||||
[k] if k else [
|
||||
k for k in Result._sort
|
||||
if k in fields])),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
|
||||
|
||||
# build up our lines
|
||||
lines = []
|
||||
|
||||
# header
|
||||
header = [
|
||||
'%s%s' % (
|
||||
','.join(by),
|
||||
' (%d added, %d removed)' % (
|
||||
sum(1 for n in table if n not in diff_table),
|
||||
sum(1 for n in diff_table if n not in table))
|
||||
if diff_results is not None and not percent else '')
|
||||
header = ['%s%s' % (
|
||||
','.join(by),
|
||||
' (%d added, %d removed)' % (
|
||||
sum(1 for n in table if n not in diff_table),
|
||||
sum(1 for n in diff_table if n not in table))
|
||||
if diff_results is not None and not percent else '')
|
||||
if not summary else '']
|
||||
if diff_results is None:
|
||||
for k in fields:
|
||||
@@ -743,43 +748,43 @@ def table(Result, results, diff_results=None, *,
|
||||
if diff_results is None:
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(getattr(r, k).table(),
|
||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
(getattr(r, k).table(),
|
||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
elif percent:
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none,
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none,
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
else:
|
||||
for k in fields:
|
||||
entry.append(getattr(diff_r, k).table()
|
||||
if getattr(diff_r, k, None) is not None
|
||||
else types[k].none)
|
||||
if getattr(diff_r, k, None) is not None
|
||||
else types[k].none)
|
||||
for k in fields:
|
||||
entry.append(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(types[k].diff(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)),
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)] if t
|
||||
else [])(
|
||||
types[k].ratio(
|
||||
(types[k].diff(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
getattr(diff_r, k, None)),
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)] if t
|
||||
else [])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
return entry
|
||||
|
||||
# recursive entry helper
|
||||
@@ -788,8 +793,8 @@ def table(Result, results, diff_results=None, *,
|
||||
# build the children table at each layer
|
||||
results_ = fold(Result, results_, by=by)
|
||||
table_ = {
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in results_}
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in results_}
|
||||
names_ = list(table_.keys())
|
||||
|
||||
# sort the children layer
|
||||
@@ -797,13 +802,16 @@ def table(Result, results, diff_results=None, *,
|
||||
if sort:
|
||||
for k, reverse in reversed(sort):
|
||||
names_.sort(
|
||||
key=lambda n: tuple(
|
||||
(getattr(table_[n], k),)
|
||||
if getattr(table_.get(n), k, None) is not None
|
||||
else ()
|
||||
for k in ([k] if k else [
|
||||
k for k in Result._sort if k in fields])),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
key=lambda n: tuple(
|
||||
(getattr(table_[n], k),)
|
||||
if getattr(table_.get(n), k, None)
|
||||
is not None
|
||||
else ()
|
||||
for k in (
|
||||
[k] if k else [
|
||||
k for k in Result._sort
|
||||
if k in fields])),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
|
||||
for i, name in enumerate(names_):
|
||||
r = table_[name]
|
||||
@@ -824,14 +832,13 @@ def table(Result, results, diff_results=None, *,
|
||||
|
||||
# recurse?
|
||||
if depth_ > 1:
|
||||
recurse(
|
||||
r.children,
|
||||
depth_-1,
|
||||
seen | {name},
|
||||
(prefixes[2+is_last] + "|-> ",
|
||||
prefixes[2+is_last] + "'-> ",
|
||||
prefixes[2+is_last] + "| ",
|
||||
prefixes[2+is_last] + " "))
|
||||
recurse(r.children,
|
||||
depth_-1,
|
||||
seen | {name},
|
||||
(prefixes[2+is_last] + "|-> ",
|
||||
prefixes[2+is_last] + "'-> ",
|
||||
prefixes[2+is_last] + "| ",
|
||||
prefixes[2+is_last] + " "))
|
||||
|
||||
# entries
|
||||
if not summary:
|
||||
@@ -845,14 +852,13 @@ def table(Result, results, diff_results=None, *,
|
||||
|
||||
# recursive entries
|
||||
if name in table and depth > 1:
|
||||
recurse(
|
||||
table[name].children,
|
||||
depth-1,
|
||||
{name},
|
||||
("|-> ",
|
||||
"'-> ",
|
||||
"| ",
|
||||
" "))
|
||||
recurse(table[name].children,
|
||||
depth-1,
|
||||
{name},
|
||||
("|-> ",
|
||||
"'-> ",
|
||||
"| ",
|
||||
" "))
|
||||
|
||||
# total
|
||||
r = next(iter(fold(Result, results, by=[])), None)
|
||||
@@ -864,8 +870,8 @@ def table(Result, results, diff_results=None, *,
|
||||
|
||||
# homogenize
|
||||
lines = [
|
||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||
for line in lines]
|
||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||
for line in lines]
|
||||
|
||||
# find the best widths, note that column 0 contains the names and is
|
||||
# handled a bit differently
|
||||
@@ -879,11 +885,11 @@ def table(Result, results, diff_results=None, *,
|
||||
# print our table
|
||||
for line in lines:
|
||||
print('%-*s %s' % (
|
||||
widths[0], line[0][0],
|
||||
' '.join('%*s%-*s' % (
|
||||
widths[i], x[0],
|
||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||
for i, x in enumerate(line[1:], 1))))
|
||||
widths[0], line[0][0],
|
||||
' '.join('%*s%-*s' % (
|
||||
widths[i], x[0],
|
||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||
for i, x in enumerate(line[1:], 1))))
|
||||
|
||||
|
||||
def annotate(Result, results, *,
|
||||
@@ -944,14 +950,14 @@ def annotate(Result, results, *,
|
||||
or float(r.erased) / max_erased >= erase_t0):
|
||||
if last is not None and line - last.stop <= args['context']:
|
||||
last = range(
|
||||
last.start,
|
||||
line+1+args['context'])
|
||||
last.start,
|
||||
line+1+args['context'])
|
||||
else:
|
||||
if last is not None:
|
||||
spans.append((last, func))
|
||||
last = range(
|
||||
line-args['context'],
|
||||
line+1+args['context'])
|
||||
line-args['context'],
|
||||
line+1+args['context'])
|
||||
func = r.function
|
||||
if last is not None:
|
||||
spans.append((last, func))
|
||||
@@ -967,11 +973,11 @@ def annotate(Result, results, *,
|
||||
if skipped:
|
||||
skipped = False
|
||||
print('%s@@ %s:%d: %s @@%s' % (
|
||||
'\x1b[36m' if args['color'] else '',
|
||||
path,
|
||||
i+1,
|
||||
next(iter(f for _, f in spans)),
|
||||
'\x1b[m' if args['color'] else ''))
|
||||
'\x1b[36m' if args['color'] else '',
|
||||
path,
|
||||
i+1,
|
||||
next(iter(f for _, f in spans)),
|
||||
'\x1b[m' if args['color'] else ''))
|
||||
|
||||
# build line
|
||||
if line.endswith('\n'):
|
||||
@@ -980,11 +986,11 @@ def annotate(Result, results, *,
|
||||
if i+1 in table:
|
||||
r = table[i+1]
|
||||
line = '%-*s // %s readed, %s proged, %s erased' % (
|
||||
args['width'],
|
||||
line,
|
||||
r.readed,
|
||||
r.proged,
|
||||
r.erased)
|
||||
args['width'],
|
||||
line,
|
||||
r.readed,
|
||||
r.proged,
|
||||
r.erased)
|
||||
|
||||
if args['color']:
|
||||
if (float(r.readed) / max_readed >= read_t1
|
||||
@@ -1036,10 +1042,10 @@ def report(obj_path='', trace_paths=[], *,
|
||||
continue
|
||||
try:
|
||||
results.append(PerfBdResult(
|
||||
**{k: r[k] for k in PerfBdResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k] for k in PerfBdResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
**{k: r[k] for k in PerfBdResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k] for k in PerfBdResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
@@ -1051,25 +1057,27 @@ def report(obj_path='', trace_paths=[], *,
|
||||
if sort:
|
||||
for k, reverse in reversed(sort):
|
||||
results.sort(
|
||||
key=lambda r: tuple(
|
||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||
for k in ([k] if k else PerfBdResult._sort)),
|
||||
reverse=reverse ^ (not k or k in PerfBdResult._fields))
|
||||
key=lambda r: tuple(
|
||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||
for k in ([k] if k else PerfBdResult._sort)),
|
||||
reverse=reverse ^ (not k or k in PerfBdResult._fields))
|
||||
|
||||
# write results to CSV
|
||||
if args.get('output'):
|
||||
with openio(args['output'], 'w') as f:
|
||||
writer = csv.DictWriter(f,
|
||||
(by if by is not None else PerfBdResult._by)
|
||||
+ [k for k in (
|
||||
fields if fields is not None else PerfBdResult._fields)])
|
||||
(by if by is not None else PerfBdResult._by)
|
||||
+ [k for k in (
|
||||
fields if fields is not None
|
||||
else PerfBdResult._fields)])
|
||||
writer.writeheader()
|
||||
for r in results:
|
||||
writer.writerow(
|
||||
{k: getattr(r, k) for k in (
|
||||
by if by is not None else PerfBdResult._by)}
|
||||
| {k: getattr(r, k) for k in (
|
||||
fields if fields is not None else PerfBdResult._fields)})
|
||||
{k: getattr(r, k) for k in (
|
||||
by if by is not None else PerfBdResult._by)}
|
||||
| {k: getattr(r, k) for k in (
|
||||
fields if fields is not None
|
||||
else PerfBdResult._fields)})
|
||||
|
||||
# find previous results?
|
||||
if args.get('diff'):
|
||||
@@ -1087,10 +1095,10 @@ def report(obj_path='', trace_paths=[], *,
|
||||
continue
|
||||
try:
|
||||
diff_results.append(PerfBdResult(
|
||||
**{k: r[k] for k in PerfBdResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k] for k in PerfBdResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
**{k: r[k] for k in PerfBdResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k] for k in PerfBdResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
except TypeError:
|
||||
pass
|
||||
except FileNotFoundError:
|
||||
@@ -1111,11 +1119,11 @@ def report(obj_path='', trace_paths=[], *,
|
||||
else:
|
||||
# print table
|
||||
table(PerfBdResult, results,
|
||||
diff_results if args.get('diff') else None,
|
||||
by=by if by is not None else ['function'],
|
||||
fields=fields,
|
||||
sort=sort,
|
||||
**args)
|
||||
diff_results if args.get('diff') else None,
|
||||
by=by if by is not None else ['function'],
|
||||
fields=fields,
|
||||
sort=sort,
|
||||
**args)
|
||||
|
||||
|
||||
def main(**args):
|
||||
@@ -1129,168 +1137,173 @@ if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Aggregate and report call-stack propagated "
|
||||
"block-device operations from trace output.",
|
||||
allow_abbrev=False)
|
||||
description="Aggregate and report call-stack propagated "
|
||||
"block-device operations from trace output.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'obj_path',
|
||||
nargs='?',
|
||||
help="Input executable for mapping addresses to symbols.")
|
||||
'obj_path',
|
||||
nargs='?',
|
||||
help="Input executable for mapping addresses to symbols.")
|
||||
parser.add_argument(
|
||||
'trace_paths',
|
||||
nargs='*',
|
||||
help="Input *.trace files.")
|
||||
'trace_paths',
|
||||
nargs='*',
|
||||
help="Input *.trace files.")
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help="Output commands that run behind the scenes.")
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help="Output commands that run behind the scenes.")
|
||||
parser.add_argument(
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help="Don't show anything, useful with -o.")
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help="Don't show anything, useful with -o.")
|
||||
parser.add_argument(
|
||||
'-o', '--output',
|
||||
help="Specify CSV file to store results.")
|
||||
'-o', '--output',
|
||||
help="Specify CSV file to store results.")
|
||||
parser.add_argument(
|
||||
'-u', '--use',
|
||||
help="Don't parse anything, use this CSV file.")
|
||||
'-u', '--use',
|
||||
help="Don't parse anything, use this CSV file.")
|
||||
parser.add_argument(
|
||||
'-d', '--diff',
|
||||
help="Specify CSV file to diff against.")
|
||||
'-d', '--diff',
|
||||
help="Specify CSV file to diff against.")
|
||||
parser.add_argument(
|
||||
'-a', '--all',
|
||||
action='store_true',
|
||||
help="Show all, not just the ones that changed.")
|
||||
'-a', '--all',
|
||||
action='store_true',
|
||||
help="Show all, not just the ones that changed.")
|
||||
parser.add_argument(
|
||||
'-p', '--percent',
|
||||
action='store_true',
|
||||
help="Only show percentage change, not a full diff.")
|
||||
'-p', '--percent',
|
||||
action='store_true',
|
||||
help="Only show percentage change, not a full diff.")
|
||||
parser.add_argument(
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
choices=PerfBdResult._by,
|
||||
help="Group by this field.")
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
choices=PerfBdResult._by,
|
||||
help="Group by this field.")
|
||||
parser.add_argument(
|
||||
'-f', '--field',
|
||||
dest='fields',
|
||||
action='append',
|
||||
choices=PerfBdResult._fields,
|
||||
help="Show this field.")
|
||||
'-f', '--field',
|
||||
dest='fields',
|
||||
action='append',
|
||||
choices=PerfBdResult._fields,
|
||||
help="Show this field.")
|
||||
parser.add_argument(
|
||||
'-D', '--define',
|
||||
dest='defines',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
help="Only include results where this field is this value.")
|
||||
'-D', '--define',
|
||||
dest='defines',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
help="Only include results where this field is this value.")
|
||||
class AppendSort(argparse.Action):
|
||||
def __call__(self, parser, namespace, value, option):
|
||||
if namespace.sort is None:
|
||||
namespace.sort = []
|
||||
namespace.sort.append((value, True if option == '-S' else False))
|
||||
parser.add_argument(
|
||||
'-s', '--sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field.")
|
||||
'-s', '--sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field.")
|
||||
parser.add_argument(
|
||||
'-S', '--reverse-sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field, but backwards.")
|
||||
'-S', '--reverse-sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field, but backwards.")
|
||||
parser.add_argument(
|
||||
'-Y', '--summary',
|
||||
action='store_true',
|
||||
help="Only show the total.")
|
||||
'-Y', '--summary',
|
||||
action='store_true',
|
||||
help="Only show the total.")
|
||||
parser.add_argument(
|
||||
'-F', '--source',
|
||||
dest='sources',
|
||||
action='append',
|
||||
help="Only consider definitions in this file. Defaults to anything "
|
||||
"in the current directory.")
|
||||
'-F', '--source',
|
||||
dest='sources',
|
||||
action='append',
|
||||
help="Only consider definitions in this file. Defaults to "
|
||||
"anything in the current directory.")
|
||||
parser.add_argument(
|
||||
'--everything',
|
||||
action='store_true',
|
||||
help="Include builtin and libc specific symbols.")
|
||||
'--everything',
|
||||
action='store_true',
|
||||
help="Include builtin and libc specific symbols.")
|
||||
parser.add_argument(
|
||||
'-g', '--propagate',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Depth to propagate samples up the call-stack. 0 propagates up "
|
||||
"to the entry point, 1 does no propagation. Defaults to 0.")
|
||||
'-g', '--propagate',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Depth to propagate samples up the call-stack. 0 propagates "
|
||||
"up to the entry point, 1 does no propagation. Defaults to 0.")
|
||||
parser.add_argument(
|
||||
'-z', '--depth',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Depth of function calls to show. 0 shows all calls unless we "
|
||||
"find a cycle. Defaults to 0.")
|
||||
'-z', '--depth',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Depth of function calls to show. 0 shows all calls unless "
|
||||
"we find a cycle. Defaults to 0.")
|
||||
parser.add_argument(
|
||||
'-t', '--hot',
|
||||
nargs='?',
|
||||
action='append',
|
||||
help="Show only the hot path for each function call.")
|
||||
'-t', '--hot',
|
||||
nargs='?',
|
||||
action='append',
|
||||
help="Show only the hot path for each function call.")
|
||||
parser.add_argument(
|
||||
'-A', '--annotate',
|
||||
action='store_true',
|
||||
help="Show source files annotated with coverage info.")
|
||||
'-A', '--annotate',
|
||||
action='store_true',
|
||||
help="Show source files annotated with coverage info.")
|
||||
parser.add_argument(
|
||||
'-T', '--threshold',
|
||||
nargs='?',
|
||||
type=lambda x: tuple(float(x) for x in x.split(',')),
|
||||
const=THRESHOLD,
|
||||
help="Show lines with any ops above this threshold as a percent of "
|
||||
"all lines. Defaults to %s." % ','.join(str(t) for t in THRESHOLD))
|
||||
'-T', '--threshold',
|
||||
nargs='?',
|
||||
type=lambda x: tuple(float(x) for x in x.split(',')),
|
||||
const=THRESHOLD,
|
||||
help="Show lines with any ops above this threshold as a percent "
|
||||
"of all lines. Defaults to "
|
||||
"%s." % ','.join(str(t) for t in THRESHOLD))
|
||||
parser.add_argument(
|
||||
'--read-threshold',
|
||||
nargs='?',
|
||||
type=lambda x: tuple(float(x) for x in x.split(',')),
|
||||
const=THRESHOLD,
|
||||
help="Show lines with reads above this threshold as a percent of "
|
||||
"all lines. Defaults to %s." % ','.join(str(t) for t in THRESHOLD))
|
||||
'--read-threshold',
|
||||
nargs='?',
|
||||
type=lambda x: tuple(float(x) for x in x.split(',')),
|
||||
const=THRESHOLD,
|
||||
help="Show lines with reads above this threshold as a percent "
|
||||
"of all lines. Defaults to "
|
||||
"%s." % ','.join(str(t) for t in THRESHOLD))
|
||||
parser.add_argument(
|
||||
'--prog-threshold',
|
||||
nargs='?',
|
||||
type=lambda x: tuple(float(x) for x in x.split(',')),
|
||||
const=THRESHOLD,
|
||||
help="Show lines with progs above this threshold as a percent of "
|
||||
"all lines. Defaults to %s." % ','.join(str(t) for t in THRESHOLD))
|
||||
'--prog-threshold',
|
||||
nargs='?',
|
||||
type=lambda x: tuple(float(x) for x in x.split(',')),
|
||||
const=THRESHOLD,
|
||||
help="Show lines with progs above this threshold as a percent "
|
||||
"of all lines. Defaults to "
|
||||
"%s." % ','.join(str(t) for t in THRESHOLD))
|
||||
parser.add_argument(
|
||||
'--erase-threshold',
|
||||
nargs='?',
|
||||
type=lambda x: tuple(float(x) for x in x.split(',')),
|
||||
const=THRESHOLD,
|
||||
help="Show lines with erases above this threshold as a percent of "
|
||||
"all lines. Defaults to %s." % ','.join(str(t) for t in THRESHOLD))
|
||||
'--erase-threshold',
|
||||
nargs='?',
|
||||
type=lambda x: tuple(float(x) for x in x.split(',')),
|
||||
const=THRESHOLD,
|
||||
help="Show lines with erases above this threshold as a percent "
|
||||
"of all lines. Defaults to "
|
||||
"%s." % ','.join(str(t) for t in THRESHOLD))
|
||||
parser.add_argument(
|
||||
'-C', '--context',
|
||||
type=lambda x: int(x, 0),
|
||||
default=3,
|
||||
help="Show n additional lines of context. Defaults to 3.")
|
||||
'-C', '--context',
|
||||
type=lambda x: int(x, 0),
|
||||
default=3,
|
||||
help="Show n additional lines of context. Defaults to 3.")
|
||||
parser.add_argument(
|
||||
'-W', '--width',
|
||||
type=lambda x: int(x, 0),
|
||||
default=80,
|
||||
help="Assume source is styled with this many columns. Defaults to 80.")
|
||||
'-W', '--width',
|
||||
type=lambda x: int(x, 0),
|
||||
default=80,
|
||||
help="Assume source is styled with this many columns. Defaults "
|
||||
"to 80.")
|
||||
parser.add_argument(
|
||||
'--color',
|
||||
choices=['never', 'always', 'auto'],
|
||||
default='auto',
|
||||
help="When to use terminal colors. Defaults to 'auto'.")
|
||||
'--color',
|
||||
choices=['never', 'always', 'auto'],
|
||||
default='auto',
|
||||
help="When to use terminal colors. Defaults to 'auto'.")
|
||||
parser.add_argument(
|
||||
'-j', '--jobs',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Number of processes to use. 0 spawns one process per core.")
|
||||
'-j', '--jobs',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Number of processes to use. 0 spawns one process per core.")
|
||||
parser.add_argument(
|
||||
'--objdump-path',
|
||||
type=lambda x: x.split(),
|
||||
default=OBJDUMP_PATH,
|
||||
help="Path to the objdump executable, may include flags. "
|
||||
"Defaults to %r." % OBJDUMP_PATH)
|
||||
'--objdump-path',
|
||||
type=lambda x: x.split(),
|
||||
default=OBJDUMP_PATH,
|
||||
help="Path to the objdump executable, may include flags. "
|
||||
"Defaults to %r." % OBJDUMP_PATH)
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+395
-383
File diff suppressed because it is too large
Load Diff
+324
-317
@@ -25,6 +25,7 @@ import time
|
||||
import matplotlib as mpl
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
# some nicer colors borrowed from Seaborn
|
||||
# note these include a non-opaque alpha
|
||||
COLORS = [
|
||||
@@ -148,8 +149,8 @@ class AutoMultipleLocator(mpl.ticker.MultipleLocator):
|
||||
nbins = np.clip(self.axis.get_tick_space(), 1, 9)
|
||||
|
||||
# find the best power, use this as our locator's actual base
|
||||
scale = self.base
|
||||
** (mt.ceil(mt.log((vmax-vmin) / (nbins+1), self.base)))
|
||||
scale = (self.base
|
||||
** (mt.ceil(mt.log((vmax-vmin) / (nbins+1), self.base))))
|
||||
self.set_params(scale)
|
||||
|
||||
return super().__call__()
|
||||
@@ -199,8 +200,8 @@ def collect(csv_paths, renames=[], defines=[]):
|
||||
with openio(path) as f:
|
||||
reader = csv.DictReader(f, restval='')
|
||||
fields.extend(
|
||||
k for k in reader.fieldnames
|
||||
if k not in fields)
|
||||
k for k in reader.fieldnames
|
||||
if k not in fields)
|
||||
for r in reader:
|
||||
# apply any renames
|
||||
if renames:
|
||||
@@ -249,7 +250,7 @@ def fold(results, by=None, x=None, y=None, defines=[], labels=None):
|
||||
# filter by 'by'
|
||||
if by and not all(
|
||||
k in r and r[k] == v
|
||||
for k, v in zip(by, key)):
|
||||
for k, v in zip(by, key)):
|
||||
continue
|
||||
|
||||
# find xs
|
||||
@@ -353,9 +354,9 @@ class Grid:
|
||||
self_i = 0
|
||||
other_i = 0
|
||||
self_xweight = (self_xweights[self_i]
|
||||
if self_i < len(self_xweights) else mt.inf)
|
||||
if self_i < len(self_xweights) else mt.inf)
|
||||
other_xweight = (other_xweights[other_i]
|
||||
if other_i < len(other_xweights) else mt.inf)
|
||||
if other_i < len(other_xweights) else mt.inf)
|
||||
while self_i < len(self_xweights) and other_i < len(other_xweights):
|
||||
if other_xweight - self_xweight > 0.0000001:
|
||||
new_xweights.append(self_xweight)
|
||||
@@ -374,7 +375,7 @@ class Grid:
|
||||
|
||||
self_i += 1
|
||||
self_xweight = (self_xweights[self_i]
|
||||
if self_i < len(self_xweights) else mt.inf)
|
||||
if self_i < len(self_xweights) else mt.inf)
|
||||
elif self_xweight - other_xweight > 0.0000001:
|
||||
new_xweights.append(other_xweight)
|
||||
self_xweight -= other_xweight
|
||||
@@ -392,7 +393,7 @@ class Grid:
|
||||
|
||||
other_i += 1
|
||||
other_xweight = (other_xweights[other_i]
|
||||
if other_i < len(other_xweights) else mt.inf)
|
||||
if other_i < len(other_xweights) else mt.inf)
|
||||
else:
|
||||
new_xweights.append(self_xweight)
|
||||
|
||||
@@ -404,10 +405,10 @@ class Grid:
|
||||
|
||||
self_i += 1
|
||||
self_xweight = (self_xweights[self_i]
|
||||
if self_i < len(self_xweights) else mt.inf)
|
||||
if self_i < len(self_xweights) else mt.inf)
|
||||
other_i += 1
|
||||
other_xweight = (other_xweights[other_i]
|
||||
if other_i < len(other_xweights) else mt.inf)
|
||||
if other_i < len(other_xweights) else mt.inf)
|
||||
|
||||
# squish so ratios are preserved
|
||||
self_h = sum(self.yweights)
|
||||
@@ -423,8 +424,9 @@ class Grid:
|
||||
|
||||
self.xweights = new_xweights
|
||||
self.yweights = self_yweights + other.yweights
|
||||
self.map = self_map | {(x, y+len(self_yweights)): s
|
||||
for (x, y), s in other_map.items()}
|
||||
self.map = self_map | {
|
||||
(x, y+len(self_yweights)): s
|
||||
for (x, y), s in other_map.items()}
|
||||
else:
|
||||
for s in self.subplots:
|
||||
s.y += len(other.yweights)
|
||||
@@ -432,8 +434,9 @@ class Grid:
|
||||
|
||||
self.xweights = new_xweights
|
||||
self.yweights = other.yweights + self_yweights
|
||||
self.map = other_map | {(x, y+len(other.yweights)): s
|
||||
for (x, y), s in self_map.items()}
|
||||
self.map = other_map | {
|
||||
(x, y+len(other.yweights)): s
|
||||
for (x, y), s in self_map.items()}
|
||||
|
||||
if dir in ['right', 'left']:
|
||||
# first scale the two grids so they line up
|
||||
@@ -451,9 +454,9 @@ class Grid:
|
||||
self_i = 0
|
||||
other_i = 0
|
||||
self_yweight = (self_yweights[self_i]
|
||||
if self_i < len(self_yweights) else mt.inf)
|
||||
if self_i < len(self_yweights) else mt.inf)
|
||||
other_yweight = (other_yweights[other_i]
|
||||
if other_i < len(other_yweights) else mt.inf)
|
||||
if other_i < len(other_yweights) else mt.inf)
|
||||
while self_i < len(self_yweights) and other_i < len(other_yweights):
|
||||
if other_yweight - self_yweight > 0.0000001:
|
||||
new_yweights.append(self_yweight)
|
||||
@@ -472,7 +475,7 @@ class Grid:
|
||||
|
||||
self_i += 1
|
||||
self_yweight = (self_yweights[self_i]
|
||||
if self_i < len(self_yweights) else mt.inf)
|
||||
if self_i < len(self_yweights) else mt.inf)
|
||||
elif self_yweight - other_yweight > 0.0000001:
|
||||
new_yweights.append(other_yweight)
|
||||
self_yweight -= other_yweight
|
||||
@@ -490,7 +493,7 @@ class Grid:
|
||||
|
||||
other_i += 1
|
||||
other_yweight = (other_yweights[other_i]
|
||||
if other_i < len(other_yweights) else mt.inf)
|
||||
if other_i < len(other_yweights) else mt.inf)
|
||||
else:
|
||||
new_yweights.append(self_yweight)
|
||||
|
||||
@@ -502,10 +505,10 @@ class Grid:
|
||||
|
||||
self_i += 1
|
||||
self_yweight = (self_yweights[self_i]
|
||||
if self_i < len(self_yweights) else mt.inf)
|
||||
if self_i < len(self_yweights) else mt.inf)
|
||||
other_i += 1
|
||||
other_yweight = (other_yweights[other_i]
|
||||
if other_i < len(other_yweights) else mt.inf)
|
||||
if other_i < len(other_yweights) else mt.inf)
|
||||
|
||||
# squish so ratios are preserved
|
||||
self_w = sum(self.xweights)
|
||||
@@ -521,8 +524,9 @@ class Grid:
|
||||
|
||||
self.xweights = self_xweights + other.xweights
|
||||
self.yweights = new_yweights
|
||||
self.map = self_map | {(x+len(self_xweights), y): s
|
||||
for (x, y), s in other_map.items()}
|
||||
self.map = self_map | {
|
||||
(x+len(self_xweights), y): s
|
||||
for (x, y), s in other_map.items()}
|
||||
else:
|
||||
for s in self.subplots:
|
||||
s.x += len(other.xweights)
|
||||
@@ -530,8 +534,9 @@ class Grid:
|
||||
|
||||
self.xweights = other.xweights + self_xweights
|
||||
self.yweights = new_yweights
|
||||
self.map = other_map | {(x+len(other.xweights), y): s
|
||||
for (x, y), s in self_map.items()}
|
||||
self.map = other_map | {
|
||||
(x+len(other.xweights), y): s
|
||||
for (x, y), s in self_map.items()}
|
||||
|
||||
|
||||
def scale(self, width, height):
|
||||
@@ -546,11 +551,11 @@ class Grid:
|
||||
|
||||
for dir, subargs in subplots:
|
||||
subgrid = cls.fromargs(
|
||||
width=subargs.pop('width',
|
||||
0.5 if dir in ['right', 'left'] else width),
|
||||
height=subargs.pop('height',
|
||||
0.5 if dir in ['above', 'below'] else height),
|
||||
**subargs)
|
||||
width=subargs.pop('width',
|
||||
0.5 if dir in ['right', 'left'] else width),
|
||||
height=subargs.pop('height',
|
||||
0.5 if dir in ['above', 'below'] else height),
|
||||
**subargs)
|
||||
grid.merge(subgrid, dir)
|
||||
|
||||
grid.scale(width, height)
|
||||
@@ -668,8 +673,8 @@ def main(csv_paths, output, *,
|
||||
# fix ggplot when dark
|
||||
if ggplot:
|
||||
plt.rc('axes',
|
||||
facecolor=foreground_,
|
||||
edgecolor=background_)
|
||||
facecolor=foreground_,
|
||||
edgecolor=background_)
|
||||
plt.rc('grid', color=background_)
|
||||
|
||||
if font is not None:
|
||||
@@ -677,22 +682,22 @@ def main(csv_paths, output, *,
|
||||
plt.rc('font', size=font_size)
|
||||
plt.rc('text', color=font_color_)
|
||||
plt.rc('figure',
|
||||
titlesize='medium',
|
||||
labelsize='small')
|
||||
titlesize='medium',
|
||||
labelsize='small')
|
||||
plt.rc('axes',
|
||||
titlesize='small',
|
||||
labelsize='small',
|
||||
labelcolor=font_color_)
|
||||
titlesize='small',
|
||||
labelsize='small',
|
||||
labelcolor=font_color_)
|
||||
if not ggplot:
|
||||
plt.rc('axes', edgecolor=font_color_)
|
||||
plt.rc('xtick', labelsize='small', color=font_color_)
|
||||
plt.rc('ytick', labelsize='small', color=font_color_)
|
||||
plt.rc('legend',
|
||||
fontsize='small',
|
||||
fancybox=False,
|
||||
framealpha=None,
|
||||
edgecolor=foreground_,
|
||||
borderaxespad=0)
|
||||
fontsize='small',
|
||||
fancybox=False,
|
||||
framealpha=None,
|
||||
edgecolor=foreground_,
|
||||
borderaxespad=0)
|
||||
plt.rc('axes.spines', top=False, right=False)
|
||||
|
||||
plt.rc('figure', facecolor=background_, edgecolor=background_)
|
||||
@@ -723,19 +728,19 @@ def main(csv_paths, output, *,
|
||||
all_defines[k] |= vs
|
||||
all_defines = sorted(all_defines.items())
|
||||
all_labels = ((label or [])
|
||||
+ subplots_get('label', **subplot, subplots=subplots))
|
||||
+ subplots_get('label', **subplot, subplots=subplots))
|
||||
|
||||
# separate out renames
|
||||
all_renames = list(it.chain.from_iterable(
|
||||
((k, v) for v in vs)
|
||||
for k, vs in it.chain(all_by, all_x, all_y)))
|
||||
((k, v) for v in vs)
|
||||
for k, vs in it.chain(all_by, all_x, all_y)))
|
||||
all_by = [k for k, _ in all_by]
|
||||
all_x = [k for k, _ in all_x]
|
||||
all_y = [k for k, _ in all_y]
|
||||
|
||||
if not all_by and not all_y:
|
||||
print("error: needs --by or -y to figure out fields",
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
# first collect results from CSV files
|
||||
@@ -743,8 +748,7 @@ def main(csv_paths, output, *,
|
||||
|
||||
# if y not specified, guess it's anything not in by/defines/x/renames
|
||||
if not all_y:
|
||||
all_y = [
|
||||
k for k in fields_
|
||||
all_y = [k for k in fields_
|
||||
if k not in all_by
|
||||
and not any(k == k_ for k_, _ in all_defines)
|
||||
and not any(k == old_k for _, old_k in all_renames)]
|
||||
@@ -757,42 +761,43 @@ def main(csv_paths, output, *,
|
||||
# figure out formats/colors here so that subplot defines don't change
|
||||
# them later, that'd be bad
|
||||
dataformats_ = {
|
||||
name: formats_[i % len(formats_)]
|
||||
for i, name in enumerate(datasets_.keys())}
|
||||
name: formats_[i % len(formats_)]
|
||||
for i, name in enumerate(datasets_.keys())}
|
||||
datacolors_ = {
|
||||
name: colors_[i % len(colors_)]
|
||||
for i, name in enumerate(datasets_.keys())}
|
||||
name: colors_[i % len(colors_)]
|
||||
for i, name in enumerate(datasets_.keys())}
|
||||
|
||||
# create a grid of subplots
|
||||
grid = Grid.fromargs(**subplot, subplots=subplots)
|
||||
|
||||
# create a matplotlib plot
|
||||
fig = plt.figure(figsize=(
|
||||
width/plt.rcParams['figure.dpi'],
|
||||
height/plt.rcParams['figure.dpi']),
|
||||
layout='constrained',
|
||||
# we need a linewidth to keep xkcd mode happy
|
||||
linewidth=8 if xkcd else 0)
|
||||
fig = plt.figure(
|
||||
figsize=(
|
||||
width/plt.rcParams['figure.dpi'],
|
||||
height/plt.rcParams['figure.dpi']),
|
||||
layout='constrained',
|
||||
# we need a linewidth to keep xkcd mode happy
|
||||
linewidth=8 if xkcd else 0)
|
||||
|
||||
gs = fig.add_gridspec(
|
||||
grid.height
|
||||
+ (1 if legend_above else 0)
|
||||
+ (1 if legend_below else 0),
|
||||
grid.width
|
||||
+ (1 if legend_right else 0),
|
||||
height_ratios=([0.001] if legend_above else [])
|
||||
+ [max(s, 0.01) for s in reversed(grid.yweights)]
|
||||
+ ([0.001] if legend_below else []),
|
||||
width_ratios=[max(s, 0.01) for s in grid.xweights]
|
||||
+ ([0.001] if legend_right else []))
|
||||
grid.height
|
||||
+ (1 if legend_above else 0)
|
||||
+ (1 if legend_below else 0),
|
||||
grid.width
|
||||
+ (1 if legend_right else 0),
|
||||
height_ratios=([0.001] if legend_above else [])
|
||||
+ [max(s, 0.01) for s in reversed(grid.yweights)]
|
||||
+ ([0.001] if legend_below else []),
|
||||
width_ratios=[max(s, 0.01) for s in grid.xweights]
|
||||
+ ([0.001] if legend_right else []))
|
||||
|
||||
# first create axes so that plots can interact with each other
|
||||
for s in grid:
|
||||
s.ax = fig.add_subplot(gs[
|
||||
grid.height-(s.y+s.yspan) + (1 if legend_above else 0)
|
||||
: grid.height-s.y + (1 if legend_above else 0),
|
||||
s.x
|
||||
: s.x+s.xspan])
|
||||
grid.height-(s.y+s.yspan) + (1 if legend_above else 0)
|
||||
: grid.height-s.y + (1 if legend_above else 0),
|
||||
s.x
|
||||
: s.x+s.xspan])
|
||||
|
||||
# now plot each subplot
|
||||
for s in grid:
|
||||
@@ -830,18 +835,18 @@ def main(csv_paths, output, *,
|
||||
|
||||
# filter by subplot x/y
|
||||
subdatasets = co.OrderedDict([(name, dataset)
|
||||
for name, dataset in subdatasets.items()
|
||||
if len(all_x) <= 1 or name[-(1 if len(all_y) <= 1 else 2)] in x_
|
||||
if len(all_y) <= 1 or name[-1] in y_])
|
||||
for name, dataset in subdatasets.items()
|
||||
if len(all_x) <= 1 or name[-(1 if len(all_y) <= 1 else 2)] in x_
|
||||
if len(all_y) <= 1 or name[-1] in y_])
|
||||
|
||||
# plot!
|
||||
ax = s.ax
|
||||
for name, dataset in subdatasets.items():
|
||||
dats = sorted((x,y) for x,y in dataset)
|
||||
ax.plot([x for x,_ in dats], [y for _,y in dats],
|
||||
dataformats_[name],
|
||||
color=datacolors_[name],
|
||||
label=','.join(name))
|
||||
dataformats_[name],
|
||||
color=datacolors_[name],
|
||||
label=','.join(name))
|
||||
|
||||
# axes scaling
|
||||
if xlog_:
|
||||
@@ -852,31 +857,31 @@ def main(csv_paths, output, *,
|
||||
ax.yaxis.set_minor_locator(mpl.ticker.NullLocator())
|
||||
# axes limits
|
||||
ax.set_xlim(
|
||||
xlim_[0] if xlim_[0] is not None
|
||||
else min(it.chain([0], (x
|
||||
for dataset in subdatasets.values()
|
||||
for x, y in dataset
|
||||
if y is not None))),
|
||||
xlim_[1] if xlim_[1] is not None
|
||||
else max(it.chain([0], (x
|
||||
for r in subdatasets.values()
|
||||
for x, y in dataset
|
||||
if y is not None))))
|
||||
xlim_[0] if xlim_[0] is not None
|
||||
else min(it.chain([0], (x
|
||||
for dataset in subdatasets.values()
|
||||
for x, y in dataset
|
||||
if y is not None))),
|
||||
xlim_[1] if xlim_[1] is not None
|
||||
else max(it.chain([0], (x
|
||||
for r in subdatasets.values()
|
||||
for x, y in dataset
|
||||
if y is not None))))
|
||||
ax.set_ylim(
|
||||
ylim_[0] if ylim_[0] is not None
|
||||
else min(it.chain([0], (y
|
||||
for dataset in subdatasets.values()
|
||||
for _, y in dataset
|
||||
if y is not None))),
|
||||
ylim_[1] if ylim_[1] is not None
|
||||
else max(it.chain([0], (y
|
||||
for dataset in subdatasets.values()
|
||||
for _, y in dataset
|
||||
if y is not None))))
|
||||
ylim_[0] if ylim_[0] is not None
|
||||
else min(it.chain([0], (y
|
||||
for dataset in subdatasets.values()
|
||||
for _, y in dataset
|
||||
if y is not None))),
|
||||
ylim_[1] if ylim_[1] is not None
|
||||
else max(it.chain([0], (y
|
||||
for dataset in subdatasets.values()
|
||||
for _, y in dataset
|
||||
if y is not None))))
|
||||
# axes ticks
|
||||
if x2_:
|
||||
ax.xaxis.set_major_formatter(lambda x, pos:
|
||||
si2(x)+(xunits_ if xunits_ else ''))
|
||||
si2(x)+(xunits_ if xunits_ else ''))
|
||||
if xticklabels_ is not None:
|
||||
ax.xaxis.set_ticklabels(xticklabels_)
|
||||
if xticks_ is None:
|
||||
@@ -889,7 +894,7 @@ def main(csv_paths, output, *,
|
||||
ax.xaxis.set_major_locator(mpl.ticker.NullLocator())
|
||||
else:
|
||||
ax.xaxis.set_major_formatter(lambda x, pos:
|
||||
si(x)+(xunits_ if xunits_ else ''))
|
||||
si(x)+(xunits_ if xunits_ else ''))
|
||||
if xticklabels_ is not None:
|
||||
ax.xaxis.set_ticklabels(xticklabels_)
|
||||
if xticks_ is None:
|
||||
@@ -902,7 +907,7 @@ def main(csv_paths, output, *,
|
||||
ax.xaxis.set_major_locator(mpl.ticker.NullLocator())
|
||||
if y2_:
|
||||
ax.yaxis.set_major_formatter(lambda x, pos:
|
||||
si2(x)+(yunits_ if yunits_ else ''))
|
||||
si2(x)+(yunits_ if yunits_ else ''))
|
||||
if yticklabels_ is not None:
|
||||
ax.yaxis.set_ticklabels(yticklabels_)
|
||||
if yticks_ is None:
|
||||
@@ -915,7 +920,7 @@ def main(csv_paths, output, *,
|
||||
ax.yaxis.set_major_locator(mpl.ticker.NullLocator())
|
||||
else:
|
||||
ax.yaxis.set_major_formatter(lambda x, pos:
|
||||
si(x)+(yunits_ if yunits_ else ''))
|
||||
si(x)+(yunits_ if yunits_ else ''))
|
||||
if yticklabels_ is not None:
|
||||
ax.yaxis.set_ticklabels(yticklabels_)
|
||||
if yticks_ is None:
|
||||
@@ -966,11 +971,11 @@ def main(csv_paths, output, *,
|
||||
ax = fig.add_subplot(gs[(1 if legend_above else 0):,-1])
|
||||
ax.set_axis_off()
|
||||
ax.legend(
|
||||
[h for _,h in legend],
|
||||
[l for l,_ in legend],
|
||||
loc='upper left',
|
||||
fancybox=False,
|
||||
borderaxespad=0)
|
||||
[h for _,h in legend],
|
||||
[l for l,_ in legend],
|
||||
loc='upper left',
|
||||
fancybox=False,
|
||||
borderaxespad=0)
|
||||
|
||||
if legend_above:
|
||||
ax = fig.add_subplot(gs[0, :grid.width])
|
||||
@@ -988,12 +993,12 @@ def main(csv_paths, output, *,
|
||||
legend_ = [l for l in legend_ if l is not None]
|
||||
|
||||
legend_ = ax.legend(
|
||||
[h for _,h in legend_],
|
||||
[l for l,_ in legend_],
|
||||
loc='upper center',
|
||||
ncol=ncol,
|
||||
fancybox=False,
|
||||
borderaxespad=0)
|
||||
[h for _,h in legend_],
|
||||
[l for l,_ in legend_],
|
||||
loc='upper center',
|
||||
ncol=ncol,
|
||||
fancybox=False,
|
||||
borderaxespad=0)
|
||||
|
||||
if (legend_.get_window_extent().width
|
||||
<= ax.get_window_extent().width):
|
||||
@@ -1007,8 +1012,8 @@ def main(csv_paths, output, *,
|
||||
# works really well actually
|
||||
if xlabel:
|
||||
ax.set_title(escape(xlabel),
|
||||
size=plt.rcParams['axes.labelsize'],
|
||||
weight=plt.rcParams['axes.labelweight'])
|
||||
size=plt.rcParams['axes.labelsize'],
|
||||
weight=plt.rcParams['axes.labelweight'])
|
||||
|
||||
# try different column counts until we fit in the axes
|
||||
for ncol in reversed(range(1, len(legend)+1)):
|
||||
@@ -1022,12 +1027,12 @@ def main(csv_paths, output, *,
|
||||
legend_ = [l for l in legend_ if l is not None]
|
||||
|
||||
legend_ = ax.legend(
|
||||
[h for _,h in legend_],
|
||||
[l for l,_ in legend_],
|
||||
loc='upper center',
|
||||
ncol=ncol,
|
||||
fancybox=False,
|
||||
borderaxespad=0)
|
||||
[h for _,h in legend_],
|
||||
[l for l,_ in legend_],
|
||||
loc='upper center',
|
||||
ncol=ncol,
|
||||
fancybox=False,
|
||||
borderaxespad=0)
|
||||
|
||||
if (legend_.get_window_extent().width
|
||||
<= ax.get_window_extent().width):
|
||||
@@ -1062,9 +1067,9 @@ def main(csv_paths, output, *,
|
||||
# some stats
|
||||
if not quiet:
|
||||
print('updated %s, %s datasets, %s points' % (
|
||||
output,
|
||||
len(datasets_),
|
||||
sum(len(dataset) for dataset in datasets_.values())))
|
||||
output,
|
||||
len(datasets_),
|
||||
sum(len(dataset) for dataset in datasets_.values())))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -1072,265 +1077,267 @@ if __name__ == "__main__":
|
||||
import argparse
|
||||
import re
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Plot CSV files with matplotlib.",
|
||||
allow_abbrev=False)
|
||||
description="Plot CSV files with matplotlib.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'csv_paths',
|
||||
nargs='*',
|
||||
help="Input *.csv files.")
|
||||
'csv_paths',
|
||||
nargs='*',
|
||||
help="Input *.csv files.")
|
||||
output_rule = parser.add_argument(
|
||||
'-o', '--output',
|
||||
required=True,
|
||||
help="Output *.svg/*.png file.")
|
||||
'-o', '--output',
|
||||
required=True,
|
||||
help="Output *.svg/*.png file.")
|
||||
parser.add_argument(
|
||||
'--svg',
|
||||
action='store_true',
|
||||
help="Output an svg file. By default this is infered.")
|
||||
'--svg',
|
||||
action='store_true',
|
||||
help="Output an svg file. By default this is infered.")
|
||||
parser.add_argument(
|
||||
'--png',
|
||||
action='store_true',
|
||||
help="Output a png file. By default this is infered.")
|
||||
'--png',
|
||||
action='store_true',
|
||||
help="Output a png file. By default this is infered.")
|
||||
parser.add_argument(
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help="Don't print info.")
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help="Don't print info.")
|
||||
parser.add_argument(
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Group by this field. Can rename fields with new_name=old_name.")
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Group by this field. Can rename fields with "
|
||||
"new_name=old_name.")
|
||||
parser.add_argument(
|
||||
'-x',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Field to use for the x-axis. Can rename fields with "
|
||||
"new_name=old_name.")
|
||||
'-x',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Field to use for the x-axis. Can rename fields with "
|
||||
"new_name=old_name.")
|
||||
parser.add_argument(
|
||||
'-y',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Field to use for the y-axis. Can rename fields with "
|
||||
"new_name=old_name.")
|
||||
'-y',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Field to use for the y-axis. Can rename fields with "
|
||||
"new_name=old_name.")
|
||||
parser.add_argument(
|
||||
'-D', '--define',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
action='append',
|
||||
help="Only include results where this field is this value. May include "
|
||||
"comma-separated options.")
|
||||
'-D', '--define',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
action='append',
|
||||
help="Only include results where this field is this value. May "
|
||||
"include comma-separated options.")
|
||||
parser.add_argument(
|
||||
'-L', '--label',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
re.sub(r'\\([=\\])', r'\1', k.strip()),
|
||||
tuple(v.strip() for v in vs.split(',')))
|
||||
)(*re.split(r'(?<!\\)=', x, 1)),
|
||||
help="Use this label for a given group, where a group is roughly the "
|
||||
"comma-separated values in the -b/--by, -x, and -y fields. Also "
|
||||
"provides an ordering. Accepts escaped equals.")
|
||||
'-L', '--label',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
re.sub(r'\\([=\\])', r'\1', k.strip()),
|
||||
tuple(v.strip() for v in vs.split(',')))
|
||||
)(*re.split(r'(?<!\\)=', x, 1)),
|
||||
help="Use this label for a given group, where a group is roughly "
|
||||
"the comma-separated values in the -b/--by, -x, and -y "
|
||||
"fields. Also provides an ordering. Accepts escaped equals.")
|
||||
parser.add_argument(
|
||||
'-.', '--points',
|
||||
action='store_true',
|
||||
help="Only draw data points.")
|
||||
'-.', '--points',
|
||||
action='store_true',
|
||||
help="Only draw data points.")
|
||||
parser.add_argument(
|
||||
'-!', '--points-and-lines',
|
||||
action='store_true',
|
||||
help="Draw data points and lines.")
|
||||
'-!', '--points-and-lines',
|
||||
action='store_true',
|
||||
help="Draw data points and lines.")
|
||||
parser.add_argument(
|
||||
'--colors',
|
||||
type=lambda x: [x.strip() for x in x.split(',')],
|
||||
help="Comma-separated hex colors to use.")
|
||||
'--colors',
|
||||
type=lambda x: [x.strip() for x in x.split(',')],
|
||||
help="Comma-separated hex colors to use.")
|
||||
parser.add_argument(
|
||||
'--formats',
|
||||
type=lambda x: [re.sub(r'\\([,\\])', r'\1', x.strip())
|
||||
for x in re.split(r'(?<!\\),', x)],
|
||||
help="Comma-separated matplotlib formats to use. Accepts escaped "
|
||||
"commas.")
|
||||
'--formats',
|
||||
type=lambda x: [re.sub(r'\\([,\\])', r'\1', x.strip())
|
||||
for x in re.split(r'(?<!\\),', x)],
|
||||
help="Comma-separated matplotlib formats to use. Accepts escaped "
|
||||
"commas.")
|
||||
parser.add_argument(
|
||||
'-W', '--width',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Width in pixels. Defaults to %r." % WIDTH)
|
||||
'-W', '--width',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Width in pixels. Defaults to %r." % WIDTH)
|
||||
parser.add_argument(
|
||||
'-H', '--height',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Height in pixels. Defaults to %r." % HEIGHT)
|
||||
'-H', '--height',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Height in pixels. Defaults to %r." % HEIGHT)
|
||||
parser.add_argument(
|
||||
'-X', '--xlim',
|
||||
type=lambda x: tuple(
|
||||
dat(x) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Range for the x-axis.")
|
||||
'-X', '--xlim',
|
||||
type=lambda x: tuple(
|
||||
dat(x) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Range for the x-axis.")
|
||||
parser.add_argument(
|
||||
'-Y', '--ylim',
|
||||
type=lambda x: tuple(
|
||||
dat(x) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Range for the y-axis.")
|
||||
'-Y', '--ylim',
|
||||
type=lambda x: tuple(
|
||||
dat(x) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Range for the y-axis.")
|
||||
parser.add_argument(
|
||||
'--xlog',
|
||||
action='store_true',
|
||||
help="Use a logarithmic x-axis.")
|
||||
'--xlog',
|
||||
action='store_true',
|
||||
help="Use a logarithmic x-axis.")
|
||||
parser.add_argument(
|
||||
'--ylog',
|
||||
action='store_true',
|
||||
help="Use a logarithmic y-axis.")
|
||||
'--ylog',
|
||||
action='store_true',
|
||||
help="Use a logarithmic y-axis.")
|
||||
parser.add_argument(
|
||||
'--x2',
|
||||
action='store_true',
|
||||
help="Use base-2 prefixes for the x-axis.")
|
||||
'--x2',
|
||||
action='store_true',
|
||||
help="Use base-2 prefixes for the x-axis.")
|
||||
parser.add_argument(
|
||||
'--y2',
|
||||
action='store_true',
|
||||
help="Use base-2 prefixes for the y-axis.")
|
||||
'--y2',
|
||||
action='store_true',
|
||||
help="Use base-2 prefixes for the y-axis.")
|
||||
parser.add_argument(
|
||||
'--xticks',
|
||||
type=lambda x: int(x, 0) if ',' not in x
|
||||
else [dat(x) for x in x.split(',')],
|
||||
help="Ticks for the x-axis. This can be explicit comma-separated "
|
||||
"ticks, the number of ticks, or 0 to disable.")
|
||||
'--xticks',
|
||||
type=lambda x: int(x, 0) if ',' not in x
|
||||
else [dat(x) for x in x.split(',')],
|
||||
help="Ticks for the x-axis. This can be explicit comma-separated "
|
||||
"ticks, the number of ticks, or 0 to disable.")
|
||||
parser.add_argument(
|
||||
'--yticks',
|
||||
type=lambda x: int(x, 0) if ',' not in x
|
||||
else [dat(x) for x in x.split(',')],
|
||||
help="Ticks for the y-axis. This can be explicit comma-separated "
|
||||
"ticks, the number of ticks, or 0 to disable.")
|
||||
'--yticks',
|
||||
type=lambda x: int(x, 0) if ',' not in x
|
||||
else [dat(x) for x in x.split(',')],
|
||||
help="Ticks for the y-axis. This can be explicit comma-separated "
|
||||
"ticks, the number of ticks, or 0 to disable.")
|
||||
parser.add_argument(
|
||||
'--xunits',
|
||||
help="Units for the x-axis.")
|
||||
'--xunits',
|
||||
help="Units for the x-axis.")
|
||||
parser.add_argument(
|
||||
'--yunits',
|
||||
help="Units for the y-axis.")
|
||||
'--yunits',
|
||||
help="Units for the y-axis.")
|
||||
parser.add_argument(
|
||||
'--xlabel',
|
||||
help="Add a label to the x-axis.")
|
||||
'--xlabel',
|
||||
help="Add a label to the x-axis.")
|
||||
parser.add_argument(
|
||||
'--ylabel',
|
||||
help="Add a label to the y-axis.")
|
||||
'--ylabel',
|
||||
help="Add a label to the y-axis.")
|
||||
parser.add_argument(
|
||||
'--xticklabels',
|
||||
type=lambda x: [re.sub(r'\\([,\\])', r'\1', x.strip())
|
||||
for x in re.split(r'(?<!\\),', x)]
|
||||
if x.strip() else [],
|
||||
help="Comma separated xticklabels. Accepts escaped commas.")
|
||||
'--xticklabels',
|
||||
type=lambda x: [re.sub(r'\\([,\\])', r'\1', x.strip())
|
||||
for x in re.split(r'(?<!\\),', x)]
|
||||
if x.strip() else [],
|
||||
help="Comma separated xticklabels. Accepts escaped commas.")
|
||||
parser.add_argument(
|
||||
'--yticklabels',
|
||||
type=lambda x: [re.sub(r'\\([,\\])', r'\1', x.strip())
|
||||
for x in re.split(r'(?<!\\),', x)]
|
||||
if x.strip() else [],
|
||||
help="Comma separated yticklabels. Accepts escaped commas.")
|
||||
'--yticklabels',
|
||||
type=lambda x: [re.sub(r'\\([,\\])', r'\1', x.strip())
|
||||
for x in re.split(r'(?<!\\),', x)]
|
||||
if x.strip() else [],
|
||||
help="Comma separated yticklabels. Accepts escaped commas.")
|
||||
parser.add_argument(
|
||||
'-t', '--title',
|
||||
help="Add a title.")
|
||||
'-t', '--title',
|
||||
help="Add a title.")
|
||||
parser.add_argument(
|
||||
'-l', '--legend', '--legend-right',
|
||||
dest='legend_right',
|
||||
action='store_true',
|
||||
help="Place a legend to the right.")
|
||||
'-l', '--legend', '--legend-right',
|
||||
dest='legend_right',
|
||||
action='store_true',
|
||||
help="Place a legend to the right.")
|
||||
parser.add_argument(
|
||||
'--legend-above',
|
||||
action='store_true',
|
||||
help="Place a legend above.")
|
||||
'--legend-above',
|
||||
action='store_true',
|
||||
help="Place a legend above.")
|
||||
parser.add_argument(
|
||||
'--legend-below',
|
||||
action='store_true',
|
||||
help="Place a legend below.")
|
||||
'--legend-below',
|
||||
action='store_true',
|
||||
help="Place a legend below.")
|
||||
parser.add_argument(
|
||||
'--dark',
|
||||
action='store_true',
|
||||
help="Use the dark style.")
|
||||
'--dark',
|
||||
action='store_true',
|
||||
help="Use the dark style.")
|
||||
parser.add_argument(
|
||||
'--ggplot',
|
||||
action='store_true',
|
||||
help="Use the ggplot style.")
|
||||
'--ggplot',
|
||||
action='store_true',
|
||||
help="Use the ggplot style.")
|
||||
parser.add_argument(
|
||||
'--xkcd',
|
||||
action='store_true',
|
||||
help="Use the xkcd style.")
|
||||
'--xkcd',
|
||||
action='store_true',
|
||||
help="Use the xkcd style.")
|
||||
parser.add_argument(
|
||||
'--font',
|
||||
type=lambda x: [x.strip() for x in x.split(',')],
|
||||
help="Font family for matplotlib.")
|
||||
'--font',
|
||||
type=lambda x: [x.strip() for x in x.split(',')],
|
||||
help="Font family for matplotlib.")
|
||||
parser.add_argument(
|
||||
'--font-size',
|
||||
help="Font size for matplotlib. Defaults to %r." % FONT_SIZE)
|
||||
'--font-size',
|
||||
help="Font size for matplotlib. Defaults to %r." % FONT_SIZE)
|
||||
parser.add_argument(
|
||||
'--font-color',
|
||||
help="Color for the font and other line elements.")
|
||||
'--font-color',
|
||||
help="Color for the font and other line elements.")
|
||||
parser.add_argument(
|
||||
'--foreground',
|
||||
help="Foreground color to use.")
|
||||
'--foreground',
|
||||
help="Foreground color to use.")
|
||||
parser.add_argument(
|
||||
'--background',
|
||||
help="Background color to use.")
|
||||
'--background',
|
||||
help="Background color to use.")
|
||||
class AppendSubplot(argparse.Action):
|
||||
@staticmethod
|
||||
def parse(value):
|
||||
import copy
|
||||
subparser = copy.deepcopy(parser)
|
||||
next(a for a in subparser._actions
|
||||
if '--output' in a.option_strings).required = False
|
||||
if '--output' in a.option_strings).required = False
|
||||
next(a for a in subparser._actions
|
||||
if '--width' in a.option_strings).type = float
|
||||
if '--width' in a.option_strings).type = float
|
||||
next(a for a in subparser._actions
|
||||
if '--height' in a.option_strings).type = float
|
||||
if '--height' in a.option_strings).type = float
|
||||
return subparser.parse_intermixed_args(shlex.split(value or ""))
|
||||
def __call__(self, parser, namespace, value, option):
|
||||
if not hasattr(namespace, 'subplots'):
|
||||
namespace.subplots = []
|
||||
namespace.subplots.append((
|
||||
option.split('-')[-1],
|
||||
self.__class__.parse(value)))
|
||||
option.split('-')[-1],
|
||||
self.__class__.parse(value)))
|
||||
parser.add_argument(
|
||||
'--subplot-above',
|
||||
action=AppendSubplot,
|
||||
help="Add subplot above with the same dataset. Takes an arg string to "
|
||||
"control the subplot which supports most (but not all) of the "
|
||||
"parameters listed here. The relative dimensions of the subplot "
|
||||
"can be controlled with -W/-H which now take a percentage.")
|
||||
'--subplot-above',
|
||||
action=AppendSubplot,
|
||||
help="Add subplot above with the same dataset. Takes an arg "
|
||||
"string to control the subplot which supports most (but "
|
||||
"not all) of the parameters listed here. The relative "
|
||||
"dimensions of the subplot can be controlled with -W/-H "
|
||||
"which now take a percentage.")
|
||||
parser.add_argument(
|
||||
'--subplot-below',
|
||||
action=AppendSubplot,
|
||||
help="Add subplot below with the same dataset.")
|
||||
'--subplot-below',
|
||||
action=AppendSubplot,
|
||||
help="Add subplot below with the same dataset.")
|
||||
parser.add_argument(
|
||||
'--subplot-left',
|
||||
action=AppendSubplot,
|
||||
help="Add subplot left with the same dataset.")
|
||||
'--subplot-left',
|
||||
action=AppendSubplot,
|
||||
help="Add subplot left with the same dataset.")
|
||||
parser.add_argument(
|
||||
'--subplot-right',
|
||||
action=AppendSubplot,
|
||||
help="Add subplot right with the same dataset.")
|
||||
'--subplot-right',
|
||||
action=AppendSubplot,
|
||||
help="Add subplot right with the same dataset.")
|
||||
parser.add_argument(
|
||||
'--subplot',
|
||||
type=AppendSubplot.parse,
|
||||
help="Add subplot-specific arguments to the main plot.")
|
||||
'--subplot',
|
||||
type=AppendSubplot.parse,
|
||||
help="Add subplot-specific arguments to the main plot.")
|
||||
|
||||
def dictify(ns):
|
||||
if hasattr(ns, 'subplots'):
|
||||
ns.subplots = [(dir, dictify(subplot_ns))
|
||||
for dir, subplot_ns in ns.subplots]
|
||||
for dir, subplot_ns in ns.subplots]
|
||||
if ns.subplot is not None:
|
||||
ns.subplot = dictify(ns.subplot)
|
||||
return {k: v
|
||||
for k, v in vars(ns).items()
|
||||
if v is not None}
|
||||
for k, v in vars(ns).items()
|
||||
if v is not None}
|
||||
|
||||
sys.exit(main(**dictify(parser.parse_intermixed_args())))
|
||||
|
||||
+58
-61
@@ -13,6 +13,7 @@
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
LIMIT = 16
|
||||
|
||||
CMP = {
|
||||
@@ -133,15 +134,14 @@ def write_header(f, limit=LIMIT):
|
||||
|
||||
# write assert macros
|
||||
for op, cmp in sorted(CMP.items()):
|
||||
f.writeln("#define __PRETTY_ASSERT_BOOL_%s(lh, rh) do { \\"
|
||||
% cmp.upper())
|
||||
f.writeln("#define __PRETTY_ASSERT_BOOL_%s(lh, rh) do { \\" % (
|
||||
cmp.upper()))
|
||||
f.writeln(" bool _lh = !!(lh); \\")
|
||||
f.writeln(" bool _rh = !!(rh); \\")
|
||||
f.writeln(" if (!(_lh %s _rh)) { \\" % op)
|
||||
f.writeln(" __pretty_assert_print( \\")
|
||||
f.writeln(" __FILE__, __LINE__, \\")
|
||||
f.writeln(" __pretty_assert_bool, \"%s\", \\"
|
||||
% cmp)
|
||||
f.writeln(" __pretty_assert_bool, \"%s\", \\" % cmp)
|
||||
f.writeln(" &_lh, 0, \\")
|
||||
f.writeln(" &_rh, 0); \\")
|
||||
f.writeln(" __builtin_trap(); \\")
|
||||
@@ -149,15 +149,14 @@ def write_header(f, limit=LIMIT):
|
||||
f.writeln("} while (0)")
|
||||
f.writeln()
|
||||
for op, cmp in sorted(CMP.items()):
|
||||
f.writeln("#define __PRETTY_ASSERT_INT_%s(lh, rh) do { \\"
|
||||
% cmp.upper())
|
||||
f.writeln("#define __PRETTY_ASSERT_INT_%s(lh, rh) do { \\" % (
|
||||
cmp.upper()))
|
||||
f.writeln(" __typeof__(rh) _lh = lh; \\")
|
||||
f.writeln(" __typeof__(rh) _rh = rh; \\")
|
||||
f.writeln(" if (!(_lh %s _rh)) { \\" % op)
|
||||
f.writeln(" __pretty_assert_print( \\")
|
||||
f.writeln(" __FILE__, __LINE__, \\")
|
||||
f.writeln(" __pretty_assert_int, \"%s\", \\"
|
||||
% cmp)
|
||||
f.writeln(" __pretty_assert_int, \"%s\", \\" % cmp)
|
||||
f.writeln(" &(intmax_t){(intmax_t)_lh}, 0, \\")
|
||||
f.writeln(" &(intmax_t){(intmax_t)_rh}, 0); \\")
|
||||
f.writeln(" __builtin_trap(); \\")
|
||||
@@ -165,15 +164,14 @@ def write_header(f, limit=LIMIT):
|
||||
f.writeln("} while (0)")
|
||||
f.writeln()
|
||||
for op, cmp in sorted(CMP.items()):
|
||||
f.writeln("#define __PRETTY_ASSERT_MEM_%s(lh, rh, size) do { \\"
|
||||
% cmp.upper())
|
||||
f.writeln("#define __PRETTY_ASSERT_MEM_%s(lh, rh, size) do { \\" % (
|
||||
cmp.upper()))
|
||||
f.writeln(" const void *_lh = lh; \\")
|
||||
f.writeln(" const void *_rh = rh; \\")
|
||||
f.writeln(" if (!(memcmp(_lh, _rh, size) %s 0)) { \\" % op)
|
||||
f.writeln(" __pretty_assert_print( \\")
|
||||
f.writeln(" __FILE__, __LINE__, \\")
|
||||
f.writeln(" __pretty_assert_mem, \"%s\", \\"
|
||||
% cmp)
|
||||
f.writeln(" __pretty_assert_mem, \"%s\", \\" % cmp)
|
||||
f.writeln(" _lh, size, \\")
|
||||
f.writeln(" _rh, size); \\")
|
||||
f.writeln(" __builtin_trap(); \\")
|
||||
@@ -181,15 +179,14 @@ def write_header(f, limit=LIMIT):
|
||||
f.writeln("} while (0)")
|
||||
f.writeln()
|
||||
for op, cmp in sorted(CMP.items()):
|
||||
f.writeln("#define __PRETTY_ASSERT_STR_%s(lh, rh) do { \\"
|
||||
% cmp.upper())
|
||||
f.writeln("#define __PRETTY_ASSERT_STR_%s(lh, rh) do { \\" % (
|
||||
cmp.upper()))
|
||||
f.writeln(" const char *_lh = lh; \\")
|
||||
f.writeln(" const char *_rh = rh; \\")
|
||||
f.writeln(" if (!(strcmp(_lh, _rh) %s 0)) { \\" % op)
|
||||
f.writeln(" __pretty_assert_print( \\")
|
||||
f.writeln(" __FILE__, __LINE__, \\")
|
||||
f.writeln(" __pretty_assert_str, \"%s\", \\"
|
||||
% cmp)
|
||||
f.writeln(" __pretty_assert_str, \"%s\", \\" % cmp)
|
||||
f.writeln(" _lh, strlen(_lh), \\")
|
||||
f.writeln(" _rh, strlen(_rh)); \\")
|
||||
f.writeln(" __builtin_trap(); \\")
|
||||
@@ -206,11 +203,11 @@ def write_header(f, limit=LIMIT):
|
||||
|
||||
def mkassert(type, cmp, lh, rh, size=None):
|
||||
if size is not None:
|
||||
return ("__PRETTY_ASSERT_%s_%s(%s, %s, %s)"
|
||||
% (type.upper(), cmp.upper(), lh, rh, size))
|
||||
return ("__PRETTY_ASSERT_%s_%s(%s, %s, %s)" % (
|
||||
type.upper(), cmp.upper(), lh, rh, size))
|
||||
else:
|
||||
return ("__PRETTY_ASSERT_%s_%s(%s, %s)"
|
||||
% (type.upper(), cmp.upper(), lh, rh))
|
||||
return ("__PRETTY_ASSERT_%s_%s(%s, %s)" % (
|
||||
type.upper(), cmp.upper(), lh, rh))
|
||||
|
||||
def mkunreachable():
|
||||
return "__PRETTY_ASSERT_UNREACHABLE()"
|
||||
@@ -224,12 +221,12 @@ class ParseFailure(Exception):
|
||||
|
||||
def __str__(self):
|
||||
return "expected %r, found %s..." % (
|
||||
self.expected, repr(self.found)[:70])
|
||||
self.expected, repr(self.found)[:70])
|
||||
|
||||
class Parser:
|
||||
def __init__(self, in_f, lexemes=LEXEMES):
|
||||
p = '|'.join('(?P<%s>%s)' % (n, '|'.join(l))
|
||||
for n, l in lexemes.items())
|
||||
for n, l in lexemes.items())
|
||||
p = re.compile(p, re.DOTALL)
|
||||
data = in_f.read()
|
||||
tokens = []
|
||||
@@ -496,54 +493,54 @@ if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Preprocessor that makes asserts easier to debug.",
|
||||
allow_abbrev=False)
|
||||
description="Preprocessor that makes asserts easier to debug.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'input',
|
||||
help="Input C file.")
|
||||
'input',
|
||||
help="Input C file.")
|
||||
parser.add_argument(
|
||||
'-o', '--output',
|
||||
required=True,
|
||||
help="Output C file.")
|
||||
'-o', '--output',
|
||||
required=True,
|
||||
help="Output C file.")
|
||||
parser.add_argument(
|
||||
'-p', '--prefix',
|
||||
action='append',
|
||||
help="Additional prefixes for symbols.")
|
||||
'-p', '--prefix',
|
||||
action='append',
|
||||
help="Additional prefixes for symbols.")
|
||||
parser.add_argument(
|
||||
'-P', '--prefix-insensitive',
|
||||
action='append',
|
||||
help="Additional prefixes for lower/upper case symbol variants.")
|
||||
'-P', '--prefix-insensitive',
|
||||
action='append',
|
||||
help="Additional prefixes for lower/upper case symbol variants.")
|
||||
parser.add_argument(
|
||||
'--assert',
|
||||
dest='assert_',
|
||||
action='append',
|
||||
help="Additional symbols for assert statements.")
|
||||
'--assert',
|
||||
dest='assert_',
|
||||
action='append',
|
||||
help="Additional symbols for assert statements.")
|
||||
parser.add_argument(
|
||||
'--unreachable',
|
||||
action='append',
|
||||
help="Additional symbols for unreachable statements.")
|
||||
'--unreachable',
|
||||
action='append',
|
||||
help="Additional symbols for unreachable statements.")
|
||||
parser.add_argument(
|
||||
'--memcmp',
|
||||
action='append',
|
||||
help="Additional symbols for memcmp expressions.")
|
||||
'--memcmp',
|
||||
action='append',
|
||||
help="Additional symbols for memcmp expressions.")
|
||||
parser.add_argument(
|
||||
'--strcmp',
|
||||
action='append',
|
||||
help="Additional symbols for strcmp expressions.")
|
||||
'--strcmp',
|
||||
action='append',
|
||||
help="Additional symbols for strcmp expressions.")
|
||||
parser.add_argument(
|
||||
'-n', '--no-defaults',
|
||||
action='store_true',
|
||||
help="Disable default symbols.")
|
||||
'-n', '--no-defaults',
|
||||
action='store_true',
|
||||
help="Disable default symbols.")
|
||||
parser.add_argument(
|
||||
'--no-arrows',
|
||||
action='store_true',
|
||||
help="Disable arrow (=>) expressions.")
|
||||
'--no-arrows',
|
||||
action='store_true',
|
||||
help="Disable arrow (=>) expressions.")
|
||||
parser.add_argument(
|
||||
'-l', '--limit',
|
||||
type=lambda x: int(x, 0),
|
||||
default=LIMIT,
|
||||
help="Maximum number of characters to display in strcmp and memcmp. "
|
||||
"Defaults to %r." % LIMIT)
|
||||
'-l', '--limit',
|
||||
type=lambda x: int(x, 0),
|
||||
default=LIMIT,
|
||||
help="Maximum number of characters to display in strcmp and "
|
||||
"memcmp. Defaults to %r." % LIMIT)
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
@@ -2,25 +2,27 @@
|
||||
|
||||
import subprocess as sp
|
||||
|
||||
|
||||
def main(args):
|
||||
with open(args.disk, 'rb') as f:
|
||||
f.seek(args.block * args.block_size)
|
||||
block = (f.read(args.block_size)
|
||||
.ljust(args.block_size, b'\xff'))
|
||||
.ljust(args.block_size, b'\xff'))
|
||||
|
||||
# what did you expect?
|
||||
print("%-8s %-s" % ('off', 'data'))
|
||||
return sp.run(['xxd', '-g1', '-'], input=block).returncode
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Hex dump a specific block in a disk.")
|
||||
description="Hex dump a specific block in a disk.")
|
||||
parser.add_argument('disk',
|
||||
help="File representing the block device.")
|
||||
help="File representing the block device.")
|
||||
parser.add_argument('block_size', type=lambda x: int(x, 0),
|
||||
help="Size of a block in bytes.")
|
||||
help="Size of a block in bytes.")
|
||||
parser.add_argument('block', type=lambda x: int(x, 0),
|
||||
help="Address of block to dump.")
|
||||
help="Address of block to dump.")
|
||||
sys.exit(main(parser.parse_args()))
|
||||
|
||||
+44
-41
@@ -5,6 +5,7 @@ import binascii
|
||||
import sys
|
||||
import itertools as it
|
||||
|
||||
|
||||
TAG_TYPES = {
|
||||
'splice': (0x700, 0x400),
|
||||
'create': (0x7ff, 0x401),
|
||||
@@ -104,8 +105,9 @@ class Tag:
|
||||
try:
|
||||
if ' ' in type:
|
||||
type1, type3 = type.split()
|
||||
return (self.is_(type1) and
|
||||
(self.type & ~TAG_TYPES[type1][0]) == int(type3, 0))
|
||||
return (self.is_(type1)
|
||||
and (self.type & ~TAG_TYPES[type1][0])
|
||||
== int(type3, 0))
|
||||
|
||||
return self.type == int(type, 0)
|
||||
|
||||
@@ -114,9 +116,9 @@ class Tag:
|
||||
|
||||
def mkmask(self):
|
||||
return Tag(
|
||||
0x700 if self.isunique else 0x7ff,
|
||||
0x3ff if self.isattr else 0,
|
||||
0)
|
||||
0x700 if self.isunique else 0x7ff,
|
||||
0x3ff if self.isattr else 0,
|
||||
0)
|
||||
|
||||
def chid(self, nid):
|
||||
ntag = Tag(self.type, nid, self.size)
|
||||
@@ -142,7 +144,7 @@ class Tag:
|
||||
type = reverse_types[mask, self.type & mask]
|
||||
if prefix > 0:
|
||||
return '%s %#x%s' % (
|
||||
type, self.type & ((1 << prefix)-1), crc_status)
|
||||
type, self.type & ((1 << prefix)-1), crc_status)
|
||||
else:
|
||||
return '%s%s' % (type, crc_status)
|
||||
else:
|
||||
@@ -226,7 +228,7 @@ class MetadataPair:
|
||||
if fcrcdata:
|
||||
fcrcsize, fcrc = fcrcdata
|
||||
fcrc_ = 0xffffffff ^ binascii.crc32(
|
||||
block[off:off+fcrcsize])
|
||||
block[off:off+fcrcsize])
|
||||
if fcrc_ == fcrc:
|
||||
fcrctag.erased = True
|
||||
corrupt = True
|
||||
@@ -239,8 +241,8 @@ class MetadataPair:
|
||||
|
||||
# find active ids
|
||||
self.ids = list(it.takewhile(
|
||||
lambda id: Tag('name', id, 0) in self,
|
||||
it.count()))
|
||||
lambda id: Tag('name', id, 0) in self,
|
||||
it.count()))
|
||||
|
||||
# find most recent tags
|
||||
self.tags = []
|
||||
@@ -286,16 +288,16 @@ class MetadataPair:
|
||||
|
||||
gdiff = 0
|
||||
for tag in reversed(self.log):
|
||||
if (gmask.id != 0 and tag.is_('splice') and
|
||||
tag.id <= gtag.id - gdiff):
|
||||
if (gmask.id != 0 and tag.is_('splice')
|
||||
and tag.id <= gtag.id - gdiff):
|
||||
if tag.is_('create') and tag.id == gtag.id - gdiff:
|
||||
# creation point
|
||||
break
|
||||
|
||||
gdiff += tag.schunk
|
||||
|
||||
if ((int(gmask) & int(tag)) ==
|
||||
(int(gmask) & int(gtag.chid(gtag.id - gdiff)))):
|
||||
if ((int(gmask) & int(tag))
|
||||
== (int(gmask) & int(gtag.chid(gtag.id - gdiff)))):
|
||||
if tag.size == 0x3ff:
|
||||
# deleted
|
||||
break
|
||||
@@ -306,28 +308,28 @@ class MetadataPair:
|
||||
|
||||
def _dump_tags(self, tags, f=sys.stdout, truncate=True):
|
||||
f.write("%-8s %-8s %-13s %4s %4s" % (
|
||||
'off', 'tag', 'type', 'id', 'len'))
|
||||
'off', 'tag', 'type', 'id', 'len'))
|
||||
if truncate:
|
||||
f.write(' data (truncated)')
|
||||
f.write('\n')
|
||||
|
||||
for tag in tags:
|
||||
f.write("%08x: %08x %-14s %3s %4s" % (
|
||||
tag.off, tag,
|
||||
tag.typerepr(), tag.idrepr(), tag.sizerepr()))
|
||||
tag.off, tag,
|
||||
tag.typerepr(), tag.idrepr(), tag.sizerepr()))
|
||||
if truncate:
|
||||
f.write(" %-23s %-8s\n" % (
|
||||
' '.join('%02x' % c for c in tag.data[:8]),
|
||||
''.join(c if c >= ' ' and c <= '~' else '.'
|
||||
for c in map(chr, tag.data[:8]))))
|
||||
' '.join('%02x' % c for c in tag.data[:8]),
|
||||
''.join(c if c >= ' ' and c <= '~' else '.'
|
||||
for c in map(chr, tag.data[:8]))))
|
||||
else:
|
||||
f.write("\n")
|
||||
for i in range(0, len(tag.data), 16):
|
||||
f.write(" %08x: %-47s %-16s\n" % (
|
||||
tag.off+i,
|
||||
' '.join('%02x' % c for c in tag.data[i:i+16]),
|
||||
''.join(c if c >= ' ' and c <= '~' else '.'
|
||||
for c in map(chr, tag.data[i:i+16]))))
|
||||
tag.off+i,
|
||||
' '.join('%02x' % c for c in tag.data[i:i+16]),
|
||||
''.join(c if c >= ' ' and c <= '~' else '.'
|
||||
for c in map(chr, tag.data[i:i+16]))))
|
||||
|
||||
def dump_tags(self, f=sys.stdout, truncate=True):
|
||||
self._dump_tags(self.tags, f=f, truncate=truncate)
|
||||
@@ -346,7 +348,7 @@ def main(args):
|
||||
continue
|
||||
f.seek(block * args.block_size)
|
||||
blocks.append(f.read(args.block_size)
|
||||
.ljust(args.block_size, b'\xff'))
|
||||
.ljust(args.block_size, b'\xff'))
|
||||
|
||||
# find most recent pair
|
||||
mdir = MetadataPair(blocks)
|
||||
@@ -359,15 +361,15 @@ def main(args):
|
||||
mdir.tail = None
|
||||
|
||||
print("mdir {%s} rev %d%s%s%s" % (
|
||||
', '.join('%#x' % b
|
||||
for b in [args.block1, args.block2]
|
||||
if b is not None),
|
||||
mdir.rev,
|
||||
' (was %s)' % ', '.join('%d' % m.rev for m in mdir.pair[1:])
|
||||
if len(mdir.pair) > 1 else '',
|
||||
' (corrupted!)' if not mdir else '',
|
||||
' -> {%#x, %#x}' % struct.unpack('<II', mdir.tail.data)
|
||||
if mdir.tail else ''))
|
||||
', '.join('%#x' % b
|
||||
for b in [args.block1, args.block2]
|
||||
if b is not None),
|
||||
mdir.rev,
|
||||
' (was %s)' % ', '.join('%d' % m.rev for m in mdir.pair[1:])
|
||||
if len(mdir.pair) > 1 else '',
|
||||
' (corrupted!)' if not mdir else '',
|
||||
' -> {%#x, %#x}' % struct.unpack('<II', mdir.tail.data)
|
||||
if mdir.tail else ''))
|
||||
if args.all:
|
||||
mdir.dump_all(truncate=not args.no_truncate)
|
||||
elif args.log:
|
||||
@@ -377,23 +379,24 @@ def main(args):
|
||||
|
||||
return 0 if mdir else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Dump useful info about metadata pairs in littlefs.")
|
||||
description="Dump useful info about metadata pairs in littlefs.")
|
||||
parser.add_argument('disk',
|
||||
help="File representing the block device.")
|
||||
help="File representing the block device.")
|
||||
parser.add_argument('block_size', type=lambda x: int(x, 0),
|
||||
help="Size of a block in bytes.")
|
||||
help="Size of a block in bytes.")
|
||||
parser.add_argument('block1', type=lambda x: int(x, 0),
|
||||
help="First block address for finding the metadata pair.")
|
||||
help="First block address for finding the metadata pair.")
|
||||
parser.add_argument('block2', nargs='?', type=lambda x: int(x, 0),
|
||||
help="Second block address for finding the metadata pair.")
|
||||
help="Second block address for finding the metadata pair.")
|
||||
parser.add_argument('-l', '--log', action='store_true',
|
||||
help="Show tags in log.")
|
||||
help="Show tags in log.")
|
||||
parser.add_argument('-a', '--all', action='store_true',
|
||||
help="Show all tags in log, included tags in corrupted commits.")
|
||||
help="Show all tags in log, included tags in corrupted commits.")
|
||||
parser.add_argument('-T', '--no-truncate', action='store_true',
|
||||
help="Don't truncate large amounts of data.")
|
||||
help="Don't truncate large amounts of data.")
|
||||
sys.exit(main(parser.parse_args()))
|
||||
|
||||
+35
-29
@@ -7,6 +7,7 @@ import io
|
||||
import itertools as it
|
||||
from readmdir import Tag, MetadataPair
|
||||
|
||||
|
||||
def main(args):
|
||||
superblock = None
|
||||
gstate = b'\0\0\0\0\0\0\0\0\0\0\0\0'
|
||||
@@ -31,7 +32,7 @@ def main(args):
|
||||
for block in tail:
|
||||
f.seek(block * args.block_size)
|
||||
data.append(f.read(args.block_size)
|
||||
.ljust(args.block_size, b'\xff'))
|
||||
.ljust(args.block_size, b'\xff'))
|
||||
blocks[id(data[-1])] = block
|
||||
|
||||
mdir = MetadataPair(data)
|
||||
@@ -48,7 +49,7 @@ def main(args):
|
||||
# have superblock?
|
||||
try:
|
||||
nsuperblock = mdir[
|
||||
Tag(0x7ff, 0x3ff, 0), Tag('superblock', 0, 0)]
|
||||
Tag(0x7ff, 0x3ff, 0), Tag('superblock', 0, 0)]
|
||||
superblock = nsuperblock, mdir[Tag('inlinestruct', 0, 0)]
|
||||
except KeyError:
|
||||
pass
|
||||
@@ -57,7 +58,7 @@ def main(args):
|
||||
try:
|
||||
ngstate = mdir[Tag('movestate', 0, 0)]
|
||||
gstate = bytes((a or 0) ^ (b or 0)
|
||||
for a,b in it.zip_longest(gstate, ngstate.data))
|
||||
for a,b in it.zip_longest(gstate, ngstate.data))
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
@@ -103,10 +104,13 @@ def main(args):
|
||||
version = ('?', '?')
|
||||
if superblock:
|
||||
version = tuple(reversed(
|
||||
struct.unpack('<HH', superblock[1].data[0:4].ljust(4, b'\xff'))))
|
||||
print("%-47s%s" % ("littlefs v%s.%s" % version,
|
||||
"data (truncated, if it fits)"
|
||||
if not any([args.no_truncate, args.log, args.all]) else ""))
|
||||
struct.unpack('<HH',
|
||||
superblock[1].data[0:4].ljust(4, b'\xff'))))
|
||||
print("%-47s%s" % (
|
||||
"littlefs v%s.%s" % version,
|
||||
"data (truncated, if it fits)"
|
||||
if not any([args.no_truncate, args.log, args.all])
|
||||
else ""))
|
||||
|
||||
# print gstate
|
||||
print("gstate 0x%s" % ''.join('%02x' % c for c in gstate))
|
||||
@@ -116,19 +120,19 @@ def main(args):
|
||||
print(" orphans >=%d" % max(tag.size, 1))
|
||||
if tag.type:
|
||||
print(" move dir {%#x, %#x} id %d" % (
|
||||
blocks[0], blocks[1], tag.id))
|
||||
blocks[0], blocks[1], tag.id))
|
||||
|
||||
# print mdir info
|
||||
for i, dir in enumerate(dirs):
|
||||
print("dir %s" % (json.dumps(dir[0].path)
|
||||
if hasattr(dir[0], 'path') else '(orphan)'))
|
||||
if hasattr(dir[0], 'path') else '(orphan)'))
|
||||
|
||||
for j, mdir in enumerate(dir):
|
||||
print("mdir {%#x, %#x} rev %d (was %d)%s%s" % (
|
||||
mdir.blocks[0], mdir.blocks[1], mdir.rev, mdir.pair[1].rev,
|
||||
' (corrupted!)' if not mdir else '',
|
||||
' -> {%#x, %#x}' % struct.unpack('<II', mdir.tail.data)
|
||||
if mdir.tail else ''))
|
||||
mdir.blocks[0], mdir.blocks[1], mdir.rev, mdir.pair[1].rev,
|
||||
' (corrupted!)' if not mdir else '',
|
||||
' -> {%#x, %#x}' % struct.unpack('<II', mdir.tail.data)
|
||||
if mdir.tail else ''))
|
||||
|
||||
f = io.StringIO()
|
||||
if args.log:
|
||||
@@ -141,43 +145,45 @@ def main(args):
|
||||
lines = list(filter(None, f.getvalue().split('\n')))
|
||||
for k, line in enumerate(lines):
|
||||
print("%s %s" % (
|
||||
' ' if j == len(dir)-1 else
|
||||
'v' if k == len(lines)-1 else
|
||||
'|',
|
||||
line))
|
||||
' ' if j == len(dir)-1 else
|
||||
'v' if k == len(lines)-1 else
|
||||
'|',
|
||||
line))
|
||||
|
||||
errcode = 0
|
||||
for mdir in corrupted:
|
||||
errcode = errcode or 1
|
||||
print("*** corrupted mdir {%#x, %#x}! ***" % (
|
||||
mdir.blocks[0], mdir.blocks[1]))
|
||||
mdir.blocks[0], mdir.blocks[1]))
|
||||
|
||||
if cycle:
|
||||
errcode = errcode or 2
|
||||
print("*** cycle detected {%#x, %#x}! ***" % (
|
||||
cycle[0], cycle[1]))
|
||||
cycle[0], cycle[1]))
|
||||
|
||||
return errcode
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Dump semantic info about the metadata tree in littlefs")
|
||||
description="Dump semantic info about the metadata tree in "
|
||||
"littlefs")
|
||||
parser.add_argument('disk',
|
||||
help="File representing the block device.")
|
||||
help="File representing the block device.")
|
||||
parser.add_argument('block_size', type=lambda x: int(x, 0),
|
||||
help="Size of a block in bytes.")
|
||||
help="Size of a block in bytes.")
|
||||
parser.add_argument('block1', nargs='?', default=0,
|
||||
type=lambda x: int(x, 0),
|
||||
help="Optional first block address for finding the superblock.")
|
||||
type=lambda x: int(x, 0),
|
||||
help="Optional first block address for finding the superblock.")
|
||||
parser.add_argument('block2', nargs='?', default=1,
|
||||
type=lambda x: int(x, 0),
|
||||
help="Optional second block address for finding the superblock.")
|
||||
type=lambda x: int(x, 0),
|
||||
help="Optional second block address for finding the superblock.")
|
||||
parser.add_argument('-l', '--log', action='store_true',
|
||||
help="Show tags in log.")
|
||||
help="Show tags in log.")
|
||||
parser.add_argument('-a', '--all', action='store_true',
|
||||
help="Show all tags in log, included tags in corrupted commits.")
|
||||
help="Show all tags in log, included tags in corrupted commits.")
|
||||
parser.add_argument('-T', '--no-truncate', action='store_true',
|
||||
help="Show the full contents of files/attrs/tags.")
|
||||
help="Show the full contents of files/attrs/tags.")
|
||||
sys.exit(main(parser.parse_args()))
|
||||
|
||||
+221
-216
@@ -18,7 +18,6 @@ import os
|
||||
import re
|
||||
|
||||
|
||||
|
||||
# integer fields
|
||||
class RInt(co.namedtuple('RInt', 'x')):
|
||||
__slots__ = ()
|
||||
@@ -107,14 +106,14 @@ class StackResult(co.namedtuple('StackResult', [
|
||||
frame=0, limit=0,
|
||||
children=[]):
|
||||
return super().__new__(cls, file, function,
|
||||
RInt(frame), RInt(limit),
|
||||
children)
|
||||
RInt(frame), RInt(limit),
|
||||
children)
|
||||
|
||||
def __add__(self, other):
|
||||
return StackResult(self.file, self.function,
|
||||
self.frame + other.frame,
|
||||
max(self.limit, other.limit),
|
||||
self.children + other.children)
|
||||
self.frame + other.frame,
|
||||
max(self.limit, other.limit),
|
||||
self.children + other.children)
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
@@ -163,7 +162,7 @@ def collect(ci_paths, *,
|
||||
# collect into functions
|
||||
callgraph = co.defaultdict(lambda: (None, None, 0, set()))
|
||||
f_pattern = re.compile(
|
||||
r'([^\\]*)\\n([^:]*)[^\\]*\\n([0-9]+) bytes \((.*)\)')
|
||||
r'([^\\]*)\\n([^:]*)[^\\]*\\n([0-9]+) bytes \((.*)\)')
|
||||
for path in ci_paths:
|
||||
with open(path) as f:
|
||||
vcg = parse_vcg(f.read())
|
||||
@@ -179,12 +178,12 @@ def collect(ci_paths, *,
|
||||
if (not args.get('quiet')
|
||||
and 'static' not in type
|
||||
and 'bounded' not in type):
|
||||
print("warning: "
|
||||
"found non-static stack for %s (%s, %s)" % (
|
||||
function, type, size))
|
||||
print("warning: found non-static stack "
|
||||
"for %s (%s, %s)" % (
|
||||
function, type, size))
|
||||
_, _, _, targets = callgraph[info['title']]
|
||||
callgraph[info['title']] = (
|
||||
file, function, int(size), targets)
|
||||
file, function, int(size), targets)
|
||||
elif k == 'edge':
|
||||
info = dict(info)
|
||||
_, _, _, targets = callgraph[info['sourcename']]
|
||||
@@ -199,8 +198,7 @@ def collect(ci_paths, *,
|
||||
continue
|
||||
# ignore filtered sources
|
||||
if sources is not None:
|
||||
if not any(
|
||||
os.path.abspath(s_file) == os.path.abspath(s)
|
||||
if not any(os.path.abspath(s_file) == os.path.abspath(s)
|
||||
for s in sources):
|
||||
continue
|
||||
else:
|
||||
@@ -268,9 +266,9 @@ def collect(ci_paths, *,
|
||||
# in the case of recursion
|
||||
for source, (_, _, _, targets) in callgraph.items():
|
||||
results[source].children.extend(
|
||||
results[target]
|
||||
for target in targets
|
||||
if target in results)
|
||||
results[target]
|
||||
for target in targets
|
||||
if target in results)
|
||||
|
||||
return list(results.values())
|
||||
|
||||
@@ -282,7 +280,7 @@ def fold(Result, results, by=None, defines=[]):
|
||||
for k in it.chain(by or [], (k for k, _ in defines)):
|
||||
if k not in Result._by and k not in Result._fields:
|
||||
print("error: could not find field %r?" % k,
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
# filter by matching defines
|
||||
@@ -338,74 +336,78 @@ def table(Result, results, diff_results=None, *,
|
||||
return []
|
||||
|
||||
r = max(results_,
|
||||
key=lambda r: tuple(
|
||||
tuple(
|
||||
(getattr(r, k),)
|
||||
if getattr(r, k, None) is not None
|
||||
else ()
|
||||
for k in ([k] if k else [
|
||||
k for k in Result._sort if k in fields])
|
||||
if k in fields)
|
||||
for k in it.chain(hot, [None])))
|
||||
key=lambda r: tuple(
|
||||
tuple((getattr(r, k),)
|
||||
if getattr(r, k, None) is not None
|
||||
else ()
|
||||
for k in (
|
||||
[k] if k else [
|
||||
k for k in Result._sort
|
||||
if k in fields])
|
||||
if k in fields)
|
||||
for k in it.chain(hot, [None])))
|
||||
|
||||
# found a cycle?
|
||||
if tuple(getattr(r, k) for k in Result._by) in seen:
|
||||
return []
|
||||
|
||||
return [r._replace(children=[])] + rec_hot(
|
||||
r.children,
|
||||
seen | {tuple(getattr(r, k) for k in Result._by)})
|
||||
r.children,
|
||||
seen | {tuple(getattr(r, k) for k in Result._by)})
|
||||
|
||||
results = [r._replace(children=rec_hot(r.children)) for r in results]
|
||||
|
||||
# organize by name
|
||||
table = {
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in results}
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in results}
|
||||
diff_table = {
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in diff_results or []}
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in diff_results or []}
|
||||
names = [name
|
||||
for name in table.keys() | diff_table.keys()
|
||||
if diff_results is None
|
||||
or all_
|
||||
or any(
|
||||
types[k].ratio(
|
||||
getattr(table.get(name), k, None),
|
||||
getattr(diff_table.get(name), k, None))
|
||||
for k in fields)]
|
||||
for name in table.keys() | diff_table.keys()
|
||||
if diff_results is None
|
||||
or all_
|
||||
or any(
|
||||
types[k].ratio(
|
||||
getattr(table.get(name), k, None),
|
||||
getattr(diff_table.get(name), k, None))
|
||||
for k in fields)]
|
||||
|
||||
# sort again, now with diff info, note that python's sort is stable
|
||||
names.sort()
|
||||
if diff_results is not None:
|
||||
names.sort(key=lambda n: tuple(
|
||||
types[k].ratio(
|
||||
getattr(table.get(n), k, None),
|
||||
getattr(diff_table.get(n), k, None))
|
||||
for k in fields),
|
||||
reverse=True)
|
||||
names.sort(
|
||||
key=lambda n: tuple(
|
||||
types[k].ratio(
|
||||
getattr(table.get(n), k, None),
|
||||
getattr(diff_table.get(n), k, None))
|
||||
for k in fields),
|
||||
reverse=True)
|
||||
if sort:
|
||||
for k, reverse in reversed(sort):
|
||||
names.sort(
|
||||
key=lambda n: tuple(
|
||||
(getattr(table[n], k),)
|
||||
if getattr(table.get(n), k, None) is not None else ()
|
||||
for k in ([k] if k else [
|
||||
k for k in Result._sort if k in fields])),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
key=lambda n: tuple(
|
||||
(getattr(table[n], k),)
|
||||
if getattr(table.get(n), k, None) is not None
|
||||
else ()
|
||||
for k in (
|
||||
[k] if k else [
|
||||
k for k in Result._sort
|
||||
if k in fields])),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
|
||||
|
||||
# build up our lines
|
||||
lines = []
|
||||
|
||||
# header
|
||||
header = [
|
||||
'%s%s' % (
|
||||
','.join(by),
|
||||
' (%d added, %d removed)' % (
|
||||
sum(1 for n in table if n not in diff_table),
|
||||
sum(1 for n in diff_table if n not in table))
|
||||
if diff_results is not None and not percent else '')
|
||||
header = ['%s%s' % (
|
||||
','.join(by),
|
||||
' (%d added, %d removed)' % (
|
||||
sum(1 for n in table if n not in diff_table),
|
||||
sum(1 for n in diff_table if n not in table))
|
||||
if diff_results is not None and not percent else '')
|
||||
if not summary else '']
|
||||
if diff_results is None:
|
||||
for k in fields:
|
||||
@@ -428,43 +430,43 @@ def table(Result, results, diff_results=None, *,
|
||||
if diff_results is None:
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(getattr(r, k).table(),
|
||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
(getattr(r, k).table(),
|
||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
elif percent:
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none,
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none,
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
else:
|
||||
for k in fields:
|
||||
entry.append(getattr(diff_r, k).table()
|
||||
if getattr(diff_r, k, None) is not None
|
||||
else types[k].none)
|
||||
if getattr(diff_r, k, None) is not None
|
||||
else types[k].none)
|
||||
for k in fields:
|
||||
entry.append(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(types[k].diff(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)),
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)] if t
|
||||
else [])(
|
||||
types[k].ratio(
|
||||
(types[k].diff(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
getattr(diff_r, k, None)),
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)] if t
|
||||
else [])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
return entry
|
||||
|
||||
# recursive entry helper
|
||||
@@ -473,8 +475,8 @@ def table(Result, results, diff_results=None, *,
|
||||
# build the children table at each layer
|
||||
results_ = fold(Result, results_, by=by)
|
||||
table_ = {
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in results_}
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in results_}
|
||||
names_ = list(table_.keys())
|
||||
|
||||
# sort the children layer
|
||||
@@ -482,13 +484,16 @@ def table(Result, results, diff_results=None, *,
|
||||
if sort:
|
||||
for k, reverse in reversed(sort):
|
||||
names_.sort(
|
||||
key=lambda n: tuple(
|
||||
(getattr(table_[n], k),)
|
||||
if getattr(table_.get(n), k, None) is not None
|
||||
else ()
|
||||
for k in ([k] if k else [
|
||||
k for k in Result._sort if k in fields])),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
key=lambda n: tuple(
|
||||
(getattr(table_[n], k),)
|
||||
if getattr(table_.get(n), k, None)
|
||||
is not None
|
||||
else ()
|
||||
for k in (
|
||||
[k] if k else [
|
||||
k for k in Result._sort
|
||||
if k in fields])),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
|
||||
for i, name in enumerate(names_):
|
||||
r = table_[name]
|
||||
@@ -509,14 +514,13 @@ def table(Result, results, diff_results=None, *,
|
||||
|
||||
# recurse?
|
||||
if depth_ > 1:
|
||||
recurse(
|
||||
r.children,
|
||||
depth_-1,
|
||||
seen | {name},
|
||||
(prefixes[2+is_last] + "|-> ",
|
||||
prefixes[2+is_last] + "'-> ",
|
||||
prefixes[2+is_last] + "| ",
|
||||
prefixes[2+is_last] + " "))
|
||||
recurse(r.children,
|
||||
depth_-1,
|
||||
seen | {name},
|
||||
(prefixes[2+is_last] + "|-> ",
|
||||
prefixes[2+is_last] + "'-> ",
|
||||
prefixes[2+is_last] + "| ",
|
||||
prefixes[2+is_last] + " "))
|
||||
|
||||
# entries
|
||||
if not summary:
|
||||
@@ -530,14 +534,13 @@ def table(Result, results, diff_results=None, *,
|
||||
|
||||
# recursive entries
|
||||
if name in table and depth > 1:
|
||||
recurse(
|
||||
table[name].children,
|
||||
depth-1,
|
||||
{name},
|
||||
("|-> ",
|
||||
"'-> ",
|
||||
"| ",
|
||||
" "))
|
||||
recurse(table[name].children,
|
||||
depth-1,
|
||||
{name},
|
||||
("|-> ",
|
||||
"'-> ",
|
||||
"| ",
|
||||
" "))
|
||||
|
||||
# total
|
||||
r = next(iter(fold(Result, results, by=[])), None)
|
||||
@@ -549,8 +552,8 @@ def table(Result, results, diff_results=None, *,
|
||||
|
||||
# homogenize
|
||||
lines = [
|
||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||
for line in lines]
|
||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||
for line in lines]
|
||||
|
||||
# find the best widths, note that column 0 contains the names and is
|
||||
# handled a bit differently
|
||||
@@ -564,11 +567,11 @@ def table(Result, results, diff_results=None, *,
|
||||
# print our table
|
||||
for line in lines:
|
||||
print('%-*s %s' % (
|
||||
widths[0], line[0][0],
|
||||
' '.join('%*s%-*s' % (
|
||||
widths[i], x[0],
|
||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||
for i, x in enumerate(line[1:], 1))))
|
||||
widths[0], line[0][0],
|
||||
' '.join('%*s%-*s' % (
|
||||
widths[i], x[0],
|
||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||
for i, x in enumerate(line[1:], 1))))
|
||||
|
||||
|
||||
def main(ci_paths,
|
||||
@@ -600,10 +603,10 @@ def main(ci_paths,
|
||||
continue
|
||||
try:
|
||||
results.append(StackResult(
|
||||
**{k: r[k] for k in StackResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k] for k in StackResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
**{k: r[k] for k in StackResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k] for k in StackResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
@@ -615,25 +618,27 @@ def main(ci_paths,
|
||||
if sort:
|
||||
for k, reverse in reversed(sort):
|
||||
results.sort(
|
||||
key=lambda r: tuple(
|
||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||
for k in ([k] if k else StackResult._sort)),
|
||||
reverse=reverse ^ (not k or k in StackResult._fields))
|
||||
key=lambda r: tuple(
|
||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||
for k in ([k] if k else StackResult._sort)),
|
||||
reverse=reverse ^ (not k or k in StackResult._fields))
|
||||
|
||||
# write results to CSV
|
||||
if args.get('output'):
|
||||
with openio(args['output'], 'w') as f:
|
||||
writer = csv.DictWriter(f,
|
||||
(by if by is not None else StackResult._by)
|
||||
+ [k for k in (
|
||||
fields if fields is not None else StackResult._fields)])
|
||||
(by if by is not None else StackResult._by)
|
||||
+ [k for k in (
|
||||
fields if fields is not None
|
||||
else StackResult._fields)])
|
||||
writer.writeheader()
|
||||
for r in results:
|
||||
writer.writerow(
|
||||
{k: getattr(r, k) for k in (
|
||||
by if by is not None else StackResult._by)}
|
||||
| {k: getattr(r, k) for k in (
|
||||
fields if fields is not None else StackResult._fields)})
|
||||
{k: getattr(r, k) for k in (
|
||||
by if by is not None else StackResult._by)}
|
||||
| {k: getattr(r, k) for k in (
|
||||
fields if fields is not None
|
||||
else StackResult._fields)})
|
||||
|
||||
# find previous results?
|
||||
if args.get('diff'):
|
||||
@@ -651,10 +656,10 @@ def main(ci_paths,
|
||||
continue
|
||||
try:
|
||||
diff_results.append(StackResult(
|
||||
**{k: r[k] for k in StackResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k] for k in StackResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
**{k: r[k] for k in StackResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k] for k in StackResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
except TypeError:
|
||||
raise
|
||||
except FileNotFoundError:
|
||||
@@ -666,11 +671,11 @@ def main(ci_paths,
|
||||
# print table
|
||||
if not args.get('quiet'):
|
||||
table(StackResult, results,
|
||||
diff_results if args.get('diff') else None,
|
||||
by=by if by is not None else ['function'],
|
||||
fields=fields,
|
||||
sort=sort,
|
||||
**args)
|
||||
diff_results if args.get('diff') else None,
|
||||
by=by if by is not None else ['function'],
|
||||
fields=fields,
|
||||
sort=sort,
|
||||
**args)
|
||||
|
||||
# error on recursion
|
||||
if args.get('error_on_recursion') and any(
|
||||
@@ -682,103 +687,103 @@ if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Find stack usage at the function level.",
|
||||
allow_abbrev=False)
|
||||
description="Find stack usage at the function level.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'ci_paths',
|
||||
nargs='*',
|
||||
help="Input *.ci files.")
|
||||
'ci_paths',
|
||||
nargs='*',
|
||||
help="Input *.ci files.")
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help="Output commands that run behind the scenes.")
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help="Output commands that run behind the scenes.")
|
||||
parser.add_argument(
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help="Don't show anything, useful with -o.")
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help="Don't show anything, useful with -o.")
|
||||
parser.add_argument(
|
||||
'-o', '--output',
|
||||
help="Specify CSV file to store results.")
|
||||
'-o', '--output',
|
||||
help="Specify CSV file to store results.")
|
||||
parser.add_argument(
|
||||
'-u', '--use',
|
||||
help="Don't parse anything, use this CSV file.")
|
||||
'-u', '--use',
|
||||
help="Don't parse anything, use this CSV file.")
|
||||
parser.add_argument(
|
||||
'-d', '--diff',
|
||||
help="Specify CSV file to diff against.")
|
||||
'-d', '--diff',
|
||||
help="Specify CSV file to diff against.")
|
||||
parser.add_argument(
|
||||
'-a', '--all',
|
||||
action='store_true',
|
||||
help="Show all, not just the ones that changed.")
|
||||
'-a', '--all',
|
||||
action='store_true',
|
||||
help="Show all, not just the ones that changed.")
|
||||
parser.add_argument(
|
||||
'-p', '--percent',
|
||||
action='store_true',
|
||||
help="Only show percentage change, not a full diff.")
|
||||
'-p', '--percent',
|
||||
action='store_true',
|
||||
help="Only show percentage change, not a full diff.")
|
||||
parser.add_argument(
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
choices=StackResult._by,
|
||||
help="Group by this field.")
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
choices=StackResult._by,
|
||||
help="Group by this field.")
|
||||
parser.add_argument(
|
||||
'-f', '--field',
|
||||
dest='fields',
|
||||
action='append',
|
||||
choices=StackResult._fields,
|
||||
help="Show this field.")
|
||||
'-f', '--field',
|
||||
dest='fields',
|
||||
action='append',
|
||||
choices=StackResult._fields,
|
||||
help="Show this field.")
|
||||
parser.add_argument(
|
||||
'-D', '--define',
|
||||
dest='defines',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
help="Only include results where this field is this value.")
|
||||
'-D', '--define',
|
||||
dest='defines',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
help="Only include results where this field is this value.")
|
||||
class AppendSort(argparse.Action):
|
||||
def __call__(self, parser, namespace, value, option):
|
||||
if namespace.sort is None:
|
||||
namespace.sort = []
|
||||
namespace.sort.append((value, True if option == '-S' else False))
|
||||
parser.add_argument(
|
||||
'-s', '--sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field.")
|
||||
'-s', '--sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field.")
|
||||
parser.add_argument(
|
||||
'-S', '--reverse-sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field, but backwards.")
|
||||
'-S', '--reverse-sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field, but backwards.")
|
||||
parser.add_argument(
|
||||
'-Y', '--summary',
|
||||
action='store_true',
|
||||
help="Only show the total.")
|
||||
'-Y', '--summary',
|
||||
action='store_true',
|
||||
help="Only show the total.")
|
||||
parser.add_argument(
|
||||
'-F', '--source',
|
||||
dest='sources',
|
||||
action='append',
|
||||
help="Only consider definitions in this file. Defaults to anything "
|
||||
"in the current directory.")
|
||||
'-F', '--source',
|
||||
dest='sources',
|
||||
action='append',
|
||||
help="Only consider definitions in this file. Defaults to "
|
||||
"anything in the current directory.")
|
||||
parser.add_argument(
|
||||
'--everything',
|
||||
action='store_true',
|
||||
help="Include builtin and libc specific symbols.")
|
||||
'--everything',
|
||||
action='store_true',
|
||||
help="Include builtin and libc specific symbols.")
|
||||
parser.add_argument(
|
||||
'-z', '--depth',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Depth of function calls to show. 0 shows all calls unless we "
|
||||
"find a cycle. Defaults to 0.")
|
||||
'-z', '--depth',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Depth of function calls to show. 0 shows all calls unless "
|
||||
"we find a cycle. Defaults to 0.")
|
||||
parser.add_argument(
|
||||
'-t', '--hot',
|
||||
nargs='?',
|
||||
action='append',
|
||||
help="Show only the hot path for each function call.")
|
||||
'-t', '--hot',
|
||||
nargs='?',
|
||||
action='append',
|
||||
help="Show only the hot path for each function call.")
|
||||
parser.add_argument(
|
||||
'-e', '--error-on-recursion',
|
||||
action='store_true',
|
||||
help="Error if any functions are recursive.")
|
||||
'-e', '--error-on-recursion',
|
||||
action='store_true',
|
||||
help="Error if any functions are recursive.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+197
-192
@@ -23,7 +23,6 @@ import subprocess as sp
|
||||
OBJDUMP_PATH = ['objdump']
|
||||
|
||||
|
||||
|
||||
# integer fields
|
||||
class RInt(co.namedtuple('RInt', 'x')):
|
||||
__slots__ = ()
|
||||
@@ -100,7 +99,9 @@ class RInt(co.namedtuple('RInt', 'x')):
|
||||
return self.__class__(self.x * other.x)
|
||||
|
||||
# struct size results
|
||||
class StructResult(co.namedtuple('StructResult', ['file', 'struct', 'size'])):
|
||||
class StructResult(co.namedtuple('StructResult', [
|
||||
'file', 'struct',
|
||||
'size'])):
|
||||
_by = ['file', 'struct']
|
||||
_fields = ['size']
|
||||
_sort = ['size']
|
||||
@@ -109,11 +110,11 @@ class StructResult(co.namedtuple('StructResult', ['file', 'struct', 'size'])):
|
||||
__slots__ = ()
|
||||
def __new__(cls, file='', struct='', size=0):
|
||||
return super().__new__(cls, file, struct,
|
||||
RInt(size))
|
||||
RInt(size))
|
||||
|
||||
def __add__(self, other):
|
||||
return StructResult(self.file, self.struct,
|
||||
self.size + other.size)
|
||||
self.size + other.size)
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
@@ -133,15 +134,15 @@ def collect(obj_paths, *,
|
||||
internal=False,
|
||||
**args):
|
||||
line_pattern = re.compile(
|
||||
'^\s+(?P<no>[0-9]+)'
|
||||
'(?:\s+(?P<dir>[0-9]+))?'
|
||||
'\s+.*'
|
||||
'\s+(?P<path>[^\s]+)$')
|
||||
'^\s+(?P<no>[0-9]+)'
|
||||
'(?:\s+(?P<dir>[0-9]+))?'
|
||||
'\s+.*'
|
||||
'\s+(?P<path>[^\s]+)$')
|
||||
info_pattern = re.compile(
|
||||
'^(?:.*(?P<tag>DW_TAG_[a-z_]+).*'
|
||||
'|.*DW_AT_name.*:\s*(?P<name>[^:\s]+)\s*'
|
||||
'|.*DW_AT_decl_file.*:\s*(?P<file>[0-9]+)\s*'
|
||||
'|.*DW_AT_byte_size.*:\s*(?P<size>[0-9]+)\s*)$')
|
||||
'^(?:.*(?P<tag>DW_TAG_[a-z_]+).*'
|
||||
'|.*DW_AT_name.*:\s*(?P<name>[^:\s]+)\s*'
|
||||
'|.*DW_AT_decl_file.*:\s*(?P<file>[0-9]+)\s*'
|
||||
'|.*DW_AT_byte_size.*:\s*(?P<size>[0-9]+)\s*)$')
|
||||
|
||||
results = []
|
||||
for path in obj_paths:
|
||||
@@ -153,11 +154,11 @@ def collect(obj_paths, *,
|
||||
if args.get('verbose'):
|
||||
print(' '.join(shlex.quote(c) for c in cmd))
|
||||
proc = sp.Popen(cmd,
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
for line in proc.stdout:
|
||||
# note that files contain references to dirs, which we
|
||||
# dereference as soon as we see them as each file table follows a
|
||||
@@ -172,8 +173,8 @@ def collect(obj_paths, *,
|
||||
dir = int(m.group('dir'))
|
||||
if dir in dirs:
|
||||
files[int(m.group('no'))] = os.path.join(
|
||||
dirs[dir],
|
||||
m.group('path'))
|
||||
dirs[dir],
|
||||
m.group('path'))
|
||||
else:
|
||||
files[int(m.group('no'))] = m.group('path')
|
||||
proc.wait()
|
||||
@@ -199,11 +200,11 @@ def collect(obj_paths, *,
|
||||
if args.get('verbose'):
|
||||
print(' '.join(shlex.quote(c) for c in cmd))
|
||||
proc = sp.Popen(cmd,
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
stdout=sp.PIPE,
|
||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||
universal_newlines=True,
|
||||
errors='replace',
|
||||
close_fds=False)
|
||||
for i, line in enumerate(proc.stdout):
|
||||
# state machine here to find structs
|
||||
m = info_pattern.match(line)
|
||||
@@ -211,7 +212,7 @@ def collect(obj_paths, *,
|
||||
if m.group('tag'):
|
||||
append()
|
||||
is_struct = (m.group('tag') == 'DW_TAG_structure_type'
|
||||
or m.group('tag') == 'DW_TAG_union_type')
|
||||
or m.group('tag') == 'DW_TAG_union_type')
|
||||
s_name = None
|
||||
s_file = None
|
||||
s_size = None
|
||||
@@ -233,15 +234,14 @@ def collect(obj_paths, *,
|
||||
for r in results_:
|
||||
# ignore filtered sources
|
||||
if sources is not None:
|
||||
if not any(
|
||||
os.path.abspath(r.file) == os.path.abspath(s)
|
||||
if not any(os.path.abspath(r.file) == os.path.abspath(s)
|
||||
for s in sources):
|
||||
continue
|
||||
else:
|
||||
# default to only cwd
|
||||
if not everything and not os.path.commonpath([
|
||||
os.getcwd(),
|
||||
os.path.abspath(r.file)]) == os.getcwd():
|
||||
os.getcwd(),
|
||||
os.path.abspath(r.file)]) == os.getcwd():
|
||||
continue
|
||||
|
||||
# limit to .h files unless --internal
|
||||
@@ -250,8 +250,8 @@ def collect(obj_paths, *,
|
||||
|
||||
# simplify path
|
||||
if os.path.commonpath([
|
||||
os.getcwd(),
|
||||
os.path.abspath(r.file)]) == os.getcwd():
|
||||
os.getcwd(),
|
||||
os.path.abspath(r.file)]) == os.getcwd():
|
||||
file = os.path.relpath(r.file)
|
||||
else:
|
||||
file = os.path.abspath(r.file)
|
||||
@@ -268,7 +268,7 @@ def fold(Result, results, by=None, defines=[]):
|
||||
for k in it.chain(by or [], (k for k, _ in defines)):
|
||||
if k not in Result._by and k not in Result._fields:
|
||||
print("error: could not find field %r?" % k,
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
# filter by matching defines
|
||||
@@ -317,52 +317,55 @@ def table(Result, results, diff_results=None, *,
|
||||
|
||||
# organize by name
|
||||
table = {
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in results}
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in results}
|
||||
diff_table = {
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in diff_results or []}
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in diff_results or []}
|
||||
names = [name
|
||||
for name in table.keys() | diff_table.keys()
|
||||
if diff_results is None
|
||||
or all_
|
||||
or any(
|
||||
types[k].ratio(
|
||||
getattr(table.get(name), k, None),
|
||||
getattr(diff_table.get(name), k, None))
|
||||
for k in fields)]
|
||||
for name in table.keys() | diff_table.keys()
|
||||
if diff_results is None
|
||||
or all_
|
||||
or any(
|
||||
types[k].ratio(
|
||||
getattr(table.get(name), k, None),
|
||||
getattr(diff_table.get(name), k, None))
|
||||
for k in fields)]
|
||||
|
||||
# sort again, now with diff info, note that python's sort is stable
|
||||
names.sort()
|
||||
if diff_results is not None:
|
||||
names.sort(key=lambda n: tuple(
|
||||
types[k].ratio(
|
||||
getattr(table.get(n), k, None),
|
||||
getattr(diff_table.get(n), k, None))
|
||||
for k in fields),
|
||||
reverse=True)
|
||||
names.sort(
|
||||
key=lambda n: tuple(
|
||||
types[k].ratio(
|
||||
getattr(table.get(n), k, None),
|
||||
getattr(diff_table.get(n), k, None))
|
||||
for k in fields),
|
||||
reverse=True)
|
||||
if sort:
|
||||
for k, reverse in reversed(sort):
|
||||
names.sort(
|
||||
key=lambda n: tuple(
|
||||
(getattr(table[n], k),)
|
||||
if getattr(table.get(n), k, None) is not None else ()
|
||||
for k in ([k] if k else [
|
||||
k for k in Result._sort if k in fields])),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
key=lambda n: tuple(
|
||||
(getattr(table[n], k),)
|
||||
if getattr(table.get(n), k, None) is not None
|
||||
else ()
|
||||
for k in (
|
||||
[k] if k else [
|
||||
k for k in Result._sort
|
||||
if k in fields])),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
|
||||
|
||||
# build up our lines
|
||||
lines = []
|
||||
|
||||
# header
|
||||
header = [
|
||||
'%s%s' % (
|
||||
','.join(by),
|
||||
' (%d added, %d removed)' % (
|
||||
sum(1 for n in table if n not in diff_table),
|
||||
sum(1 for n in diff_table if n not in table))
|
||||
if diff_results is not None and not percent else '')
|
||||
header = ['%s%s' % (
|
||||
','.join(by),
|
||||
' (%d added, %d removed)' % (
|
||||
sum(1 for n in table if n not in diff_table),
|
||||
sum(1 for n in diff_table if n not in table))
|
||||
if diff_results is not None and not percent else '')
|
||||
if not summary else '']
|
||||
if diff_results is None:
|
||||
for k in fields:
|
||||
@@ -385,43 +388,43 @@ def table(Result, results, diff_results=None, *,
|
||||
if diff_results is None:
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(getattr(r, k).table(),
|
||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
(getattr(r, k).table(),
|
||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
elif percent:
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none,
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none,
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
else:
|
||||
for k in fields:
|
||||
entry.append(getattr(diff_r, k).table()
|
||||
if getattr(diff_r, k, None) is not None
|
||||
else types[k].none)
|
||||
if getattr(diff_r, k, None) is not None
|
||||
else types[k].none)
|
||||
for k in fields:
|
||||
entry.append(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(types[k].diff(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)),
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)] if t
|
||||
else [])(
|
||||
types[k].ratio(
|
||||
(types[k].diff(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
getattr(diff_r, k, None)),
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)] if t
|
||||
else [])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
return entry
|
||||
|
||||
# entries
|
||||
@@ -444,8 +447,8 @@ def table(Result, results, diff_results=None, *,
|
||||
|
||||
# homogenize
|
||||
lines = [
|
||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||
for line in lines]
|
||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||
for line in lines]
|
||||
|
||||
# find the best widths, note that column 0 contains the names and is
|
||||
# handled a bit differently
|
||||
@@ -459,11 +462,11 @@ def table(Result, results, diff_results=None, *,
|
||||
# print our table
|
||||
for line in lines:
|
||||
print('%-*s %s' % (
|
||||
widths[0], line[0][0],
|
||||
' '.join('%*s%-*s' % (
|
||||
widths[i], x[0],
|
||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||
for i, x in enumerate(line[1:], 1))))
|
||||
widths[0], line[0][0],
|
||||
' '.join('%*s%-*s' % (
|
||||
widths[i], x[0],
|
||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||
for i, x in enumerate(line[1:], 1))))
|
||||
|
||||
|
||||
def main(obj_paths, *,
|
||||
@@ -489,11 +492,11 @@ def main(obj_paths, *,
|
||||
continue
|
||||
try:
|
||||
results.append(StructResult(
|
||||
**{k: r[k] for k in StructResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k]
|
||||
for k in StructResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
**{k: r[k] for k in StructResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k]
|
||||
for k in StructResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
@@ -505,25 +508,27 @@ def main(obj_paths, *,
|
||||
if sort:
|
||||
for k, reverse in reversed(sort):
|
||||
results.sort(
|
||||
key=lambda r: tuple(
|
||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||
for k in ([k] if k else StructResult._sort)),
|
||||
reverse=reverse ^ (not k or k in StructResult._fields))
|
||||
key=lambda r: tuple(
|
||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||
for k in ([k] if k else StructResult._sort)),
|
||||
reverse=reverse ^ (not k or k in StructResult._fields))
|
||||
|
||||
# write results to CSV
|
||||
if args.get('output'):
|
||||
with openio(args['output'], 'w') as f:
|
||||
writer = csv.DictWriter(f,
|
||||
(by if by is not None else StructResult._by)
|
||||
+ [k for k in (
|
||||
fields if fields is not None else StructResult._fields)])
|
||||
(by if by is not None else StructResult._by)
|
||||
+ [k for k in (
|
||||
fields if fields is not None
|
||||
else StructResult._fields)])
|
||||
writer.writeheader()
|
||||
for r in results:
|
||||
writer.writerow(
|
||||
{k: getattr(r, k) for k in (
|
||||
by if by is not None else StructResult._by)}
|
||||
| {k: getattr(r, k) for k in (
|
||||
fields if fields is not None else StructResult._fields)})
|
||||
{k: getattr(r, k) for k in (
|
||||
by if by is not None else StructResult._by)}
|
||||
| {k: getattr(r, k) for k in (
|
||||
fields if fields is not None
|
||||
else StructResult._fields)})
|
||||
|
||||
# find previous results?
|
||||
if args.get('diff'):
|
||||
@@ -541,11 +546,11 @@ def main(obj_paths, *,
|
||||
continue
|
||||
try:
|
||||
diff_results.append(StructResult(
|
||||
**{k: r[k] for k in StructResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k]
|
||||
for k in StructResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
**{k: r[k] for k in StructResult._by
|
||||
if k in r and r[k].strip()},
|
||||
**{k: r[k]
|
||||
for k in StructResult._fields
|
||||
if k in r and r[k].strip()}))
|
||||
except TypeError:
|
||||
pass
|
||||
except FileNotFoundError:
|
||||
@@ -557,108 +562,108 @@ def main(obj_paths, *,
|
||||
# print table
|
||||
if not args.get('quiet'):
|
||||
table(StructResult, results,
|
||||
diff_results if args.get('diff') else None,
|
||||
by=by if by is not None else ['struct'],
|
||||
fields=fields,
|
||||
sort=sort,
|
||||
**args)
|
||||
diff_results if args.get('diff') else None,
|
||||
by=by if by is not None else ['struct'],
|
||||
fields=fields,
|
||||
sort=sort,
|
||||
**args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Find struct sizes.",
|
||||
allow_abbrev=False)
|
||||
description="Find struct sizes.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'obj_paths',
|
||||
nargs='*',
|
||||
help="Input *.o files.")
|
||||
'obj_paths',
|
||||
nargs='*',
|
||||
help="Input *.o files.")
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help="Output commands that run behind the scenes.")
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help="Output commands that run behind the scenes.")
|
||||
parser.add_argument(
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help="Don't show anything, useful with -o.")
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help="Don't show anything, useful with -o.")
|
||||
parser.add_argument(
|
||||
'-o', '--output',
|
||||
help="Specify CSV file to store results.")
|
||||
'-o', '--output',
|
||||
help="Specify CSV file to store results.")
|
||||
parser.add_argument(
|
||||
'-u', '--use',
|
||||
help="Don't parse anything, use this CSV file.")
|
||||
'-u', '--use',
|
||||
help="Don't parse anything, use this CSV file.")
|
||||
parser.add_argument(
|
||||
'-d', '--diff',
|
||||
help="Specify CSV file to diff against.")
|
||||
'-d', '--diff',
|
||||
help="Specify CSV file to diff against.")
|
||||
parser.add_argument(
|
||||
'-a', '--all',
|
||||
action='store_true',
|
||||
help="Show all, not just the ones that changed.")
|
||||
'-a', '--all',
|
||||
action='store_true',
|
||||
help="Show all, not just the ones that changed.")
|
||||
parser.add_argument(
|
||||
'-p', '--percent',
|
||||
action='store_true',
|
||||
help="Only show percentage change, not a full diff.")
|
||||
'-p', '--percent',
|
||||
action='store_true',
|
||||
help="Only show percentage change, not a full diff.")
|
||||
parser.add_argument(
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
choices=StructResult._by,
|
||||
help="Group by this field.")
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
choices=StructResult._by,
|
||||
help="Group by this field.")
|
||||
parser.add_argument(
|
||||
'-f', '--field',
|
||||
dest='fields',
|
||||
action='append',
|
||||
choices=StructResult._fields,
|
||||
help="Show this field.")
|
||||
'-f', '--field',
|
||||
dest='fields',
|
||||
action='append',
|
||||
choices=StructResult._fields,
|
||||
help="Show this field.")
|
||||
parser.add_argument(
|
||||
'-D', '--define',
|
||||
dest='defines',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
help="Only include results where this field is this value.")
|
||||
'-D', '--define',
|
||||
dest='defines',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
help="Only include results where this field is this value.")
|
||||
class AppendSort(argparse.Action):
|
||||
def __call__(self, parser, namespace, value, option):
|
||||
if namespace.sort is None:
|
||||
namespace.sort = []
|
||||
namespace.sort.append((value, True if option == '-S' else False))
|
||||
parser.add_argument(
|
||||
'-s', '--sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field.")
|
||||
'-s', '--sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field.")
|
||||
parser.add_argument(
|
||||
'-S', '--reverse-sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field, but backwards.")
|
||||
'-S', '--reverse-sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field, but backwards.")
|
||||
parser.add_argument(
|
||||
'-Y', '--summary',
|
||||
action='store_true',
|
||||
help="Only show the total.")
|
||||
'-Y', '--summary',
|
||||
action='store_true',
|
||||
help="Only show the total.")
|
||||
parser.add_argument(
|
||||
'-F', '--source',
|
||||
dest='sources',
|
||||
action='append',
|
||||
help="Only consider definitions in this file. Defaults to anything "
|
||||
"in the current directory.")
|
||||
'-F', '--source',
|
||||
dest='sources',
|
||||
action='append',
|
||||
help="Only consider definitions in this file. Defaults to "
|
||||
"anything in the current directory.")
|
||||
parser.add_argument(
|
||||
'--everything',
|
||||
action='store_true',
|
||||
help="Include builtin and libc specific symbols.")
|
||||
'--everything',
|
||||
action='store_true',
|
||||
help="Include builtin and libc specific symbols.")
|
||||
parser.add_argument(
|
||||
'--internal',
|
||||
action='store_true',
|
||||
help="Also show structs in .c files.")
|
||||
'--internal',
|
||||
action='store_true',
|
||||
help="Also show structs in .c files.")
|
||||
parser.add_argument(
|
||||
'--objdump-path',
|
||||
type=lambda x: x.split(),
|
||||
default=OBJDUMP_PATH,
|
||||
help="Path to the objdump executable, may include flags. "
|
||||
"Defaults to %r." % OBJDUMP_PATH)
|
||||
'--objdump-path',
|
||||
type=lambda x: x.split(),
|
||||
default=OBJDUMP_PATH,
|
||||
help="Path to the objdump executable, may include flags. "
|
||||
"Defaults to %r." % OBJDUMP_PATH)
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+221
-217
@@ -32,16 +32,16 @@ OPS = {
|
||||
'max': max,
|
||||
'avg': lambda xs: RFloat(sum(float(x) for x in xs) / len(xs)),
|
||||
'stddev': lambda xs: (
|
||||
lambda avg: RFloat(
|
||||
mt.sqrt(sum((float(x) - avg)**2 for x in xs) / len(xs)))
|
||||
lambda avg: RFloat(
|
||||
mt.sqrt(sum((float(x) - avg)**2 for x in xs) / len(xs)))
|
||||
)(sum(float(x) for x in xs) / len(xs)),
|
||||
'gmean': lambda xs: RFloat(mt.prod(float(x) for x in xs)**(1/len(xs))),
|
||||
'gstddev': lambda xs: (
|
||||
lambda gmean: RFloat(
|
||||
mt.exp(mt.sqrt(
|
||||
sum(mt.log(float(x)/gmean)**2 for x in xs)
|
||||
/ len(xs)))
|
||||
if gmean else mt.inf)
|
||||
sum(mt.log(float(x)/gmean)**2 for x in xs)
|
||||
/ len(xs)))
|
||||
if gmean else mt.inf)
|
||||
)(mt.prod(float(x) for x in xs)**(1/len(xs))),
|
||||
}
|
||||
|
||||
@@ -196,15 +196,15 @@ class RFrac(co.namedtuple('RFrac', 'a,b')):
|
||||
def notes(self):
|
||||
t = self.a.x/self.b.x if self.b.x else 1.0
|
||||
return ['∞%' if t == +mt.inf
|
||||
else '-∞%' if t == -mt.inf
|
||||
else '%.1f%%' % (100*t)]
|
||||
else '-∞%' if t == -mt.inf
|
||||
else '%.1f%%' % (100*t)]
|
||||
|
||||
def diff(self, other):
|
||||
new_a, new_b = self if self else (RInt(0), RInt(0))
|
||||
old_a, old_b = other if other else (RInt(0), RInt(0))
|
||||
return '%11s' % ('%s/%s' % (
|
||||
new_a.diff(old_a).strip(),
|
||||
new_b.diff(old_b).strip()))
|
||||
new_a.diff(old_a).strip(),
|
||||
new_b.diff(old_b).strip()))
|
||||
|
||||
def ratio(self, other):
|
||||
new_a, new_b = self if self else (RInt(0), RInt(0))
|
||||
@@ -271,8 +271,8 @@ def collect(csv_paths, renames=[], defines=[]):
|
||||
with openio(path) as f:
|
||||
reader = csv.DictReader(f, restval='')
|
||||
fields.extend(
|
||||
k for k in reader.fieldnames
|
||||
if k not in fields)
|
||||
k for k in reader.fieldnames
|
||||
if k not in fields)
|
||||
for r in reader:
|
||||
# apply any renames
|
||||
if renames:
|
||||
@@ -302,19 +302,17 @@ def infer(fields_, results,
|
||||
defines=[]):
|
||||
# if by not specified, guess it's anything not in fields/renames/defines
|
||||
if by is None:
|
||||
by = [
|
||||
k for k in fields_
|
||||
if k not in (fields or [])
|
||||
and not any(k == old_k for _, old_k in renames)
|
||||
and not any(k == k_ for k_, _ in defines)]
|
||||
by = [k for k in fields_
|
||||
if k not in (fields or [])
|
||||
and not any(k == old_k for _, old_k in renames)
|
||||
and not any(k == k_ for k_, _ in defines)]
|
||||
|
||||
# if fields not specified, guess it's anything not in by/renames/defines
|
||||
if fields is None:
|
||||
fields = [
|
||||
k for k in fields_
|
||||
if k not in (by or [])
|
||||
and not any(k == old_k for _, old_k in renames)
|
||||
and not any(k == k_ for k_, _ in defines)]
|
||||
fields = [k for k in fields_
|
||||
if k not in (by or [])
|
||||
and not any(k == old_k for _, old_k in renames)
|
||||
and not any(k == k_ for k_, _ in defines)]
|
||||
|
||||
# deduplicate by/fields
|
||||
by = list(co.OrderedDict.fromkeys(by).keys())
|
||||
@@ -338,7 +336,7 @@ def infer(fields_, results,
|
||||
break
|
||||
else:
|
||||
print("error: no type matches field %r?" % k,
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
types = types_
|
||||
|
||||
@@ -351,11 +349,11 @@ def infer(fields_, results,
|
||||
# create result class
|
||||
def __new__(cls, **r):
|
||||
return cls.__mro__[1].__new__(cls,
|
||||
**{k: r.get(k, '') for k in by},
|
||||
**{k: r[k] if k in r and isinstance(r[k], tuple)
|
||||
else ([types[k](r[k])], 1) if k in r
|
||||
else ([], 0)
|
||||
for k in fields})
|
||||
**{k: r.get(k, '') for k in by},
|
||||
**{k: r[k] if k in r and isinstance(r[k], tuple)
|
||||
else ([types[k](r[k])], 1) if k in r
|
||||
else ([], 0)
|
||||
for k in fields})
|
||||
|
||||
def __add__(self, other):
|
||||
# reuse lists if possible
|
||||
@@ -367,11 +365,11 @@ def infer(fields_, results,
|
||||
return (a[0][:a[1]] + b[0][:b[1]], a[1] + b[1])
|
||||
|
||||
return self.__class__(
|
||||
**{k: getattr(self, k) for k in by},
|
||||
**{k: extend(
|
||||
object.__getattribute__(self, k),
|
||||
object.__getattribute__(other, k))
|
||||
for k in fields})
|
||||
**{k: getattr(self, k) for k in by},
|
||||
**{k: extend(
|
||||
object.__getattribute__(self, k),
|
||||
object.__getattribute__(other, k))
|
||||
for k in fields})
|
||||
|
||||
def __getattribute__(self, k):
|
||||
if k in fields:
|
||||
@@ -401,7 +399,7 @@ def fold(Result, results, by=None, defines=[]):
|
||||
for k in it.chain(by or [], (k for k, _ in defines)):
|
||||
if k not in Result._by and k not in Result._fields:
|
||||
print("error: could not find field %r?" % k,
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
# filter by matching defines
|
||||
@@ -450,52 +448,55 @@ def table(Result, results, diff_results=None, *,
|
||||
|
||||
# organize by name
|
||||
table = {
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in results}
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in results}
|
||||
diff_table = {
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in diff_results or []}
|
||||
','.join(str(getattr(r, k) or '') for k in by): r
|
||||
for r in diff_results or []}
|
||||
names = [name
|
||||
for name in table.keys() | diff_table.keys()
|
||||
if diff_results is None
|
||||
or all_
|
||||
or any(
|
||||
types[k].ratio(
|
||||
getattr(table.get(name), k, None),
|
||||
getattr(diff_table.get(name), k, None))
|
||||
for k in fields)]
|
||||
for name in table.keys() | diff_table.keys()
|
||||
if diff_results is None
|
||||
or all_
|
||||
or any(
|
||||
types[k].ratio(
|
||||
getattr(table.get(name), k, None),
|
||||
getattr(diff_table.get(name), k, None))
|
||||
for k in fields)]
|
||||
|
||||
# sort again, now with diff info, note that python's sort is stable
|
||||
names.sort()
|
||||
if diff_results is not None:
|
||||
names.sort(key=lambda n: tuple(
|
||||
types[k].ratio(
|
||||
getattr(table.get(n), k, None),
|
||||
getattr(diff_table.get(n), k, None))
|
||||
for k in fields),
|
||||
reverse=True)
|
||||
names.sort(
|
||||
key=lambda n: tuple(
|
||||
types[k].ratio(
|
||||
getattr(table.get(n), k, None),
|
||||
getattr(diff_table.get(n), k, None))
|
||||
for k in fields),
|
||||
reverse=True)
|
||||
if sort:
|
||||
for k, reverse in reversed(sort):
|
||||
names.sort(
|
||||
key=lambda n: tuple(
|
||||
(getattr(table[n], k),)
|
||||
if getattr(table.get(n), k, None) is not None else ()
|
||||
for k in ([k] if k else [
|
||||
k for k in Result._sort if k in fields])),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
key=lambda n: tuple(
|
||||
(getattr(table[n], k),)
|
||||
if getattr(table.get(n), k, None) is not None
|
||||
else ()
|
||||
for k in (
|
||||
[k] if k else [
|
||||
k for k in Result._sort
|
||||
if k in fields])),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
|
||||
|
||||
# build up our lines
|
||||
lines = []
|
||||
|
||||
# header
|
||||
header = [
|
||||
'%s%s' % (
|
||||
','.join(by),
|
||||
' (%d added, %d removed)' % (
|
||||
sum(1 for n in table if n not in diff_table),
|
||||
sum(1 for n in diff_table if n not in table))
|
||||
if diff_results is not None and not percent else '')
|
||||
header = ['%s%s' % (
|
||||
','.join(by),
|
||||
' (%d added, %d removed)' % (
|
||||
sum(1 for n in table if n not in diff_table),
|
||||
sum(1 for n in diff_table if n not in table))
|
||||
if diff_results is not None and not percent else '')
|
||||
if not summary else '']
|
||||
if diff_results is None:
|
||||
for k in fields:
|
||||
@@ -518,43 +519,43 @@ def table(Result, results, diff_results=None, *,
|
||||
if diff_results is None:
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(getattr(r, k).table(),
|
||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
(getattr(r, k).table(),
|
||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
elif percent:
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none,
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none,
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
else:
|
||||
for k in fields:
|
||||
entry.append(getattr(diff_r, k).table()
|
||||
if getattr(diff_r, k, None) is not None
|
||||
else types[k].none)
|
||||
if getattr(diff_r, k, None) is not None
|
||||
else types[k].none)
|
||||
for k in fields:
|
||||
entry.append(getattr(r, k).table()
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
if getattr(r, k, None) is not None
|
||||
else types[k].none)
|
||||
for k in fields:
|
||||
entry.append(
|
||||
(types[k].diff(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)),
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)] if t
|
||||
else [])(
|
||||
types[k].ratio(
|
||||
(types[k].diff(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
getattr(diff_r, k, None)),
|
||||
(lambda t: ['+∞%'] if t == +mt.inf
|
||||
else ['-∞%'] if t == -mt.inf
|
||||
else ['%+.1f%%' % (100*t)] if t
|
||||
else [])(
|
||||
types[k].ratio(
|
||||
getattr(r, k, None),
|
||||
getattr(diff_r, k, None)))))
|
||||
return entry
|
||||
|
||||
# entries
|
||||
@@ -577,8 +578,8 @@ def table(Result, results, diff_results=None, *,
|
||||
|
||||
# homogenize
|
||||
lines = [
|
||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||
for line in lines]
|
||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||
for line in lines]
|
||||
|
||||
# find the best widths, note that column 0 contains the names and is
|
||||
# handled a bit differently
|
||||
@@ -592,11 +593,11 @@ def table(Result, results, diff_results=None, *,
|
||||
# print our table
|
||||
for line in lines:
|
||||
print('%-*s %s' % (
|
||||
widths[0], line[0][0],
|
||||
' '.join('%*s%-*s' % (
|
||||
widths[i], x[0],
|
||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||
for i, x in enumerate(line[1:], 1))))
|
||||
widths[0], line[0][0],
|
||||
' '.join('%*s%-*s' % (
|
||||
widths[i], x[0],
|
||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||
for i, x in enumerate(line[1:], 1))))
|
||||
|
||||
|
||||
def main(csv_paths, *,
|
||||
@@ -607,8 +608,8 @@ def main(csv_paths, *,
|
||||
**args):
|
||||
# separate out renames
|
||||
renames = list(it.chain.from_iterable(
|
||||
((k, v) for v in vs)
|
||||
for k, vs in it.chain(by or [], fields or [])))
|
||||
((k, v) for v in vs)
|
||||
for k, vs in it.chain(by or [], fields or [])))
|
||||
if by is not None:
|
||||
by = [k for k, _ in by]
|
||||
if fields is not None:
|
||||
@@ -620,7 +621,7 @@ def main(csv_paths, *,
|
||||
for k in args.get(t, []):
|
||||
if k in types:
|
||||
print("error: conflicting type for field %r?" % k,
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
types[k] = TYPES[t]
|
||||
# rename types?
|
||||
@@ -637,7 +638,7 @@ def main(csv_paths, *,
|
||||
for k in args.get(o, []):
|
||||
if k in ops:
|
||||
print("error: conflicting op for field %r?" % k,
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
ops[k] = OPS[o]
|
||||
# rename ops?
|
||||
@@ -650,7 +651,7 @@ def main(csv_paths, *,
|
||||
|
||||
if by is None and fields is None:
|
||||
print("error: needs --by or --fields to figure out fields",
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
# use is just an alias
|
||||
@@ -662,12 +663,12 @@ def main(csv_paths, *,
|
||||
|
||||
# homogenize
|
||||
Result = infer(fields_, results,
|
||||
by=by,
|
||||
fields=fields,
|
||||
types=types,
|
||||
ops=ops,
|
||||
renames=renames,
|
||||
defines=defines)
|
||||
by=by,
|
||||
fields=fields,
|
||||
types=types,
|
||||
ops=ops,
|
||||
renames=renames,
|
||||
defines=defines)
|
||||
results_ = []
|
||||
for r in results:
|
||||
if not any(k in r and r[k].strip()
|
||||
@@ -675,8 +676,8 @@ def main(csv_paths, *,
|
||||
continue
|
||||
try:
|
||||
results_.append(Result(**{
|
||||
k: r[k] for k in Result._by + Result._fields
|
||||
if k in r and r[k].strip()}))
|
||||
k: r[k] for k in Result._by + Result._fields
|
||||
if k in r and r[k].strip()}))
|
||||
except TypeError:
|
||||
pass
|
||||
results = results_
|
||||
@@ -689,10 +690,10 @@ def main(csv_paths, *,
|
||||
if sort:
|
||||
for k, reverse in reversed(sort):
|
||||
results.sort(
|
||||
key=lambda r: tuple(
|
||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||
for k in ([k] if k else Result._sort)),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
key=lambda r: tuple(
|
||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||
for k in ([k] if k else Result._sort)),
|
||||
reverse=reverse ^ (not k or k in Result._fields))
|
||||
|
||||
# write results to CSV
|
||||
if args.get('output'):
|
||||
@@ -702,7 +703,8 @@ def main(csv_paths, *,
|
||||
for r in results:
|
||||
# note we need to go through getattr to resolve lazy fields
|
||||
writer.writerow({
|
||||
k: getattr(r, k) for k in Result._by + Result._fields})
|
||||
k: getattr(r, k)
|
||||
for k in Result._by + Result._fields})
|
||||
|
||||
# find previous results?
|
||||
if args.get('diff'):
|
||||
@@ -714,8 +716,8 @@ def main(csv_paths, *,
|
||||
continue
|
||||
try:
|
||||
diff_results_.append(Result(**{
|
||||
k: r[k] for k in Result._by + Result._fields
|
||||
if k in r and r[k].strip()}))
|
||||
k: r[k] for k in Result._by + Result._fields
|
||||
if k in r and r[k].strip()}))
|
||||
except TypeError:
|
||||
pass
|
||||
diff_results = diff_results_
|
||||
@@ -726,139 +728,141 @@ def main(csv_paths, *,
|
||||
# print table
|
||||
if not args.get('quiet'):
|
||||
table(Result, results,
|
||||
diff_results if args.get('diff') else None,
|
||||
by=by,
|
||||
fields=fields,
|
||||
sort=sort,
|
||||
**args)
|
||||
diff_results if args.get('diff') else None,
|
||||
by=by,
|
||||
fields=fields,
|
||||
sort=sort,
|
||||
**args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Summarize measurements in CSV files.",
|
||||
allow_abbrev=False)
|
||||
description="Summarize measurements in CSV files.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'csv_paths',
|
||||
nargs='*',
|
||||
help="Input *.csv files.")
|
||||
'csv_paths',
|
||||
nargs='*',
|
||||
help="Input *.csv files.")
|
||||
parser.add_argument(
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help="Don't show anything, useful with -o.")
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help="Don't show anything, useful with -o.")
|
||||
parser.add_argument(
|
||||
'-o', '--output',
|
||||
help="Specify CSV file to store results.")
|
||||
'-o', '--output',
|
||||
help="Specify CSV file to store results.")
|
||||
parser.add_argument(
|
||||
'-u', '--use',
|
||||
help="Don't parse anything, use this CSV file.")
|
||||
'-u', '--use',
|
||||
help="Don't parse anything, use this CSV file.")
|
||||
parser.add_argument(
|
||||
'-d', '--diff',
|
||||
help="Specify CSV file to diff against.")
|
||||
'-d', '--diff',
|
||||
help="Specify CSV file to diff against.")
|
||||
parser.add_argument(
|
||||
'-a', '--all',
|
||||
action='store_true',
|
||||
help="Show all, not just the ones that changed.")
|
||||
'-a', '--all',
|
||||
action='store_true',
|
||||
help="Show all, not just the ones that changed.")
|
||||
parser.add_argument(
|
||||
'-p', '--percent',
|
||||
action='store_true',
|
||||
help="Only show percentage change, not a full diff.")
|
||||
'-p', '--percent',
|
||||
action='store_true',
|
||||
help="Only show percentage change, not a full diff.")
|
||||
parser.add_argument(
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Group by this field. Can rename fields with new_name=old_name.")
|
||||
'-b', '--by',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Group by this field. Can rename fields with "
|
||||
"new_name=old_name.")
|
||||
parser.add_argument(
|
||||
'-f', '--field',
|
||||
dest='fields',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Show this field. Can rename fields with new_name=old_name.")
|
||||
'-f', '--field',
|
||||
dest='fields',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs=None: (
|
||||
k.strip(),
|
||||
tuple(v.strip() for v in vs.split(','))
|
||||
if vs is not None else ())
|
||||
)(*x.split('=', 1)),
|
||||
help="Show this field. Can rename fields with "
|
||||
"new_name=old_name.")
|
||||
parser.add_argument(
|
||||
'-D', '--define',
|
||||
dest='defines',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
help="Only include results where this field is this value. May include "
|
||||
"comma-separated options.")
|
||||
'-D', '--define',
|
||||
dest='defines',
|
||||
action='append',
|
||||
type=lambda x: (
|
||||
lambda k, vs: (
|
||||
k.strip(),
|
||||
{v.strip() for v in vs.split(',')})
|
||||
)(*x.split('=', 1)),
|
||||
help="Only include results where this field is this value. May "
|
||||
"include comma-separated options.")
|
||||
class AppendSort(argparse.Action):
|
||||
def __call__(self, parser, namespace, value, option):
|
||||
if namespace.sort is None:
|
||||
namespace.sort = []
|
||||
namespace.sort.append((value, True if option == '-S' else False))
|
||||
parser.add_argument(
|
||||
'-s', '--sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field.")
|
||||
'-s', '--sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field.")
|
||||
parser.add_argument(
|
||||
'-S', '--reverse-sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field, but backwards.")
|
||||
'-S', '--reverse-sort',
|
||||
nargs='?',
|
||||
action=AppendSort,
|
||||
help="Sort by this field, but backwards.")
|
||||
parser.add_argument(
|
||||
'-Y', '--summary',
|
||||
action='store_true',
|
||||
help="Only show the total.")
|
||||
'-Y', '--summary',
|
||||
action='store_true',
|
||||
help="Only show the total.")
|
||||
parser.add_argument(
|
||||
'--int',
|
||||
action='append',
|
||||
help="Treat these fields as ints.")
|
||||
'--int',
|
||||
action='append',
|
||||
help="Treat these fields as ints.")
|
||||
parser.add_argument(
|
||||
'--float',
|
||||
action='append',
|
||||
help="Treat these fields as floats.")
|
||||
'--float',
|
||||
action='append',
|
||||
help="Treat these fields as floats.")
|
||||
parser.add_argument(
|
||||
'--frac',
|
||||
action='append',
|
||||
help="Treat these fields as fractions.")
|
||||
'--frac',
|
||||
action='append',
|
||||
help="Treat these fields as fractions.")
|
||||
parser.add_argument(
|
||||
'--sum',
|
||||
action='append',
|
||||
help="Add these fields (the default).")
|
||||
'--sum',
|
||||
action='append',
|
||||
help="Add these fields (the default).")
|
||||
parser.add_argument(
|
||||
'--prod',
|
||||
action='append',
|
||||
help="Multiply these fields.")
|
||||
'--prod',
|
||||
action='append',
|
||||
help="Multiply these fields.")
|
||||
parser.add_argument(
|
||||
'--min',
|
||||
action='append',
|
||||
help="Take the minimum of these fields.")
|
||||
'--min',
|
||||
action='append',
|
||||
help="Take the minimum of these fields.")
|
||||
parser.add_argument(
|
||||
'--max',
|
||||
action='append',
|
||||
help="Take the maximum of these fields.")
|
||||
'--max',
|
||||
action='append',
|
||||
help="Take the maximum of these fields.")
|
||||
parser.add_argument(
|
||||
'--avg', '--mean',
|
||||
action='append',
|
||||
help="Average these fields.")
|
||||
'--avg', '--mean',
|
||||
action='append',
|
||||
help="Average these fields.")
|
||||
parser.add_argument(
|
||||
'--stddev',
|
||||
action='append',
|
||||
help="Find the standard deviation of these fields.")
|
||||
'--stddev',
|
||||
action='append',
|
||||
help="Find the standard deviation of these fields.")
|
||||
parser.add_argument(
|
||||
'--gmean',
|
||||
action='append',
|
||||
help="Find the geometric mean of these fields.")
|
||||
'--gmean',
|
||||
action='append',
|
||||
help="Find the geometric mean of these fields.")
|
||||
parser.add_argument(
|
||||
'--gstddev',
|
||||
action='append',
|
||||
help="Find the geometric standard deviation of these fields.")
|
||||
'--gstddev',
|
||||
action='append',
|
||||
help="Find the geometric standard deviation of these fields.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+27
-26
@@ -75,8 +75,8 @@ class RingIO:
|
||||
# pad to fill any existing canvas, but truncate to terminal size
|
||||
h = shutil.get_terminal_size((80, 5))[1]
|
||||
lines.extend('' for _ in range(
|
||||
len(lines),
|
||||
min(RingIO.canvas_lines, h)))
|
||||
len(lines),
|
||||
min(RingIO.canvas_lines, h)))
|
||||
while len(lines) > h:
|
||||
if self.head:
|
||||
lines.pop()
|
||||
@@ -142,7 +142,7 @@ def main(path='-', *,
|
||||
time.sleep(sleep or 0.1)
|
||||
except FileNotFoundError as e:
|
||||
print("error: file not found %r" % path,
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
@@ -157,32 +157,33 @@ if __name__ == "__main__":
|
||||
import sys
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Efficiently displays the last n lines of a file/pipe.",
|
||||
allow_abbrev=False)
|
||||
description="Efficiently displays the last n lines of a "
|
||||
"file/pipe.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'path',
|
||||
nargs='?',
|
||||
help="Path to read from.")
|
||||
'path',
|
||||
nargs='?',
|
||||
help="Path to read from.")
|
||||
parser.add_argument(
|
||||
'-n', '--lines',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Show this many lines of history. 0 uses the terminal height. "
|
||||
"Defaults to 5.")
|
||||
'-n', '--lines',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Show this many lines of history. 0 uses the terminal "
|
||||
"height. Defaults to 5.")
|
||||
parser.add_argument(
|
||||
'-z', '--cat',
|
||||
action='store_true',
|
||||
help="Pipe directly to stdout.")
|
||||
'-z', '--cat',
|
||||
action='store_true',
|
||||
help="Pipe directly to stdout.")
|
||||
parser.add_argument(
|
||||
'-s', '--sleep',
|
||||
type=float,
|
||||
help="Seconds to sleep between reads. Defaults to 0.01.")
|
||||
'-s', '--sleep',
|
||||
type=float,
|
||||
help="Seconds to sleep between reads. Defaults to 0.01.")
|
||||
parser.add_argument(
|
||||
'-k', '--keep-open',
|
||||
action='store_true',
|
||||
help="Reopen the pipe on EOF, useful when multiple "
|
||||
"processes are writing.")
|
||||
'-k', '--keep-open',
|
||||
action='store_true',
|
||||
help="Reopen the pipe on EOF, useful when multiple "
|
||||
"processes are writing.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+14
-14
@@ -45,7 +45,7 @@ def main(in_path, out_paths, *, keep_open=False):
|
||||
pass
|
||||
except FileNotFoundError as e:
|
||||
print("error: file not found %r" % in_path,
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
@@ -55,20 +55,20 @@ if __name__ == "__main__":
|
||||
import sys
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(
|
||||
description="tee, but for pipes.",
|
||||
allow_abbrev=False)
|
||||
description="tee, but for pipes.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'in_path',
|
||||
help="Path to read from.")
|
||||
'in_path',
|
||||
help="Path to read from.")
|
||||
parser.add_argument(
|
||||
'out_paths',
|
||||
nargs='+',
|
||||
help="Path to write to.")
|
||||
'out_paths',
|
||||
nargs='+',
|
||||
help="Path to write to.")
|
||||
parser.add_argument(
|
||||
'-k', '--keep-open',
|
||||
action='store_true',
|
||||
help="Reopen the pipe on EOF, useful when multiple "
|
||||
"processes are writing.")
|
||||
'-k', '--keep-open',
|
||||
action='store_true',
|
||||
help="Reopen the pipe on EOF, useful when multiple "
|
||||
"processes are writing.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+543
-534
File diff suppressed because it is too large
Load Diff
+272
-268
@@ -29,14 +29,14 @@ WEAR_COLORS = ['90', '', '', '', '', '', '', '35', '35', '1;31']
|
||||
|
||||
CHARS_DOTS = " .':"
|
||||
CHARS_BRAILLE = (
|
||||
'⠀⢀⡀⣀⠠⢠⡠⣠⠄⢄⡄⣄⠤⢤⡤⣤' '⠐⢐⡐⣐⠰⢰⡰⣰⠔⢔⡔⣔⠴⢴⡴⣴'
|
||||
'⠂⢂⡂⣂⠢⢢⡢⣢⠆⢆⡆⣆⠦⢦⡦⣦' '⠒⢒⡒⣒⠲⢲⡲⣲⠖⢖⡖⣖⠶⢶⡶⣶'
|
||||
'⠈⢈⡈⣈⠨⢨⡨⣨⠌⢌⡌⣌⠬⢬⡬⣬' '⠘⢘⡘⣘⠸⢸⡸⣸⠜⢜⡜⣜⠼⢼⡼⣼'
|
||||
'⠊⢊⡊⣊⠪⢪⡪⣪⠎⢎⡎⣎⠮⢮⡮⣮' '⠚⢚⡚⣚⠺⢺⡺⣺⠞⢞⡞⣞⠾⢾⡾⣾'
|
||||
'⠁⢁⡁⣁⠡⢡⡡⣡⠅⢅⡅⣅⠥⢥⡥⣥' '⠑⢑⡑⣑⠱⢱⡱⣱⠕⢕⡕⣕⠵⢵⡵⣵'
|
||||
'⠃⢃⡃⣃⠣⢣⡣⣣⠇⢇⡇⣇⠧⢧⡧⣧' '⠓⢓⡓⣓⠳⢳⡳⣳⠗⢗⡗⣗⠷⢷⡷⣷'
|
||||
'⠉⢉⡉⣉⠩⢩⡩⣩⠍⢍⡍⣍⠭⢭⡭⣭' '⠙⢙⡙⣙⠹⢹⡹⣹⠝⢝⡝⣝⠽⢽⡽⣽'
|
||||
'⠋⢋⡋⣋⠫⢫⡫⣫⠏⢏⡏⣏⠯⢯⡯⣯' '⠛⢛⡛⣛⠻⢻⡻⣻⠟⢟⡟⣟⠿⢿⡿⣿')
|
||||
'⠀⢀⡀⣀⠠⢠⡠⣠⠄⢄⡄⣄⠤⢤⡤⣤' '⠐⢐⡐⣐⠰⢰⡰⣰⠔⢔⡔⣔⠴⢴⡴⣴'
|
||||
'⠂⢂⡂⣂⠢⢢⡢⣢⠆⢆⡆⣆⠦⢦⡦⣦' '⠒⢒⡒⣒⠲⢲⡲⣲⠖⢖⡖⣖⠶⢶⡶⣶'
|
||||
'⠈⢈⡈⣈⠨⢨⡨⣨⠌⢌⡌⣌⠬⢬⡬⣬' '⠘⢘⡘⣘⠸⢸⡸⣸⠜⢜⡜⣜⠼⢼⡼⣼'
|
||||
'⠊⢊⡊⣊⠪⢪⡪⣪⠎⢎⡎⣎⠮⢮⡮⣮' '⠚⢚⡚⣚⠺⢺⡺⣺⠞⢞⡞⣞⠾⢾⡾⣾'
|
||||
'⠁⢁⡁⣁⠡⢡⡡⣡⠅⢅⡅⣅⠥⢥⡥⣥' '⠑⢑⡑⣑⠱⢱⡱⣱⠕⢕⡕⣕⠵⢵⡵⣵'
|
||||
'⠃⢃⡃⣃⠣⢣⡣⣣⠇⢇⡇⣇⠧⢧⡧⣧' '⠓⢓⡓⣓⠳⢳⡳⣳⠗⢗⡗⣗⠷⢷⡷⣷'
|
||||
'⠉⢉⡉⣉⠩⢩⡩⣩⠍⢍⡍⣍⠭⢭⡭⣭' '⠙⢙⡙⣙⠹⢹⡹⣹⠝⢝⡝⣝⠽⢽⡽⣽'
|
||||
'⠋⢋⡋⣋⠫⢫⡫⣫⠏⢏⡏⣏⠯⢯⡯⣯' '⠛⢛⡛⣛⠻⢻⡻⣻⠟⢟⡟⣟⠿⢿⡿⣿')
|
||||
|
||||
|
||||
def openio(path, mode='r', buffering=-1):
|
||||
@@ -156,8 +156,8 @@ class RingIO:
|
||||
# pad to fill any existing canvas, but truncate to terminal size
|
||||
h = shutil.get_terminal_size((80, 5))[1]
|
||||
lines.extend('' for _ in range(
|
||||
len(lines),
|
||||
min(RingIO.canvas_lines, h)))
|
||||
len(lines),
|
||||
min(RingIO.canvas_lines, h)))
|
||||
while len(lines) > h:
|
||||
if self.head:
|
||||
lines.pop()
|
||||
@@ -238,8 +238,8 @@ def hilbert_curve(width, height):
|
||||
yield from hilbert_(x, y, b_x_, b_y_, a_x_, a_y_)
|
||||
yield from hilbert_(x+b_x_, y+b_y_, a_x, a_y, b_x-b_x_, b_y-b_y_)
|
||||
yield from hilbert_(
|
||||
x+(a_x-a_dx)+(b_x_-b_dx), y+(a_y-a_dy)+(b_y_-b_dy),
|
||||
-b_x_, -b_y_, -(a_x-a_x_), -(a_y-a_y_))
|
||||
x+(a_x-a_dx)+(b_x_-b_dx), y+(a_y-a_dy)+(b_y_-b_dy),
|
||||
-b_x_, -b_y_, -(a_x-a_x_), -(a_y-a_y_))
|
||||
|
||||
if width >= height:
|
||||
curve = hilbert_(0, 0, +width, 0, 0, +height)
|
||||
@@ -276,11 +276,11 @@ class Pixel(int):
|
||||
proged=False,
|
||||
erased=False):
|
||||
return super().__new__(cls,
|
||||
state
|
||||
| (wear << 3)
|
||||
| (1 if readed else 0)
|
||||
| (2 if proged else 0)
|
||||
| (4 if erased else 0))
|
||||
state
|
||||
| (wear << 3)
|
||||
| (1 if readed else 0)
|
||||
| (2 if proged else 0)
|
||||
| (4 if erased else 0))
|
||||
|
||||
@property
|
||||
def wear(self):
|
||||
@@ -312,8 +312,8 @@ class Pixel(int):
|
||||
|
||||
def __or__(self, other):
|
||||
return Pixel(
|
||||
(int(self) | int(other)) & 7,
|
||||
wear=max(self.wear, other.wear))
|
||||
(int(self) | int(other)) & 7,
|
||||
wear=max(self.wear, other.wear))
|
||||
|
||||
def worn(self, max_wear, *,
|
||||
block_cycles=None,
|
||||
@@ -363,12 +363,11 @@ class Pixel(int):
|
||||
f = [colors[3]]
|
||||
|
||||
if wear:
|
||||
w = min(
|
||||
self.worn(
|
||||
max_wear,
|
||||
block_cycles=block_cycles,
|
||||
wear_chars=wear_chars),
|
||||
1)
|
||||
w = min(self.worn(
|
||||
max_wear,
|
||||
block_cycles=block_cycles,
|
||||
wear_chars=wear_chars),
|
||||
1)
|
||||
|
||||
c = wear_chars[int(w * (len(wear_chars)-1))]
|
||||
f.append(wear_colors[int(w * (len(wear_colors)-1))])
|
||||
@@ -390,8 +389,8 @@ class Pixel(int):
|
||||
# apply colors
|
||||
if f and color:
|
||||
c = '%s%s\x1b[m' % (
|
||||
''.join('\x1b[%sm' % f_ for f_ in f),
|
||||
c)
|
||||
''.join('\x1b[%sm' % f_ for f_ in f),
|
||||
c)
|
||||
|
||||
return c
|
||||
|
||||
@@ -457,25 +456,25 @@ class Bmap:
|
||||
block -= self._block_window.start
|
||||
|
||||
size = (max(self._off_window.start,
|
||||
min(self._off_window.stop, off+size))
|
||||
- max(self._off_window.start,
|
||||
min(self._off_window.stop, off)))
|
||||
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)
|
||||
min(self._off_window.stop, off))
|
||||
- self._off_window.start)
|
||||
if size == 0:
|
||||
return
|
||||
|
||||
# map to our block space
|
||||
range_ = range(
|
||||
block*len(self._off_window) + off,
|
||||
block*len(self._off_window) + off+size)
|
||||
block*len(self._off_window) + off,
|
||||
block*len(self._off_window) + off+size)
|
||||
range_ = range(
|
||||
(range_.start*len(self.pixels)) // self._window,
|
||||
(range_.stop*len(self.pixels)) // self._window)
|
||||
(range_.start*len(self.pixels)) // self._window,
|
||||
(range_.stop*len(self.pixels)) // self._window)
|
||||
range_ = range(
|
||||
range_.start,
|
||||
max(range_.stop, range_.start+1))
|
||||
range_.start,
|
||||
max(range_.stop, range_.start+1))
|
||||
|
||||
# apply the op
|
||||
for i in range_:
|
||||
@@ -499,9 +498,9 @@ class Bmap:
|
||||
width=None,
|
||||
height=None):
|
||||
block_size = (block_size if block_size is not None
|
||||
else self.block_size)
|
||||
else self.block_size)
|
||||
block_count = (block_count if block_count is not None
|
||||
else self.block_count)
|
||||
else self.block_count)
|
||||
width = width if width is not None else self.width
|
||||
height = height if height is not None else self.height
|
||||
|
||||
@@ -519,17 +518,17 @@ class Bmap:
|
||||
for x in range(width*height):
|
||||
# map into our old bd space
|
||||
range_ = range(
|
||||
(x*self._window) // (width*height),
|
||||
((x+1)*self._window) // (width*height))
|
||||
(x*self._window) // (width*height),
|
||||
((x+1)*self._window) // (width*height))
|
||||
range_ = range(
|
||||
range_.start,
|
||||
max(range_.stop, range_.start+1))
|
||||
range_.start,
|
||||
max(range_.stop, range_.start+1))
|
||||
|
||||
# aggregate state
|
||||
pixels.append(ft.reduce(
|
||||
Pixel.__or__,
|
||||
self.pixels[range_.start:range_.stop],
|
||||
Pixel()))
|
||||
Pixel.__or__,
|
||||
self.pixels[range_.start:range_.stop],
|
||||
Pixel()))
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
@@ -580,10 +579,10 @@ class Bmap:
|
||||
return None
|
||||
|
||||
grid = list(it.chain.from_iterable(
|
||||
# did we resize?
|
||||
it.islice(it.chain(h, it.repeat(Pixel())),
|
||||
self.width*self.height)
|
||||
for h in self.history))
|
||||
# did we resize?
|
||||
it.islice(it.chain(h, it.repeat(Pixel())),
|
||||
self.width*self.height)
|
||||
for h in self.history))
|
||||
self.history = []
|
||||
|
||||
line = []
|
||||
@@ -603,14 +602,14 @@ class Bmap:
|
||||
byte_p |= 1 << i
|
||||
|
||||
line.append(best_p.draw(
|
||||
max_wear,
|
||||
CHARS_BRAILLE[byte_p],
|
||||
braille=True,
|
||||
read=read,
|
||||
prog=prog,
|
||||
erase=erase,
|
||||
wear=wear,
|
||||
**args))
|
||||
max_wear,
|
||||
CHARS_BRAILLE[byte_p],
|
||||
braille=True,
|
||||
read=read,
|
||||
prog=prog,
|
||||
erase=erase,
|
||||
wear=wear,
|
||||
**args))
|
||||
elif dots:
|
||||
# encode into a byte
|
||||
for x in range(self.width):
|
||||
@@ -627,23 +626,23 @@ class Bmap:
|
||||
byte_p |= 1 << i
|
||||
|
||||
line.append(best_p.draw(
|
||||
max_wear,
|
||||
CHARS_DOTS[byte_p],
|
||||
dots=True,
|
||||
read=read,
|
||||
prog=prog,
|
||||
erase=erase,
|
||||
wear=wear,
|
||||
**args))
|
||||
max_wear,
|
||||
CHARS_DOTS[byte_p],
|
||||
dots=True,
|
||||
read=read,
|
||||
prog=prog,
|
||||
erase=erase,
|
||||
wear=wear,
|
||||
**args))
|
||||
else:
|
||||
for x in range(self.width):
|
||||
line.append(grid[x + row*self.width].draw(
|
||||
max_wear,
|
||||
read=read,
|
||||
prog=prog,
|
||||
erase=erase,
|
||||
wear=wear,
|
||||
**args))
|
||||
max_wear,
|
||||
read=read,
|
||||
prog=prog,
|
||||
erase=erase,
|
||||
wear=wear,
|
||||
**args))
|
||||
|
||||
return ''.join(line)
|
||||
|
||||
@@ -725,7 +724,7 @@ def main(path='-', *,
|
||||
|
||||
if any(isinstance(b, list) and len(b) > 1 for b in block):
|
||||
print("error: more than one block address?",
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
if isinstance(block[0], list):
|
||||
block = (block[0][0], *block[1:])
|
||||
@@ -765,10 +764,10 @@ def main(path='-', *,
|
||||
|
||||
# create our block device representation
|
||||
bmap = Bmap(
|
||||
block_size=block_size if block_size is not None else 1,
|
||||
block_count=block_count if block_count is not None else 1,
|
||||
block_window=block_window,
|
||||
off_window=off_window)
|
||||
block_size=block_size if block_size is not None else 1,
|
||||
block_count=block_count if block_count is not None else 1,
|
||||
block_window=block_window,
|
||||
off_window=off_window)
|
||||
|
||||
def resize():
|
||||
nonlocal bmap
|
||||
@@ -791,12 +790,13 @@ def main(path='-', *,
|
||||
# terminal size changed?
|
||||
if width_ != bmap.width or height_ != bmap.height:
|
||||
bmap.resize(
|
||||
# scale if we're printing with dots or braille
|
||||
width=2*width_ if braille else width_,
|
||||
height=max(1,
|
||||
4*height_ if braille
|
||||
else 2*height_ if dots
|
||||
else height_))
|
||||
# scale if we're printing with dots or braille
|
||||
width=2*width_ if braille else width_,
|
||||
height=max(
|
||||
1,
|
||||
4*height_ if braille
|
||||
else 2*height_ if dots
|
||||
else height_))
|
||||
resize()
|
||||
|
||||
# keep track of some extra info
|
||||
@@ -806,30 +806,32 @@ def main(path='-', *,
|
||||
|
||||
# parse a line of trace output
|
||||
pattern = re.compile(
|
||||
'^(?P<file>[^:]*):(?P<line>[0-9]+):trace:.*?bd_(?:'
|
||||
'(?P<create>create\w*)\('
|
||||
'(?:'
|
||||
'block_size=(?P<block_size>\w+)'
|
||||
'|' 'block_count=(?P<block_count>\w+)'
|
||||
'|' '.*?' ')*' '\)'
|
||||
'|' '(?P<read>read)\('
|
||||
'\s*(?P<read_ctx>\w+)' '\s*,'
|
||||
'\s*(?P<read_block>\w+)' '\s*,'
|
||||
'\s*(?P<read_off>\w+)' '\s*,'
|
||||
'\s*(?P<read_buffer>\w+)' '\s*,'
|
||||
'\s*(?P<read_size>\w+)' '\s*\)'
|
||||
'|' '(?P<prog>prog)\('
|
||||
'\s*(?P<prog_ctx>\w+)' '\s*,'
|
||||
'\s*(?P<prog_block>\w+)' '\s*,'
|
||||
'\s*(?P<prog_off>\w+)' '\s*,'
|
||||
'\s*(?P<prog_buffer>\w+)' '\s*,'
|
||||
'\s*(?P<prog_size>\w+)' '\s*\)'
|
||||
'|' '(?P<erase>erase)\('
|
||||
'\s*(?P<erase_ctx>\w+)' '\s*,'
|
||||
'\s*(?P<erase_block>\w+)'
|
||||
'\s*\(\s*(?P<erase_size>\w+)\s*\)' '\s*\)'
|
||||
'|' '(?P<sync>sync)\('
|
||||
'\s*(?P<sync_ctx>\w+)' '\s*\)' ')\s*$')
|
||||
'^(?P<file>[^:]*):(?P<line>[0-9]+):trace:.*?bd_(?:'
|
||||
'(?P<create>create\w*)\('
|
||||
'(?:'
|
||||
'block_size=(?P<block_size>\w+)'
|
||||
'|' 'block_count=(?P<block_count>\w+)'
|
||||
'|' '.*?' ')*'
|
||||
'\)'
|
||||
'|' '(?P<read>read)\('
|
||||
'\s*(?P<read_ctx>\w+)' '\s*,'
|
||||
'\s*(?P<read_block>\w+)' '\s*,'
|
||||
'\s*(?P<read_off>\w+)' '\s*,'
|
||||
'\s*(?P<read_buffer>\w+)' '\s*,'
|
||||
'\s*(?P<read_size>\w+)' '\s*\)'
|
||||
'|' '(?P<prog>prog)\('
|
||||
'\s*(?P<prog_ctx>\w+)' '\s*,'
|
||||
'\s*(?P<prog_block>\w+)' '\s*,'
|
||||
'\s*(?P<prog_off>\w+)' '\s*,'
|
||||
'\s*(?P<prog_buffer>\w+)' '\s*,'
|
||||
'\s*(?P<prog_size>\w+)' '\s*\)'
|
||||
'|' '(?P<erase>erase)\('
|
||||
'\s*(?P<erase_ctx>\w+)' '\s*,'
|
||||
'\s*(?P<erase_block>\w+)'
|
||||
'\s*\(\s*(?P<erase_size>\w+)\s*\)' '\s*\)'
|
||||
'|' '(?P<sync>sync)\('
|
||||
'\s*(?P<sync_ctx>\w+)' '\s*\)'
|
||||
')\s*$')
|
||||
def parse(line):
|
||||
nonlocal bmap
|
||||
nonlocal readed
|
||||
@@ -852,21 +854,21 @@ def main(path='-', *,
|
||||
|
||||
if reset:
|
||||
bmap = Bmap(
|
||||
block_size=block_size_,
|
||||
block_count=block_count_,
|
||||
block_window=bmap.block_window,
|
||||
off_window=bmap.off_window,
|
||||
width=bmap.width,
|
||||
height=bmap.height)
|
||||
block_size=block_size_,
|
||||
block_count=block_count_,
|
||||
block_window=bmap.block_window,
|
||||
off_window=bmap.off_window,
|
||||
width=bmap.width,
|
||||
height=bmap.height)
|
||||
elif ((block_size is None
|
||||
and block_size_ != bmap.block_size)
|
||||
or (block_count is None
|
||||
and block_count_ != bmap.block_count)):
|
||||
bmap.resize(
|
||||
block_size=block_size if block_size is not None
|
||||
else block_size_,
|
||||
block_count=block_count if block_count is not None
|
||||
else block_count_)
|
||||
block_size=block_size if block_size is not None
|
||||
else block_size_,
|
||||
block_count=block_count if block_count is not None
|
||||
else block_count_)
|
||||
return True
|
||||
|
||||
elif m.group('read') and read:
|
||||
@@ -877,10 +879,10 @@ def main(path='-', *,
|
||||
if ((block_size is None and off+size > bmap.block_size)
|
||||
or (block_count is None and block >= bmap.block_count)):
|
||||
bmap.resize(
|
||||
block_size=block_size if block_size is not None
|
||||
else max(off+size, bmap.block_size),
|
||||
block_count=block_count if block_count is not None
|
||||
else max(block+1, bmap.block_count))
|
||||
block_size=block_size if block_size is not None
|
||||
else max(off+size, bmap.block_size),
|
||||
block_count=block_count if block_count is not None
|
||||
else max(block+1, bmap.block_count))
|
||||
|
||||
bmap.read(block, off, size)
|
||||
readed += size
|
||||
@@ -894,10 +896,10 @@ def main(path='-', *,
|
||||
if ((block_size is None and off+size > bmap.block_size)
|
||||
or (block_count is None and block >= bmap.block_count)):
|
||||
bmap.resize(
|
||||
block_size=block_size if block_size is not None
|
||||
else max(off+size, bmap.block_size),
|
||||
block_count=block_count if block_count is not None
|
||||
else max(block+1, bmap.block_count))
|
||||
block_size=block_size if block_size is not None
|
||||
else max(off+size, bmap.block_size),
|
||||
block_count=block_count if block_count is not None
|
||||
else max(block+1, bmap.block_count))
|
||||
|
||||
bmap.prog(block, off, size)
|
||||
proged += size
|
||||
@@ -910,10 +912,10 @@ def main(path='-', *,
|
||||
if ((block_size is None and size > bmap.block_size)
|
||||
or (block_count is None and block >= bmap.block_count)):
|
||||
bmap.resize(
|
||||
block_size=block_size if block_size is not None
|
||||
else max(size, bmap.block_size),
|
||||
block_count=block_count if block_count is not None
|
||||
else max(block+1, bmap.block_count))
|
||||
block_size=block_size if block_size is not None
|
||||
else max(size, bmap.block_size),
|
||||
block_count=block_count if block_count is not None
|
||||
else max(block+1, bmap.block_count))
|
||||
|
||||
bmap.erase(block, size)
|
||||
erased += size
|
||||
@@ -936,20 +938,20 @@ def main(path='-', *,
|
||||
# don't forget we've scaled this for braille/dots!
|
||||
for row in range(
|
||||
mt.ceil(bmap.height/4) if braille
|
||||
else mt.ceil(bmap.height/2) if dots
|
||||
else bmap.height):
|
||||
else mt.ceil(bmap.height/2) if dots
|
||||
else bmap.height):
|
||||
line = bmap.draw(row,
|
||||
read=read,
|
||||
prog=prog,
|
||||
erase=erase,
|
||||
wear=wear,
|
||||
block_cycles=block_cycles,
|
||||
color=color,
|
||||
dots=dots,
|
||||
braille=braille,
|
||||
hilbert=hilbert,
|
||||
lebesgue=lebesgue,
|
||||
**args)
|
||||
read=read,
|
||||
prog=prog,
|
||||
erase=erase,
|
||||
wear=wear,
|
||||
block_cycles=block_cycles,
|
||||
color=color,
|
||||
dots=dots,
|
||||
braille=braille,
|
||||
hilbert=hilbert,
|
||||
lebesgue=lebesgue,
|
||||
**args)
|
||||
if line:
|
||||
f.writeln(line)
|
||||
|
||||
@@ -965,9 +967,9 @@ def main(path='-', *,
|
||||
# what we have
|
||||
if wear:
|
||||
mean = (sum(p.wear for p in bmap.pixels)
|
||||
/ max(len(bmap.pixels), 1))
|
||||
/ max(len(bmap.pixels), 1))
|
||||
stddev = mt.sqrt(sum((p.wear - mean)**2 for p in bmap.pixels)
|
||||
/ max(len(bmap.pixels), 1))
|
||||
/ max(len(bmap.pixels), 1))
|
||||
worst = max((p.wear for p in bmap.pixels), default=0)
|
||||
|
||||
# a bit of a hack here, but this forces our header to always be
|
||||
@@ -975,17 +977,17 @@ def main(path='-', *,
|
||||
if len(f.lines) == 0:
|
||||
f.lines.append('')
|
||||
f.lines[0] = 'bd %dx%d%s%s%s%s' % (
|
||||
bmap.block_size, bmap.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 '')
|
||||
bmap.block_size, bmap.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 '')
|
||||
|
||||
bmap.clear()
|
||||
readed = 0
|
||||
@@ -1040,7 +1042,7 @@ def main(path='-', *,
|
||||
time.sleep(sleep or 0.1)
|
||||
except FileNotFoundError as e:
|
||||
print("error: file not found %r" % path,
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
@@ -1056,145 +1058,147 @@ if __name__ == "__main__":
|
||||
import sys
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Render operations on block devices based on "
|
||||
"trace output.",
|
||||
allow_abbrev=False)
|
||||
description="Render operations on block devices based on "
|
||||
"trace output.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'path',
|
||||
nargs='?',
|
||||
help="Path to read from.")
|
||||
'path',
|
||||
nargs='?',
|
||||
help="Path to read from.")
|
||||
parser.add_argument(
|
||||
'-b', '--block-size',
|
||||
type=bdgeom,
|
||||
help="Block size/geometry in bytes.")
|
||||
'-b', '--block-size',
|
||||
type=bdgeom,
|
||||
help="Block size/geometry in bytes.")
|
||||
parser.add_argument(
|
||||
'--block-count',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Block count in blocks.")
|
||||
'--block-count',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Block count in blocks.")
|
||||
parser.add_argument(
|
||||
'-c', '--block-cycles',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Assumed maximum number of erase cycles when measuring wear.")
|
||||
'-c', '--block-cycles',
|
||||
type=lambda x: int(x, 0),
|
||||
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.")
|
||||
'-@', '--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(
|
||||
'--off',
|
||||
type=lambda x: tuple(
|
||||
int(x, 0) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Show a specific offset, may be a range.")
|
||||
'--off',
|
||||
type=lambda x: tuple(
|
||||
int(x, 0) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Show a specific offset, may be a range.")
|
||||
parser.add_argument(
|
||||
'--size',
|
||||
type=lambda x: tuple(
|
||||
int(x, 0) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Show this many bytes, may be a range.")
|
||||
'--size',
|
||||
type=lambda x: tuple(
|
||||
int(x, 0) if x.strip() else None
|
||||
for x in x.split(',')),
|
||||
help="Show this many bytes, may be a range.")
|
||||
parser.add_argument(
|
||||
'-r', '--read',
|
||||
action='store_true',
|
||||
help="Render reads.")
|
||||
'-r', '--read',
|
||||
action='store_true',
|
||||
help="Render reads.")
|
||||
parser.add_argument(
|
||||
'-p', '--prog',
|
||||
action='store_true',
|
||||
help="Render progs.")
|
||||
'-p', '--prog',
|
||||
action='store_true',
|
||||
help="Render progs.")
|
||||
parser.add_argument(
|
||||
'-e', '--erase',
|
||||
action='store_true',
|
||||
help="Render erases.")
|
||||
'-e', '--erase',
|
||||
action='store_true',
|
||||
help="Render erases.")
|
||||
parser.add_argument(
|
||||
'-w', '--wear',
|
||||
action='store_true',
|
||||
help="Render wear.")
|
||||
'-w', '--wear',
|
||||
action='store_true',
|
||||
help="Render wear.")
|
||||
parser.add_argument(
|
||||
'-R', '--reset',
|
||||
action='store_true',
|
||||
help="Reset wear on block device initialization.")
|
||||
'-R', '--reset',
|
||||
action='store_true',
|
||||
help="Reset wear on block device initialization.")
|
||||
parser.add_argument(
|
||||
'-N', '--no-header',
|
||||
action='store_true',
|
||||
help="Don't show the header.")
|
||||
'-N', '--no-header',
|
||||
action='store_true',
|
||||
help="Don't show the header.")
|
||||
parser.add_argument(
|
||||
'--color',
|
||||
choices=['never', 'always', 'auto'],
|
||||
default='auto',
|
||||
help="When to use terminal colors. Defaults to 'auto'.")
|
||||
'--color',
|
||||
choices=['never', 'always', 'auto'],
|
||||
default='auto',
|
||||
help="When to use terminal colors. Defaults to 'auto'.")
|
||||
parser.add_argument(
|
||||
'-:', '--dots',
|
||||
action='store_true',
|
||||
help="Use 1x2 ascii dot characters.")
|
||||
'-:', '--dots',
|
||||
action='store_true',
|
||||
help="Use 1x2 ascii dot characters.")
|
||||
parser.add_argument(
|
||||
'-⣿', '--braille',
|
||||
action='store_true',
|
||||
help="Use 2x4 unicode braille characters. Note that braille characters "
|
||||
"sometimes suffer from inconsistent widths.")
|
||||
'-⣿', '--braille',
|
||||
action='store_true',
|
||||
help="Use 2x4 unicode braille characters. Note that braille "
|
||||
"characters sometimes suffer from inconsistent widths.")
|
||||
parser.add_argument(
|
||||
'--chars',
|
||||
help="Characters to use for read, prog, erase, noop operations.")
|
||||
'--chars',
|
||||
help="Characters to use for read, prog, erase, noop operations.")
|
||||
parser.add_argument(
|
||||
'--wear-chars',
|
||||
help="Characters to use for showing wear.")
|
||||
'--wear-chars',
|
||||
help="Characters to use for showing wear.")
|
||||
parser.add_argument(
|
||||
'--colors',
|
||||
type=lambda x: [x.strip() for x in x.split(',')],
|
||||
help="Colors to use for read, prog, erase, noop operations.")
|
||||
'--colors',
|
||||
type=lambda x: [x.strip() for x in x.split(',')],
|
||||
help="Colors to use for read, prog, erase, noop operations.")
|
||||
parser.add_argument(
|
||||
'--wear-colors',
|
||||
type=lambda x: [x.strip() for x in x.split(',')],
|
||||
help="Colors to use for showing wear.")
|
||||
'--wear-colors',
|
||||
type=lambda x: [x.strip() for x in x.split(',')],
|
||||
help="Colors to use for showing wear.")
|
||||
parser.add_argument(
|
||||
'-W', '--width',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Width in columns. 0 uses the terminal width. Defaults to "
|
||||
"min(terminal, 80).")
|
||||
'-W', '--width',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Width in columns. 0 uses the terminal width. Defaults to "
|
||||
"min(terminal, 80).")
|
||||
parser.add_argument(
|
||||
'-H', '--height',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Height in rows. 0 uses the terminal height. Defaults to 1.")
|
||||
'-H', '--height',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Height in rows. 0 uses the terminal height. Defaults to 1.")
|
||||
parser.add_argument(
|
||||
'-n', '--lines',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Show this many lines of history. 0 uses the terminal height. "
|
||||
"Defaults to 5.")
|
||||
'-n', '--lines',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Show this many lines of history. 0 uses the terminal "
|
||||
"height. Defaults to 5.")
|
||||
parser.add_argument(
|
||||
'-^', '--head',
|
||||
action='store_true',
|
||||
help="Show the first n lines.")
|
||||
'-^', '--head',
|
||||
action='store_true',
|
||||
help="Show the first n lines.")
|
||||
parser.add_argument(
|
||||
'-z', '--cat',
|
||||
action='store_true',
|
||||
help="Pipe directly to stdout.")
|
||||
'-z', '--cat',
|
||||
action='store_true',
|
||||
help="Pipe directly to stdout.")
|
||||
parser.add_argument(
|
||||
'-U', '--hilbert',
|
||||
action='store_true',
|
||||
help="Render as a space-filling Hilbert curve.")
|
||||
'-U', '--hilbert',
|
||||
action='store_true',
|
||||
help="Render as a space-filling Hilbert curve.")
|
||||
parser.add_argument(
|
||||
'-Z', '--lebesgue',
|
||||
action='store_true',
|
||||
help="Render as a space-filling Z-curve.")
|
||||
'-Z', '--lebesgue',
|
||||
action='store_true',
|
||||
help="Render as a space-filling Z-curve.")
|
||||
parser.add_argument(
|
||||
'-S', '--coalesce',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Number of operations to coalesce together.")
|
||||
'-S', '--coalesce',
|
||||
type=lambda x: int(x, 0),
|
||||
help="Number of operations to coalesce together.")
|
||||
parser.add_argument(
|
||||
'-s', '--sleep',
|
||||
type=float,
|
||||
help="Time in seconds to sleep between reads, coalescing operations.")
|
||||
'-s', '--sleep',
|
||||
type=float,
|
||||
help="Time in seconds to sleep between reads, coalescing "
|
||||
"operations.")
|
||||
parser.add_argument(
|
||||
'-k', '--keep-open',
|
||||
action='store_true',
|
||||
help="Reopen the pipe on EOF, useful when multiple "
|
||||
"processes are writing.")
|
||||
'-k', '--keep-open',
|
||||
action='store_true',
|
||||
help="Reopen the pipe on EOF, useful when multiple "
|
||||
"processes are writing.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
+55
-54
@@ -49,13 +49,13 @@ else:
|
||||
|
||||
# wait for interesting events
|
||||
flags = (inotify_simple.flags.ATTRIB
|
||||
| inotify_simple.flags.CREATE
|
||||
| inotify_simple.flags.DELETE
|
||||
| inotify_simple.flags.DELETE_SELF
|
||||
| inotify_simple.flags.MODIFY
|
||||
| inotify_simple.flags.MOVED_FROM
|
||||
| inotify_simple.flags.MOVED_TO
|
||||
| inotify_simple.flags.MOVE_SELF)
|
||||
| inotify_simple.flags.CREATE
|
||||
| inotify_simple.flags.DELETE
|
||||
| inotify_simple.flags.DELETE_SELF
|
||||
| inotify_simple.flags.MODIFY
|
||||
| inotify_simple.flags.MOVED_FROM
|
||||
| inotify_simple.flags.MOVED_TO
|
||||
| inotify_simple.flags.MOVE_SELF)
|
||||
|
||||
# recurse into directories
|
||||
for path in paths:
|
||||
@@ -113,8 +113,8 @@ class RingIO:
|
||||
# pad to fill any existing canvas, but truncate to terminal size
|
||||
h = shutil.get_terminal_size((80, 5))[1]
|
||||
lines.extend('' for _ in range(
|
||||
len(lines),
|
||||
min(RingIO.canvas_lines, h)))
|
||||
len(lines),
|
||||
min(RingIO.canvas_lines, h)))
|
||||
while len(lines) > h:
|
||||
if self.head:
|
||||
lines.pop()
|
||||
@@ -154,7 +154,7 @@ def main(command, *,
|
||||
exit_on_error=False):
|
||||
if not command:
|
||||
print('usage: %s [options] command' % sys.argv[0],
|
||||
file=sys.stderr)
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
# if we have keep_open_paths, assume user wanted keep_open
|
||||
@@ -199,12 +199,12 @@ def main(command, *,
|
||||
if lines:
|
||||
h = lines
|
||||
fcntl.ioctl(spty, termios.TIOCSWINSZ,
|
||||
struct.pack('HHHH', h, w, 0, 0))
|
||||
struct.pack('HHHH', h, w, 0, 0))
|
||||
|
||||
proc = sp.Popen(command,
|
||||
stdout=spty,
|
||||
stderr=spty,
|
||||
close_fds=False)
|
||||
stdout=spty,
|
||||
stderr=spty,
|
||||
close_fds=False)
|
||||
os.close(spty)
|
||||
mpty = os.fdopen(mpty, 'r', 1)
|
||||
|
||||
@@ -261,54 +261,55 @@ if __name__ == "__main__":
|
||||
import sys
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Traditional watch command, but with higher resolution "
|
||||
"updates and a bit different options/output format.",
|
||||
allow_abbrev=False)
|
||||
description="Traditional watch command, but with higher "
|
||||
"resolution updates and a bit different options/output "
|
||||
"format.",
|
||||
allow_abbrev=False)
|
||||
parser.add_argument(
|
||||
'command',
|
||||
nargs=argparse.REMAINDER,
|
||||
help="Command to run.")
|
||||
'command',
|
||||
nargs=argparse.REMAINDER,
|
||||
help="Command to run.")
|
||||
parser.add_argument(
|
||||
'-n', '--lines',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Show this many lines of history. 0 uses the terminal height. "
|
||||
"Defaults to 0.")
|
||||
'-n', '--lines',
|
||||
nargs='?',
|
||||
type=lambda x: int(x, 0),
|
||||
const=0,
|
||||
help="Show this many lines of history. 0 uses the terminal "
|
||||
"height. Defaults to 0.")
|
||||
parser.add_argument(
|
||||
'-^', '--head',
|
||||
action='store_true',
|
||||
help="Show the first n lines.")
|
||||
'-^', '--head',
|
||||
action='store_true',
|
||||
help="Show the first n lines.")
|
||||
parser.add_argument(
|
||||
'-z', '--cat',
|
||||
action='store_true',
|
||||
help="Pipe directly to stdout.")
|
||||
'-z', '--cat',
|
||||
action='store_true',
|
||||
help="Pipe directly to stdout.")
|
||||
parser.add_argument(
|
||||
'-s', '--sleep',
|
||||
type=float,
|
||||
help="Seconds to sleep between runs. Defaults to 0.1.")
|
||||
'-s', '--sleep',
|
||||
type=float,
|
||||
help="Seconds to sleep between runs. Defaults to 0.1.")
|
||||
parser.add_argument(
|
||||
'-k', '--keep-open',
|
||||
action='store_true',
|
||||
help="Try to use inotify to wait for changes.")
|
||||
'-k', '--keep-open',
|
||||
action='store_true',
|
||||
help="Try to use inotify to wait for changes.")
|
||||
parser.add_argument(
|
||||
'-K', '--keep-open-path',
|
||||
dest='keep_open_paths',
|
||||
action='append',
|
||||
help="Use this path for inotify. Defaults to guessing. Implies "
|
||||
"--keep-open.")
|
||||
'-K', '--keep-open-path',
|
||||
dest='keep_open_paths',
|
||||
action='append',
|
||||
help="Use this path for inotify. Defaults to guessing. Implies "
|
||||
"--keep-open.")
|
||||
parser.add_argument(
|
||||
'-b', '--buffer',
|
||||
action='store_true',
|
||||
help="Wait until command finishes to show the output.")
|
||||
'-b', '--buffer',
|
||||
action='store_true',
|
||||
help="Wait until command finishes to show the output.")
|
||||
parser.add_argument(
|
||||
'-i', '--ignore-errors',
|
||||
action='store_true',
|
||||
help="Only show output after successful runs. Implies --buffer.")
|
||||
'-i', '--ignore-errors',
|
||||
action='store_true',
|
||||
help="Only show output after successful runs. Implies --buffer.")
|
||||
parser.add_argument(
|
||||
'-e', '--exit-on-error',
|
||||
action='store_true',
|
||||
help="Exit on error.")
|
||||
'-e', '--exit-on-error',
|
||||
action='store_true',
|
||||
help="Exit on error.")
|
||||
sys.exit(main(**{k: v
|
||||
for k, v in vars(parser.parse_args()).items()
|
||||
if v is not None}))
|
||||
for k, v in vars(parser.parse_args()).items()
|
||||
if v is not None}))
|
||||
|
||||
Reference in New Issue
Block a user