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:
|
with openio(path) as f:
|
||||||
reader = csv.DictReader(f, restval='')
|
reader = csv.DictReader(f, restval='')
|
||||||
fields.extend(
|
fields.extend(
|
||||||
k for k in reader.fieldnames
|
k for k in reader.fieldnames
|
||||||
if k not in fields)
|
if k not in fields)
|
||||||
for r in reader:
|
for r in reader:
|
||||||
# apply any renames
|
# apply any renames
|
||||||
if renames:
|
if renames:
|
||||||
@@ -90,8 +90,8 @@ def main(csv_paths, output, *,
|
|||||||
|
|
||||||
# separate out renames
|
# separate out renames
|
||||||
renames = list(it.chain.from_iterable(
|
renames = list(it.chain.from_iterable(
|
||||||
((k, v) for v in vs)
|
((k, v) for v in vs)
|
||||||
for k, vs in it.chain(by or [], fields or [])))
|
for k, vs in it.chain(by or [], fields or [])))
|
||||||
if by is not None:
|
if by is not None:
|
||||||
by = [k for k, _ in by]
|
by = [k for k, _ in by]
|
||||||
if fields is not None:
|
if fields is not None:
|
||||||
@@ -99,7 +99,7 @@ def main(csv_paths, output, *,
|
|||||||
|
|
||||||
if by is None and fields is None:
|
if by is None and fields is None:
|
||||||
print("error: needs --by or --fields to figure out fields",
|
print("error: needs --by or --fields to figure out fields",
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# collect results from csv files
|
# collect results from csv files
|
||||||
@@ -108,24 +108,22 @@ def main(csv_paths, output, *,
|
|||||||
# if by not specified, guess it's anything not in
|
# if by not specified, guess it's anything not in
|
||||||
# iter/size/fields/renames/defines
|
# iter/size/fields/renames/defines
|
||||||
if by is None:
|
if by is None:
|
||||||
by = [
|
by = [k for k in fields_
|
||||||
k for k in fields_
|
if k != iter
|
||||||
if k != iter
|
and k != size
|
||||||
and k != size
|
and k not in (fields or [])
|
||||||
and k not in (fields or [])
|
and not any(k == old_k for _, old_k in renames)
|
||||||
and not any(k == old_k for _, old_k in renames)
|
and not any(k == k_ for k_, _ in defines)]
|
||||||
and not any(k == k_ for k_, _ in defines)]
|
|
||||||
|
|
||||||
# if fields not specified, guess it's anything not in
|
# if fields not specified, guess it's anything not in
|
||||||
# by/iter/size/renames/defines
|
# by/iter/size/renames/defines
|
||||||
if fields is None:
|
if fields is None:
|
||||||
fields = [
|
fields = [k for k in fields_
|
||||||
k for k in fields_
|
if k not in (by or [])
|
||||||
if k not in (by or [])
|
and k != iter
|
||||||
and k != iter
|
and k != size
|
||||||
and k != size
|
and not any(k == old_k for _, old_k in renames)
|
||||||
and not any(k == old_k for _, old_k in renames)
|
and not any(k == k_ for k_, _ in defines)]
|
||||||
and not any(k == k_ for k_, _ in defines)]
|
|
||||||
|
|
||||||
# add meas to by if it isn't already present
|
# add meas to by if it isn't already present
|
||||||
if meas is not None and meas not in by:
|
if meas is not None and meas not in by:
|
||||||
@@ -161,23 +159,23 @@ def main(csv_paths, output, *,
|
|||||||
# find amortized results
|
# find amortized results
|
||||||
if amor:
|
if amor:
|
||||||
amors.append(r
|
amors.append(r
|
||||||
| {f: sums[f] / size_ for f in fields}
|
| {f: sums[f] / size_ for f in fields}
|
||||||
| ({} if meas is None
|
| ({} if meas is None
|
||||||
else {meas: r[meas]+'+amor'} if meas in r
|
else {meas: r[meas]+'+amor'} if meas in r
|
||||||
else {meas: 'amor'}))
|
else {meas: 'amor'}))
|
||||||
|
|
||||||
# also find per-byte results
|
# also find per-byte results
|
||||||
if per:
|
if per:
|
||||||
amors.append(r
|
amors.append(r
|
||||||
| {f: r.get(f, 0) / size_ for f in fields}
|
| {f: r.get(f, 0) / size_ for f in fields}
|
||||||
| ({} if meas is None
|
| ({} if meas is None
|
||||||
else {meas: r[meas]+'+per'} if meas in r
|
else {meas: r[meas]+'+per'} if meas in r
|
||||||
else {meas: 'per'}))
|
else {meas: 'per'}))
|
||||||
|
|
||||||
# write results to CSV
|
# write results to CSV
|
||||||
with openio(output, 'w') as f:
|
with openio(output, 'w') as f:
|
||||||
writer = csv.DictWriter(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()
|
writer.writeheader()
|
||||||
for r in amors:
|
for r in amors:
|
||||||
writer.writerow(r)
|
writer.writerow(r)
|
||||||
@@ -187,68 +185,70 @@ if __name__ == "__main__":
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Amortize benchmark measurements.",
|
description="Amortize benchmark measurements.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'csv_paths',
|
'csv_paths',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
help="Input *.csv files.")
|
help="Input *.csv files.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-o', '--output',
|
'-o', '--output',
|
||||||
required=True,
|
required=True,
|
||||||
help="*.csv file to write amortized measurements to.")
|
help="*.csv file to write amortized measurements to.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--amor',
|
'--amor',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Compute amortized results.")
|
help="Compute amortized results.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--per',
|
'--per',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Compute per-byte results.")
|
help="Compute per-byte results.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--by',
|
'-b', '--by',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs=None: (
|
lambda k, vs=None: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
tuple(v.strip() for v in vs.split(','))
|
tuple(v.strip() for v in vs.split(','))
|
||||||
if vs is not None else ())
|
if vs is not None else ())
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Group by this field. Can rename fields with new_name=old_name.")
|
help="Group by this field. Can rename fields with "
|
||||||
|
"new_name=old_name.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-m', '--meas',
|
'-m', '--meas',
|
||||||
help="Optional name of measurement name field. If provided, the name "
|
help="Optional name of measurement name field. If provided, the "
|
||||||
"will be modified with +amor or +per.")
|
"name will be modified with +amor or +per.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-i', '--iter',
|
'-i', '--iter',
|
||||||
required=True,
|
required=True,
|
||||||
help="Name of iteration field.")
|
help="Name of iteration field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-n', '--size',
|
'-n', '--size',
|
||||||
help="Optional name of size field.")
|
help="Optional name of size field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-f', '--field',
|
'-f', '--field',
|
||||||
dest='fields',
|
dest='fields',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs=None: (
|
lambda k, vs=None: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
tuple(v.strip() for v in vs.split(','))
|
tuple(v.strip() for v in vs.split(','))
|
||||||
if vs is not None else ())
|
if vs is not None else ())
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Field to amortize. Can rename fields with new_name=old_name.")
|
help="Field to amortize. Can rename fields with "
|
||||||
|
"new_name=old_name.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-D', '--define',
|
'-D', '--define',
|
||||||
dest='defines',
|
dest='defines',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs: (
|
lambda k, vs: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
{v.strip() for v in vs.split(',')})
|
{v.strip() for v in vs.split(',')})
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Only include results where this field is this value. May include "
|
help="Only include results where this field is this value. May "
|
||||||
"comma-separated options.")
|
"include comma-separated options.")
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|
||||||
|
|||||||
+110
-111
@@ -53,8 +53,8 @@ def collect(csv_paths, renames=[], defines=[]):
|
|||||||
with openio(path) as f:
|
with openio(path) as f:
|
||||||
reader = csv.DictReader(f, restval='')
|
reader = csv.DictReader(f, restval='')
|
||||||
fields.extend(
|
fields.extend(
|
||||||
k for k in reader.fieldnames
|
k for k in reader.fieldnames
|
||||||
if k not in fields)
|
if k not in fields)
|
||||||
for r in reader:
|
for r in reader:
|
||||||
# apply any renames
|
# apply any renames
|
||||||
if renames:
|
if renames:
|
||||||
@@ -108,8 +108,8 @@ def main(csv_paths, output, *,
|
|||||||
|
|
||||||
# separate out renames
|
# separate out renames
|
||||||
renames = list(it.chain.from_iterable(
|
renames = list(it.chain.from_iterable(
|
||||||
((k, v) for v in vs)
|
((k, v) for v in vs)
|
||||||
for k, vs in it.chain(by or [], seeds or [], fields or [])))
|
for k, vs in it.chain(by or [], seeds or [], fields or [])))
|
||||||
if by is not None:
|
if by is not None:
|
||||||
by = [k for k, _ in by]
|
by = [k for k, _ in by]
|
||||||
if seeds is not None:
|
if seeds is not None:
|
||||||
@@ -119,7 +119,7 @@ def main(csv_paths, output, *,
|
|||||||
|
|
||||||
if by is None and fields is None:
|
if by is None and fields is None:
|
||||||
print("error: needs --by or --fields to figure out fields",
|
print("error: needs --by or --fields to figure out fields",
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# collect results from csv files
|
# collect results from csv files
|
||||||
@@ -128,22 +128,20 @@ def main(csv_paths, output, *,
|
|||||||
# if by not specified, guess it's anything not in
|
# if by not specified, guess it's anything not in
|
||||||
# seeds/fields/renames/defines
|
# seeds/fields/renames/defines
|
||||||
if by is None:
|
if by is None:
|
||||||
by = [
|
by = [k for k in fields_
|
||||||
k for k in fields_
|
if k not in (seeds or [])
|
||||||
if k not in (seeds or [])
|
and k not in (fields or [])
|
||||||
and k not in (fields or [])
|
and not any(k == old_k for _, old_k in renames)
|
||||||
and not any(k == old_k for _, old_k in renames)
|
and not any(k == k_ for k_, _ in defines)]
|
||||||
and not any(k == k_ for k_, _ in defines)]
|
|
||||||
|
|
||||||
# if fields not specified, guess it's anything not in
|
# if fields not specified, guess it's anything not in
|
||||||
# by/seeds/renames/defines
|
# by/seeds/renames/defines
|
||||||
if fields is None:
|
if fields is None:
|
||||||
fields = [
|
fields = [k for k in fields_
|
||||||
k for k in fields_
|
if k not in (by or [])
|
||||||
if k not in (by or [])
|
and k not in (seeds or [])
|
||||||
and k not in (seeds or [])
|
and not any(k == old_k for _, old_k in renames)
|
||||||
and not any(k == old_k for _, old_k in renames)
|
and not any(k == k_ for k_, _ in defines)]
|
||||||
and not any(k == k_ for k_, _ in defines)]
|
|
||||||
|
|
||||||
# add meas to by if it isn't already present
|
# add meas to by if it isn't already present
|
||||||
if meas is not None and meas not in by:
|
if meas is not None and meas not in by:
|
||||||
@@ -174,12 +172,11 @@ def main(csv_paths, output, *,
|
|||||||
meas__ = r[meas]
|
meas__ = r[meas]
|
||||||
|
|
||||||
def append(meas_, f_):
|
def append(meas_, f_):
|
||||||
avgs.append(
|
avgs.append({k: v for k, v in zip(by, key)}
|
||||||
{k: v for k, v in zip(by, key)}
|
| {f: f_(vs_) for f, vs_ in vs.items()}
|
||||||
| {f: f_(vs_) for f, vs_ in vs.items()}
|
| ({} if meas is None
|
||||||
| ({} if meas is None
|
else {meas: meas_} if meas__ is None
|
||||||
else {meas: meas_} if meas__ is None
|
else {meas: meas__+'+'+meas_}))
|
||||||
else {meas: meas__+'+'+meas_}))
|
|
||||||
|
|
||||||
if sum_: append('sum', lambda vs: sum(vs))
|
if sum_: append('sum', lambda vs: sum(vs))
|
||||||
if prod: append('prod', lambda vs: mt.prod(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 bnd: append('bnd', lambda vs: max(vs, default=0))
|
||||||
if avg: append('avg', lambda vs: sum(vs) / max(len(vs), 1))
|
if avg: append('avg', lambda vs: sum(vs) / max(len(vs), 1))
|
||||||
if stddev: append('stddev', lambda vs: (
|
if stddev: append('stddev', lambda vs: (
|
||||||
lambda avg: mt.sqrt(
|
lambda avg: mt.sqrt(
|
||||||
sum((v - avg)**2 for v in vs) / max(len(vs), 1))
|
sum((v - avg)**2 for v in vs) / max(len(vs), 1))
|
||||||
)(sum(vs) / max(len(vs), 1)))
|
)(sum(vs) / max(len(vs), 1)))
|
||||||
if gmean: append('gmean', lambda vs:
|
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: (
|
if gstddev: append('gstddev', lambda vs: (
|
||||||
lambda gmean: mt.exp(mt.sqrt(
|
lambda gmean: mt.exp(mt.sqrt(
|
||||||
sum(mt.log(v/gmean)**2 for v in vs) / max(len(vs), 1)))
|
sum(mt.log(v/gmean)**2 for v in vs) / max(len(vs), 1)))
|
||||||
if gmean else mt.inf
|
if gmean else mt.inf
|
||||||
)(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))))
|
||||||
|
|
||||||
# write results to CSVS
|
# write results to CSVS
|
||||||
with openio(output, 'w') as f:
|
with openio(output, 'w') as f:
|
||||||
@@ -212,101 +209,103 @@ if __name__ == "__main__":
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Compute averages/etc of benchmark measurements.",
|
description="Compute averages/etc of benchmark measurements.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'csv_paths',
|
'csv_paths',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
help="Input *.csv files.")
|
help="Input *.csv files.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-o', '--output',
|
'-o', '--output',
|
||||||
required=True,
|
required=True,
|
||||||
help="*.csv file to write amortized measurements to.")
|
help="*.csv file to write amortized measurements to.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--sum',
|
'--sum',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Compute the sum.")
|
help="Compute the sum.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--prod',
|
'--prod',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Compute the product.")
|
help="Compute the product.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--min',
|
'--min',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Compute the min.")
|
help="Compute the min.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--max',
|
'--max',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Compute the max.")
|
help="Compute the max.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--bnd',
|
'--bnd',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Compute the bounds (min+max concatenated).")
|
help="Compute the bounds (min+max concatenated).")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--avg', '--mean',
|
'--avg', '--mean',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Compute the average (the default).")
|
help="Compute the average (the default).")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--stddev',
|
'--stddev',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Compute the standard deviation.")
|
help="Compute the standard deviation.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--gmean',
|
'--gmean',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Compute the geometric mean.")
|
help="Compute the geometric mean.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--gstddev',
|
'--gstddev',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Compute the geometric standard deviation.")
|
help="Compute the geometric standard deviation.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--by',
|
'-b', '--by',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs=None: (
|
lambda k, vs=None: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
tuple(v.strip() for v in vs.split(','))
|
tuple(v.strip() for v in vs.split(','))
|
||||||
if vs is not None else ())
|
if vs is not None else ())
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Group by this field. Can rename fields with new_name=old_name.")
|
help="Group by this field. Can rename fields with "
|
||||||
|
"new_name=old_name.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-m', '--meas',
|
'-m', '--meas',
|
||||||
help="Optional name of measurement name field. If provided, the name "
|
help="Optional name of measurement name field. If provided, the "
|
||||||
"will be modified with +amor or +per.")
|
"name will be modified with +amor or +per.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-s', '--seed',
|
'-s', '--seed',
|
||||||
dest='seeds',
|
dest='seeds',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs=None: (
|
lambda k, vs=None: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
tuple(v.strip() for v in vs.split(','))
|
tuple(v.strip() for v in vs.split(','))
|
||||||
if vs is not None else ())
|
if vs is not None else ())
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Field to ignore when averaging. Can rename fields with "
|
help="Field to ignore when averaging. Can rename fields with "
|
||||||
"new_name=old_name.")
|
"new_name=old_name.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-f', '--field',
|
'-f', '--field',
|
||||||
dest='fields',
|
dest='fields',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs=None: (
|
lambda k, vs=None: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
tuple(v.strip() for v in vs.split(','))
|
tuple(v.strip() for v in vs.split(','))
|
||||||
if vs is not None else ())
|
if vs is not None else ())
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Field to amortize. Can rename fields with new_name=old_name.")
|
help="Field to amortize. Can rename fields with "
|
||||||
|
"new_name=old_name.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-D', '--define',
|
'-D', '--define',
|
||||||
dest='defines',
|
dest='defines',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs: (
|
lambda k, vs: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
{v.strip() for v in vs.split(',')})
|
{v.strip() for v in vs.split(',')})
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Only include results where this field is this value. May include "
|
help="Only include results where this field is this value. May "
|
||||||
"comma-separated options.")
|
"include comma-separated options.")
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|
||||||
|
|||||||
+508
-501
File diff suppressed because it is too large
Load Diff
+49
-47
@@ -21,6 +21,7 @@ import shutil
|
|||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
GIT_PATH = ['git']
|
GIT_PATH = ['git']
|
||||||
|
|
||||||
|
|
||||||
@@ -36,17 +37,17 @@ def openio(path, mode='r', buffering=-1):
|
|||||||
|
|
||||||
def changeprefix(from_prefix, to_prefix, line):
|
def changeprefix(from_prefix, to_prefix, line):
|
||||||
line, count1 = re.subn(
|
line, count1 = re.subn(
|
||||||
'\\b'+from_prefix,
|
'\\b'+from_prefix,
|
||||||
to_prefix,
|
to_prefix,
|
||||||
line)
|
line)
|
||||||
line, count2 = re.subn(
|
line, count2 = re.subn(
|
||||||
'\\b'+from_prefix.upper(),
|
'\\b'+from_prefix.upper(),
|
||||||
to_prefix.upper(),
|
to_prefix.upper(),
|
||||||
line)
|
line)
|
||||||
line, count3 = re.subn(
|
line, count3 = re.subn(
|
||||||
'\\B-D'+from_prefix.upper(),
|
'\\B-D'+from_prefix.upper(),
|
||||||
'-D'+to_prefix.upper(),
|
'-D'+to_prefix.upper(),
|
||||||
line)
|
line)
|
||||||
return line, count1+count2+count3
|
return line, count1+count2+count3
|
||||||
|
|
||||||
def changefile(from_prefix, to_prefix, from_path, to_path, *,
|
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
|
# Summary
|
||||||
print('%s: %d replacements' % (
|
print('%s: %d replacements' % (
|
||||||
'%s -> %s' % (from_path, to_path) if not to_path_temp else from_path,
|
'%s -> %s' % (from_path, to_path) if not to_path_temp
|
||||||
count))
|
else from_path,
|
||||||
|
count))
|
||||||
|
|
||||||
def main(from_prefix, to_prefix, paths=[], *,
|
def main(from_prefix, to_prefix, paths=[], *,
|
||||||
verbose=False,
|
verbose=False,
|
||||||
@@ -111,7 +113,7 @@ def main(from_prefix, to_prefix, paths=[], *,
|
|||||||
|
|
||||||
# rename contents
|
# rename contents
|
||||||
changefile(from_prefix, to_prefix, from_path, to_path,
|
changefile(from_prefix, to_prefix, from_path, to_path,
|
||||||
no_replacements=no_replacements)
|
no_replacements=no_replacements)
|
||||||
|
|
||||||
# stage?
|
# stage?
|
||||||
if git and not no_stage:
|
if git and not no_stage:
|
||||||
@@ -130,49 +132,49 @@ if __name__ == "__main__":
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Change prefixes in files/filenames. Useful for creating "
|
description="Change prefixes in files/filenames. Useful for "
|
||||||
"different versions of a codebase that don't conflict at compile "
|
"creating different versions of a codebase that don't "
|
||||||
"time.",
|
"conflict at compile time.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'from_prefix',
|
'from_prefix',
|
||||||
help="Prefix to replace.")
|
help="Prefix to replace.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'to_prefix',
|
'to_prefix',
|
||||||
help="Prefix to replace with.")
|
help="Prefix to replace with.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'paths',
|
'paths',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
help="Files to operate on.")
|
help="Files to operate on.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-v', '--verbose',
|
'-v', '--verbose',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Output commands that run behind the scenes.")
|
help="Output commands that run behind the scenes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-o', '--output',
|
'-o', '--output',
|
||||||
help="Output file.")
|
help="Output file.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-N', '--no-replacements',
|
'-N', '--no-replacements',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Don't change prefixes in files")
|
help="Don't change prefixes in files")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-R', '--no-renames',
|
'-R', '--no-renames',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Don't rename files")
|
help="Don't rename files")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--git',
|
'--git',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Use git to find/update files.")
|
help="Use git to find/update files.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--no-stage',
|
'--no-stage',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Don't stage changes with git.")
|
help="Don't stage changes with git.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--git-path',
|
'--git-path',
|
||||||
type=lambda x: x.split(),
|
type=lambda x: x.split(),
|
||||||
default=GIT_PATH,
|
default=GIT_PATH,
|
||||||
help="Path to git executable, may include flags. "
|
help="Path to git executable, may include flags. "
|
||||||
"Defaults to %r." % GIT_PATH)
|
"Defaults to %r." % GIT_PATH)
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+207
-202
@@ -115,11 +115,11 @@ class CodeResult(co.namedtuple('CodeResult', [
|
|||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
def __new__(cls, file='', function='', size=0):
|
def __new__(cls, file='', function='', size=0):
|
||||||
return super().__new__(cls, file, function,
|
return super().__new__(cls, file, function,
|
||||||
RInt(size))
|
RInt(size))
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
return CodeResult(self.file, self.function,
|
return CodeResult(self.file, self.function,
|
||||||
self.size + other.size)
|
self.size + other.size)
|
||||||
|
|
||||||
|
|
||||||
def openio(path, mode='r', buffering=-1):
|
def openio(path, mode='r', buffering=-1):
|
||||||
@@ -140,18 +140,18 @@ def collect(obj_paths, *,
|
|||||||
everything=False,
|
everything=False,
|
||||||
**args):
|
**args):
|
||||||
size_pattern = re.compile(
|
size_pattern = re.compile(
|
||||||
'^(?P<size>[0-9a-fA-F]+)' +
|
'^(?P<size>[0-9a-fA-F]+)'
|
||||||
' (?P<type>[%s])' % re.escape(nm_types) +
|
+ ' (?P<type>[%s])' % re.escape(nm_types)
|
||||||
' (?P<func>.+?)$')
|
+ ' (?P<func>.+?)$')
|
||||||
line_pattern = re.compile(
|
line_pattern = re.compile(
|
||||||
'^\s+(?P<no>[0-9]+)'
|
'^\s+(?P<no>[0-9]+)'
|
||||||
'(?:\s+(?P<dir>[0-9]+))?'
|
'(?:\s+(?P<dir>[0-9]+))?'
|
||||||
'\s+.*'
|
'\s+.*'
|
||||||
'\s+(?P<path>[^\s]+)$')
|
'\s+(?P<path>[^\s]+)$')
|
||||||
info_pattern = re.compile(
|
info_pattern = re.compile(
|
||||||
'^(?:.*(?P<tag>DW_TAG_[a-z_]+).*'
|
'^(?:.*(?P<tag>DW_TAG_[a-z_]+).*'
|
||||||
'|.*DW_AT_name.*:\s*(?P<name>[^:\s]+)\s*'
|
'|.*DW_AT_name.*:\s*(?P<name>[^:\s]+)\s*'
|
||||||
'|.*DW_AT_decl_file.*:\s*(?P<file>[0-9]+)\s*)$')
|
'|.*DW_AT_decl_file.*:\s*(?P<file>[0-9]+)\s*)$')
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
for path in obj_paths:
|
for path in obj_paths:
|
||||||
@@ -165,11 +165,11 @@ def collect(obj_paths, *,
|
|||||||
if args.get('verbose'):
|
if args.get('verbose'):
|
||||||
print(' '.join(shlex.quote(c) for c in cmd))
|
print(' '.join(shlex.quote(c) for c in cmd))
|
||||||
proc = sp.Popen(cmd,
|
proc = sp.Popen(cmd,
|
||||||
stdout=sp.PIPE,
|
stdout=sp.PIPE,
|
||||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||||
universal_newlines=True,
|
universal_newlines=True,
|
||||||
errors='replace',
|
errors='replace',
|
||||||
close_fds=False)
|
close_fds=False)
|
||||||
for line in proc.stdout:
|
for line in proc.stdout:
|
||||||
m = size_pattern.match(line)
|
m = size_pattern.match(line)
|
||||||
if m:
|
if m:
|
||||||
@@ -178,8 +178,8 @@ def collect(obj_paths, *,
|
|||||||
if not everything and func.startswith('__'):
|
if not everything and func.startswith('__'):
|
||||||
continue
|
continue
|
||||||
results_.append(CodeResult(
|
results_.append(CodeResult(
|
||||||
file, func,
|
file, func,
|
||||||
int(m.group('size'), 16)))
|
int(m.group('size'), 16)))
|
||||||
proc.wait()
|
proc.wait()
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
if not args.get('verbose'):
|
if not args.get('verbose'):
|
||||||
@@ -196,11 +196,11 @@ def collect(obj_paths, *,
|
|||||||
if args.get('verbose'):
|
if args.get('verbose'):
|
||||||
print(' '.join(shlex.quote(c) for c in cmd))
|
print(' '.join(shlex.quote(c) for c in cmd))
|
||||||
proc = sp.Popen(cmd,
|
proc = sp.Popen(cmd,
|
||||||
stdout=sp.PIPE,
|
stdout=sp.PIPE,
|
||||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||||
universal_newlines=True,
|
universal_newlines=True,
|
||||||
errors='replace',
|
errors='replace',
|
||||||
close_fds=False)
|
close_fds=False)
|
||||||
for line in proc.stdout:
|
for line in proc.stdout:
|
||||||
# note that files contain references to dirs, which we
|
# note that files contain references to dirs, which we
|
||||||
# dereference as soon as we see them as each file table follows a
|
# 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'))
|
dir = int(m.group('dir'))
|
||||||
if dir in dirs:
|
if dir in dirs:
|
||||||
files[int(m.group('no'))] = os.path.join(
|
files[int(m.group('no'))] = os.path.join(
|
||||||
dirs[dir],
|
dirs[dir],
|
||||||
m.group('path'))
|
m.group('path'))
|
||||||
else:
|
else:
|
||||||
files[int(m.group('no'))] = m.group('path')
|
files[int(m.group('no'))] = m.group('path')
|
||||||
proc.wait()
|
proc.wait()
|
||||||
@@ -241,11 +241,11 @@ def collect(obj_paths, *,
|
|||||||
if args.get('verbose'):
|
if args.get('verbose'):
|
||||||
print(' '.join(shlex.quote(c) for c in cmd))
|
print(' '.join(shlex.quote(c) for c in cmd))
|
||||||
proc = sp.Popen(cmd,
|
proc = sp.Popen(cmd,
|
||||||
stdout=sp.PIPE,
|
stdout=sp.PIPE,
|
||||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||||
universal_newlines=True,
|
universal_newlines=True,
|
||||||
errors='replace',
|
errors='replace',
|
||||||
close_fds=False)
|
close_fds=False)
|
||||||
for line in proc.stdout:
|
for line in proc.stdout:
|
||||||
# state machine here to find definitions
|
# state machine here to find definitions
|
||||||
m = info_pattern.match(line)
|
m = info_pattern.match(line)
|
||||||
@@ -279,17 +279,16 @@ def collect(obj_paths, *,
|
|||||||
file = defs[r.function]
|
file = defs[r.function]
|
||||||
else:
|
else:
|
||||||
_, file = max(
|
_, file = max(
|
||||||
defs.items(),
|
defs.items(),
|
||||||
key=lambda d: difflib.SequenceMatcher(None,
|
key=lambda d: difflib.SequenceMatcher(None,
|
||||||
d[0],
|
d[0],
|
||||||
r.function, False).ratio())
|
r.function, False).ratio())
|
||||||
else:
|
else:
|
||||||
file = r.file
|
file = r.file
|
||||||
|
|
||||||
# ignore filtered sources
|
# ignore filtered sources
|
||||||
if sources is not None:
|
if sources is not None:
|
||||||
if not any(
|
if not any(os.path.abspath(file) == os.path.abspath(s)
|
||||||
os.path.abspath(file) == os.path.abspath(s)
|
|
||||||
for s in sources):
|
for s in sources):
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
@@ -319,7 +318,7 @@ def fold(Result, results, by=None, defines=[]):
|
|||||||
for k in it.chain(by or [], (k for k, _ in 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:
|
if k not in Result._by and k not in Result._fields:
|
||||||
print("error: could not find field %r?" % k,
|
print("error: could not find field %r?" % k,
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# filter by matching defines
|
# filter by matching defines
|
||||||
@@ -368,52 +367,55 @@ def table(Result, results, diff_results=None, *,
|
|||||||
|
|
||||||
# organize by name
|
# organize by name
|
||||||
table = {
|
table = {
|
||||||
','.join(str(getattr(r, k) or '') for k in by): r
|
','.join(str(getattr(r, k) or '') for k in by): r
|
||||||
for r in results}
|
for r in results}
|
||||||
diff_table = {
|
diff_table = {
|
||||||
','.join(str(getattr(r, k) or '') for k in by): r
|
','.join(str(getattr(r, k) or '') for k in by): r
|
||||||
for r in diff_results or []}
|
for r in diff_results or []}
|
||||||
names = [name
|
names = [name
|
||||||
for name in table.keys() | diff_table.keys()
|
for name in table.keys() | diff_table.keys()
|
||||||
if diff_results is None
|
if diff_results is None
|
||||||
or all_
|
or all_
|
||||||
or any(
|
or any(
|
||||||
types[k].ratio(
|
types[k].ratio(
|
||||||
getattr(table.get(name), k, None),
|
getattr(table.get(name), k, None),
|
||||||
getattr(diff_table.get(name), k, None))
|
getattr(diff_table.get(name), k, None))
|
||||||
for k in fields)]
|
for k in fields)]
|
||||||
|
|
||||||
# sort again, now with diff info, note that python's sort is stable
|
# sort again, now with diff info, note that python's sort is stable
|
||||||
names.sort()
|
names.sort()
|
||||||
if diff_results is not None:
|
if diff_results is not None:
|
||||||
names.sort(key=lambda n: tuple(
|
names.sort(
|
||||||
types[k].ratio(
|
key=lambda n: tuple(
|
||||||
getattr(table.get(n), k, None),
|
types[k].ratio(
|
||||||
getattr(diff_table.get(n), k, None))
|
getattr(table.get(n), k, None),
|
||||||
for k in fields),
|
getattr(diff_table.get(n), k, None))
|
||||||
reverse=True)
|
for k in fields),
|
||||||
|
reverse=True)
|
||||||
if sort:
|
if sort:
|
||||||
for k, reverse in reversed(sort):
|
for k, reverse in reversed(sort):
|
||||||
names.sort(
|
names.sort(
|
||||||
key=lambda n: tuple(
|
key=lambda n: tuple(
|
||||||
(getattr(table[n], k),)
|
(getattr(table[n], k),)
|
||||||
if getattr(table.get(n), k, None) is not None else ()
|
if getattr(table.get(n), k, None) is not None
|
||||||
for k in ([k] if k else [
|
else ()
|
||||||
k for k in Result._sort if k in fields])),
|
for k in (
|
||||||
reverse=reverse ^ (not k or k in Result._fields))
|
[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
|
# build up our lines
|
||||||
lines = []
|
lines = []
|
||||||
|
|
||||||
# header
|
# header
|
||||||
header = [
|
header = ['%s%s' % (
|
||||||
'%s%s' % (
|
','.join(by),
|
||||||
','.join(by),
|
' (%d added, %d removed)' % (
|
||||||
' (%d added, %d removed)' % (
|
sum(1 for n in table if n not in diff_table),
|
||||||
sum(1 for n in table if n not in diff_table),
|
sum(1 for n in diff_table if n not in table))
|
||||||
sum(1 for n in diff_table if n not in table))
|
if diff_results is not None and not percent else '')
|
||||||
if diff_results is not None and not percent else '')
|
|
||||||
if not summary else '']
|
if not summary else '']
|
||||||
if diff_results is None:
|
if diff_results is None:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
@@ -436,43 +438,43 @@ def table(Result, results, diff_results=None, *,
|
|||||||
if diff_results is None:
|
if diff_results is None:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(getattr(r, k).table(),
|
(getattr(r, k).table(),
|
||||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
elif percent:
|
elif percent:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(getattr(r, k).table()
|
(getattr(r, k).table()
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none,
|
else types[k].none,
|
||||||
(lambda t: ['+∞%'] if t == +mt.inf
|
(lambda t: ['+∞%'] if t == +mt.inf
|
||||||
else ['-∞%'] if t == -mt.inf
|
else ['-∞%'] if t == -mt.inf
|
||||||
else ['%+.1f%%' % (100*t)])(
|
else ['%+.1f%%' % (100*t)])(
|
||||||
types[k].ratio(
|
types[k].ratio(
|
||||||
getattr(r, k, None),
|
getattr(r, k, None),
|
||||||
getattr(diff_r, k, None)))))
|
getattr(diff_r, k, None)))))
|
||||||
else:
|
else:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(getattr(diff_r, k).table()
|
entry.append(getattr(diff_r, k).table()
|
||||||
if getattr(diff_r, k, None) is not None
|
if getattr(diff_r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(getattr(r, k).table()
|
entry.append(getattr(r, k).table()
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(types[k].diff(
|
(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(
|
|
||||||
getattr(r, k, None),
|
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
|
return entry
|
||||||
|
|
||||||
# entries
|
# entries
|
||||||
@@ -495,8 +497,8 @@ def table(Result, results, diff_results=None, *,
|
|||||||
|
|
||||||
# homogenize
|
# homogenize
|
||||||
lines = [
|
lines = [
|
||||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||||
for line in lines]
|
for line in lines]
|
||||||
|
|
||||||
# find the best widths, note that column 0 contains the names and is
|
# find the best widths, note that column 0 contains the names and is
|
||||||
# handled a bit differently
|
# handled a bit differently
|
||||||
@@ -510,11 +512,11 @@ def table(Result, results, diff_results=None, *,
|
|||||||
# print our table
|
# print our table
|
||||||
for line in lines:
|
for line in lines:
|
||||||
print('%-*s %s' % (
|
print('%-*s %s' % (
|
||||||
widths[0], line[0][0],
|
widths[0], line[0][0],
|
||||||
' '.join('%*s%-*s' % (
|
' '.join('%*s%-*s' % (
|
||||||
widths[i], x[0],
|
widths[i], x[0],
|
||||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||||
for i, x in enumerate(line[1:], 1))))
|
for i, x in enumerate(line[1:], 1))))
|
||||||
|
|
||||||
|
|
||||||
def main(obj_paths, *,
|
def main(obj_paths, *,
|
||||||
@@ -540,10 +542,10 @@ def main(obj_paths, *,
|
|||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
results.append(CodeResult(
|
results.append(CodeResult(
|
||||||
**{k: r[k] for k in CodeResult._by
|
**{k: r[k] for k in CodeResult._by
|
||||||
if k in r and r[k].strip()},
|
if k in r and r[k].strip()},
|
||||||
**{k: r[k] for k in CodeResult._fields
|
**{k: r[k] for k in CodeResult._fields
|
||||||
if k in r and r[k].strip()}))
|
if k in r and r[k].strip()}))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -555,25 +557,27 @@ def main(obj_paths, *,
|
|||||||
if sort:
|
if sort:
|
||||||
for k, reverse in reversed(sort):
|
for k, reverse in reversed(sort):
|
||||||
results.sort(
|
results.sort(
|
||||||
key=lambda r: tuple(
|
key=lambda r: tuple(
|
||||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||||
for k in ([k] if k else CodeResult._sort)),
|
for k in ([k] if k else CodeResult._sort)),
|
||||||
reverse=reverse ^ (not k or k in CodeResult._fields))
|
reverse=reverse ^ (not k or k in CodeResult._fields))
|
||||||
|
|
||||||
# write results to CSV
|
# write results to CSV
|
||||||
if args.get('output'):
|
if args.get('output'):
|
||||||
with openio(args['output'], 'w') as f:
|
with openio(args['output'], 'w') as f:
|
||||||
writer = csv.DictWriter(f,
|
writer = csv.DictWriter(f,
|
||||||
(by if by is not None else CodeResult._by)
|
(by if by is not None else CodeResult._by)
|
||||||
+ [k for k in (
|
+ [k for k in (
|
||||||
fields if fields is not None else CodeResult._fields)])
|
fields if fields is not None
|
||||||
|
else CodeResult._fields)])
|
||||||
writer.writeheader()
|
writer.writeheader()
|
||||||
for r in results:
|
for r in results:
|
||||||
writer.writerow(
|
writer.writerow(
|
||||||
{k: getattr(r, k) for k in (
|
{k: getattr(r, k) for k in (
|
||||||
by if by is not None else CodeResult._by)}
|
by if by is not None else CodeResult._by)}
|
||||||
| {k: getattr(r, k) for k in (
|
| {k: getattr(r, k) for k in (
|
||||||
fields if fields is not None else CodeResult._fields)})
|
fields if fields is not None
|
||||||
|
else CodeResult._fields)})
|
||||||
|
|
||||||
# find previous results?
|
# find previous results?
|
||||||
if args.get('diff'):
|
if args.get('diff'):
|
||||||
@@ -591,10 +595,10 @@ def main(obj_paths, *,
|
|||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
diff_results.append(CodeResult(
|
diff_results.append(CodeResult(
|
||||||
**{k: r[k] for k in CodeResult._by
|
**{k: r[k] for k in CodeResult._by
|
||||||
if k in r and r[k].strip()},
|
if k in r and r[k].strip()},
|
||||||
**{k: r[k] for k in CodeResult._fields
|
**{k: r[k] for k in CodeResult._fields
|
||||||
if k in r and r[k].strip()}))
|
if k in r and r[k].strip()}))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
@@ -606,115 +610,116 @@ def main(obj_paths, *,
|
|||||||
# print table
|
# print table
|
||||||
if not args.get('quiet'):
|
if not args.get('quiet'):
|
||||||
table(CodeResult, results,
|
table(CodeResult, results,
|
||||||
diff_results if args.get('diff') else None,
|
diff_results if args.get('diff') else None,
|
||||||
by=by if by is not None else ['function'],
|
by=by if by is not None else ['function'],
|
||||||
fields=fields,
|
fields=fields,
|
||||||
sort=sort,
|
sort=sort,
|
||||||
**args)
|
**args)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Find code size at the function level.",
|
description="Find code size at the function level.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'obj_paths',
|
'obj_paths',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
help="Input *.o files.")
|
help="Input *.o files.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-v', '--verbose',
|
'-v', '--verbose',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Output commands that run behind the scenes.")
|
help="Output commands that run behind the scenes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-q', '--quiet',
|
'-q', '--quiet',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Don't show anything, useful with -o.")
|
help="Don't show anything, useful with -o.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-o', '--output',
|
'-o', '--output',
|
||||||
help="Specify CSV file to store results.")
|
help="Specify CSV file to store results.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-u', '--use',
|
'-u', '--use',
|
||||||
help="Don't parse anything, use this CSV file.")
|
help="Don't parse anything, use this CSV file.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-d', '--diff',
|
'-d', '--diff',
|
||||||
help="Specify CSV file to diff against.")
|
help="Specify CSV file to diff against.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-a', '--all',
|
'-a', '--all',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show all, not just the ones that changed.")
|
help="Show all, not just the ones that changed.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-p', '--percent',
|
'-p', '--percent',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Only show percentage change, not a full diff.")
|
help="Only show percentage change, not a full diff.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--by',
|
'-b', '--by',
|
||||||
action='append',
|
action='append',
|
||||||
choices=CodeResult._by,
|
choices=CodeResult._by,
|
||||||
help="Group by this field.")
|
help="Group by this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-f', '--field',
|
'-f', '--field',
|
||||||
dest='fields',
|
dest='fields',
|
||||||
action='append',
|
action='append',
|
||||||
choices=CodeResult._fields,
|
choices=CodeResult._fields,
|
||||||
help="Show this field.")
|
help="Show this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-D', '--define',
|
'-D', '--define',
|
||||||
dest='defines',
|
dest='defines',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs: (
|
lambda k, vs: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
{v.strip() for v in vs.split(',')})
|
{v.strip() for v in vs.split(',')})
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Only include results where this field is this value.")
|
help="Only include results where this field is this value.")
|
||||||
class AppendSort(argparse.Action):
|
class AppendSort(argparse.Action):
|
||||||
def __call__(self, parser, namespace, value, option):
|
def __call__(self, parser, namespace, value, option):
|
||||||
if namespace.sort is None:
|
if namespace.sort is None:
|
||||||
namespace.sort = []
|
namespace.sort = []
|
||||||
namespace.sort.append((value, True if option == '-S' else False))
|
namespace.sort.append((value, True if option == '-S' else False))
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-s', '--sort',
|
'-s', '--sort',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
action=AppendSort,
|
action=AppendSort,
|
||||||
help="Sort by this field.")
|
help="Sort by this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-S', '--reverse-sort',
|
'-S', '--reverse-sort',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
action=AppendSort,
|
action=AppendSort,
|
||||||
help="Sort by this field, but backwards.")
|
help="Sort by this field, but backwards.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-Y', '--summary',
|
'-Y', '--summary',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Only show the total.")
|
help="Only show the total.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-F', '--source',
|
'-F', '--source',
|
||||||
dest='sources',
|
dest='sources',
|
||||||
action='append',
|
action='append',
|
||||||
help="Only consider definitions in this file. Defaults to anything "
|
help="Only consider definitions in this file. Defaults to "
|
||||||
"in the current directory.")
|
"anything in the current directory.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--everything',
|
'--everything',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Include builtin and libc specific symbols.")
|
help="Include builtin and libc specific symbols.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--nm-types',
|
'--nm-types',
|
||||||
default=NM_TYPES,
|
default=NM_TYPES,
|
||||||
help="Type of symbols to report, this uses the same single-character "
|
help="Type of symbols to report, this uses the same "
|
||||||
"type-names emitted by nm. Defaults to %r." % NM_TYPES)
|
"single-character type-names emitted by nm. Defaults to "
|
||||||
|
"%r." % NM_TYPES)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--nm-path',
|
'--nm-path',
|
||||||
type=lambda x: x.split(),
|
type=lambda x: x.split(),
|
||||||
default=NM_PATH,
|
default=NM_PATH,
|
||||||
help="Path to the nm executable, may include flags. "
|
help="Path to the nm executable, may include flags. "
|
||||||
"Defaults to %r." % NM_PATH)
|
"Defaults to %r." % NM_PATH)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--objdump-path',
|
'--objdump-path',
|
||||||
type=lambda x: x.split(),
|
type=lambda x: x.split(),
|
||||||
default=OBJDUMP_PATH,
|
default=OBJDUMP_PATH,
|
||||||
help="Path to the objdump executable, may include flags. "
|
help="Path to the objdump executable, may include flags. "
|
||||||
"Defaults to %r." % OBJDUMP_PATH)
|
"Defaults to %r." % OBJDUMP_PATH)
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+245
-241
@@ -4,8 +4,8 @@
|
|||||||
#
|
#
|
||||||
# Example:
|
# Example:
|
||||||
# ./scripts/cov.py \
|
# ./scripts/cov.py \
|
||||||
# lfs.t.a.gcda lfs_util.t.a.gcda \
|
# lfs.t.a.gcda lfs_util.t.a.gcda \
|
||||||
# -Flfs.c -Flfs_util.c -slines
|
# -Flfs.c -Flfs_util.c -slines
|
||||||
#
|
#
|
||||||
# Copyright (c) 2022, The littlefs authors.
|
# Copyright (c) 2022, The littlefs authors.
|
||||||
# Copyright (c) 2020, Arm Limited. All rights reserved.
|
# Copyright (c) 2020, Arm Limited. All rights reserved.
|
||||||
@@ -22,6 +22,7 @@ import re
|
|||||||
import shlex
|
import shlex
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
|
|
||||||
|
|
||||||
# TODO use explode_asserts to avoid counting assert branches?
|
# TODO use explode_asserts to avoid counting assert branches?
|
||||||
# TODO use dwarf=info to find functions for inline functions?
|
# TODO use dwarf=info to find functions for inline functions?
|
||||||
|
|
||||||
@@ -128,15 +129,15 @@ class RFrac(co.namedtuple('RFrac', 'a,b')):
|
|||||||
def notes(self):
|
def notes(self):
|
||||||
t = self.a.x/self.b.x if self.b.x else 1.0
|
t = self.a.x/self.b.x if self.b.x else 1.0
|
||||||
return ['∞%' if t == +mt.inf
|
return ['∞%' if t == +mt.inf
|
||||||
else '-∞%' if t == -mt.inf
|
else '-∞%' if t == -mt.inf
|
||||||
else '%.1f%%' % (100*t)]
|
else '%.1f%%' % (100*t)]
|
||||||
|
|
||||||
def diff(self, other):
|
def diff(self, other):
|
||||||
new_a, new_b = self if self else (RInt(0), RInt(0))
|
new_a, new_b = self if self else (RInt(0), RInt(0))
|
||||||
old_a, old_b = other if other else (RInt(0), RInt(0))
|
old_a, old_b = other if other else (RInt(0), RInt(0))
|
||||||
return '%11s' % ('%s/%s' % (
|
return '%11s' % ('%s/%s' % (
|
||||||
new_a.diff(old_a).strip(),
|
new_a.diff(old_a).strip(),
|
||||||
new_b.diff(old_b).strip()))
|
new_b.diff(old_b).strip()))
|
||||||
|
|
||||||
def ratio(self, other):
|
def ratio(self, other):
|
||||||
new_a, new_b = self if self else (RInt(0), RInt(0))
|
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']
|
_fields = ['calls', 'hits', 'funcs', 'lines', 'branches']
|
||||||
_sort = ['funcs', 'lines', 'branches', 'hits', 'calls']
|
_sort = ['funcs', 'lines', 'branches', 'hits', 'calls']
|
||||||
_types = {
|
_types = {
|
||||||
'calls': RInt, 'hits': RInt,
|
'calls': RInt, 'hits': RInt,
|
||||||
'funcs': RFrac, 'lines': RFrac, 'branches': RFrac}
|
'funcs': RFrac, 'lines': RFrac, 'branches': RFrac}
|
||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
def __new__(cls, file='', function='', line=0,
|
def __new__(cls, file='', function='', line=0,
|
||||||
calls=0, hits=0, funcs=0, lines=0, branches=0):
|
calls=0, hits=0, funcs=0, lines=0, branches=0):
|
||||||
return super().__new__(cls, file, function, int(RInt(line)),
|
return super().__new__(cls, file, function, int(RInt(line)),
|
||||||
RInt(calls), RInt(hits),
|
RInt(calls), RInt(hits),
|
||||||
RFrac(funcs), RFrac(lines), RFrac(branches))
|
RFrac(funcs), RFrac(lines), RFrac(branches))
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
return CovResult(self.file, self.function, self.line,
|
return CovResult(self.file, self.function, self.line,
|
||||||
max(self.calls, other.calls),
|
max(self.calls, other.calls),
|
||||||
max(self.hits, other.hits),
|
max(self.hits, other.hits),
|
||||||
self.funcs + other.funcs,
|
self.funcs + other.funcs,
|
||||||
self.lines + other.lines,
|
self.lines + other.lines,
|
||||||
self.branches + other.branches)
|
self.branches + other.branches)
|
||||||
|
|
||||||
|
|
||||||
def openio(path, mode='r', buffering=-1):
|
def openio(path, mode='r', buffering=-1):
|
||||||
@@ -226,11 +227,11 @@ def collect(gcda_paths, *,
|
|||||||
if args.get('verbose'):
|
if args.get('verbose'):
|
||||||
print(' '.join(shlex.quote(c) for c in cmd))
|
print(' '.join(shlex.quote(c) for c in cmd))
|
||||||
proc = sp.Popen(cmd,
|
proc = sp.Popen(cmd,
|
||||||
stdout=sp.PIPE,
|
stdout=sp.PIPE,
|
||||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||||
universal_newlines=True,
|
universal_newlines=True,
|
||||||
errors='replace',
|
errors='replace',
|
||||||
close_fds=False)
|
close_fds=False)
|
||||||
data = json.load(proc.stdout)
|
data = json.load(proc.stdout)
|
||||||
proc.wait()
|
proc.wait()
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
@@ -243,8 +244,7 @@ def collect(gcda_paths, *,
|
|||||||
for file in data['files']:
|
for file in data['files']:
|
||||||
# ignore filtered sources
|
# ignore filtered sources
|
||||||
if sources is not None:
|
if sources is not None:
|
||||||
if not any(
|
if not any(os.path.abspath(file['file']) == os.path.abspath(s)
|
||||||
os.path.abspath(file['file']) == os.path.abspath(s)
|
|
||||||
for s in sources):
|
for s in sources):
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
@@ -272,11 +272,11 @@ def collect(gcda_paths, *,
|
|||||||
# go ahead and add functions, later folding will merge this if
|
# go ahead and add functions, later folding will merge this if
|
||||||
# there are other hits on this line
|
# there are other hits on this line
|
||||||
results.append(CovResult(
|
results.append(CovResult(
|
||||||
file_name, func_name, func['start_line'],
|
file_name, func_name, func['start_line'],
|
||||||
func['execution_count'], 0,
|
func['execution_count'], 0,
|
||||||
RFrac(1 if func['execution_count'] > 0 else 0, 1),
|
RFrac(1 if func['execution_count'] > 0 else 0, 1),
|
||||||
0,
|
0,
|
||||||
0))
|
0))
|
||||||
|
|
||||||
for line in file['lines']:
|
for line in file['lines']:
|
||||||
func_name = line.get('function_name', '(inlined)')
|
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
|
# go ahead and add lines, later folding will merge this if
|
||||||
# there are other hits on this line
|
# there are other hits on this line
|
||||||
results.append(CovResult(
|
results.append(CovResult(
|
||||||
file_name, func_name, line['line_number'],
|
file_name, func_name, line['line_number'],
|
||||||
0, line['count'],
|
0, line['count'],
|
||||||
0,
|
0,
|
||||||
RFrac(1 if line['count'] > 0 else 0, 1),
|
RFrac(1 if line['count'] > 0 else 0, 1),
|
||||||
RFrac(
|
RFrac(
|
||||||
sum(1 if branch['count'] > 0 else 0
|
sum(1 if branch['count'] > 0 else 0
|
||||||
for branch in line['branches']),
|
for branch in line['branches']),
|
||||||
len(line['branches']))))
|
len(line['branches']))))
|
||||||
|
|
||||||
return results
|
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)):
|
for k in it.chain(by or [], (k for k, _ in defines)):
|
||||||
if k not in Result._by and k not in Result._fields:
|
if k not in Result._by and k not in Result._fields:
|
||||||
print("error: could not find field %r?" % k,
|
print("error: could not find field %r?" % k,
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# filter by matching defines
|
# filter by matching defines
|
||||||
@@ -356,52 +356,55 @@ def table(Result, results, diff_results=None, *,
|
|||||||
|
|
||||||
# organize by name
|
# organize by name
|
||||||
table = {
|
table = {
|
||||||
','.join(str(getattr(r, k) or '') for k in by): r
|
','.join(str(getattr(r, k) or '') for k in by): r
|
||||||
for r in results}
|
for r in results}
|
||||||
diff_table = {
|
diff_table = {
|
||||||
','.join(str(getattr(r, k) or '') for k in by): r
|
','.join(str(getattr(r, k) or '') for k in by): r
|
||||||
for r in diff_results or []}
|
for r in diff_results or []}
|
||||||
names = [name
|
names = [name
|
||||||
for name in table.keys() | diff_table.keys()
|
for name in table.keys() | diff_table.keys()
|
||||||
if diff_results is None
|
if diff_results is None
|
||||||
or all_
|
or all_
|
||||||
or any(
|
or any(
|
||||||
types[k].ratio(
|
types[k].ratio(
|
||||||
getattr(table.get(name), k, None),
|
getattr(table.get(name), k, None),
|
||||||
getattr(diff_table.get(name), k, None))
|
getattr(diff_table.get(name), k, None))
|
||||||
for k in fields)]
|
for k in fields)]
|
||||||
|
|
||||||
# sort again, now with diff info, note that python's sort is stable
|
# sort again, now with diff info, note that python's sort is stable
|
||||||
names.sort()
|
names.sort()
|
||||||
if diff_results is not None:
|
if diff_results is not None:
|
||||||
names.sort(key=lambda n: tuple(
|
names.sort(
|
||||||
types[k].ratio(
|
key=lambda n: tuple(
|
||||||
getattr(table.get(n), k, None),
|
types[k].ratio(
|
||||||
getattr(diff_table.get(n), k, None))
|
getattr(table.get(n), k, None),
|
||||||
for k in fields),
|
getattr(diff_table.get(n), k, None))
|
||||||
reverse=True)
|
for k in fields),
|
||||||
|
reverse=True)
|
||||||
if sort:
|
if sort:
|
||||||
for k, reverse in reversed(sort):
|
for k, reverse in reversed(sort):
|
||||||
names.sort(
|
names.sort(
|
||||||
key=lambda n: tuple(
|
key=lambda n: tuple(
|
||||||
(getattr(table[n], k),)
|
(getattr(table[n], k),)
|
||||||
if getattr(table.get(n), k, None) is not None else ()
|
if getattr(table.get(n), k, None) is not None
|
||||||
for k in ([k] if k else [
|
else ()
|
||||||
k for k in Result._sort if k in fields])),
|
for k in (
|
||||||
reverse=reverse ^ (not k or k in Result._fields))
|
[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
|
# build up our lines
|
||||||
lines = []
|
lines = []
|
||||||
|
|
||||||
# header
|
# header
|
||||||
header = [
|
header = ['%s%s' % (
|
||||||
'%s%s' % (
|
','.join(by),
|
||||||
','.join(by),
|
' (%d added, %d removed)' % (
|
||||||
' (%d added, %d removed)' % (
|
sum(1 for n in table if n not in diff_table),
|
||||||
sum(1 for n in table if n not in diff_table),
|
sum(1 for n in diff_table if n not in table))
|
||||||
sum(1 for n in diff_table if n not in table))
|
if diff_results is not None and not percent else '')
|
||||||
if diff_results is not None and not percent else '')
|
|
||||||
if not summary else '']
|
if not summary else '']
|
||||||
if diff_results is None:
|
if diff_results is None:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
@@ -424,43 +427,43 @@ def table(Result, results, diff_results=None, *,
|
|||||||
if diff_results is None:
|
if diff_results is None:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(getattr(r, k).table(),
|
(getattr(r, k).table(),
|
||||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
elif percent:
|
elif percent:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(getattr(r, k).table()
|
(getattr(r, k).table()
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none,
|
else types[k].none,
|
||||||
(lambda t: ['+∞%'] if t == +mt.inf
|
(lambda t: ['+∞%'] if t == +mt.inf
|
||||||
else ['-∞%'] if t == -mt.inf
|
else ['-∞%'] if t == -mt.inf
|
||||||
else ['%+.1f%%' % (100*t)])(
|
else ['%+.1f%%' % (100*t)])(
|
||||||
types[k].ratio(
|
types[k].ratio(
|
||||||
getattr(r, k, None),
|
getattr(r, k, None),
|
||||||
getattr(diff_r, k, None)))))
|
getattr(diff_r, k, None)))))
|
||||||
else:
|
else:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(getattr(diff_r, k).table()
|
entry.append(getattr(diff_r, k).table()
|
||||||
if getattr(diff_r, k, None) is not None
|
if getattr(diff_r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(getattr(r, k).table()
|
entry.append(getattr(r, k).table()
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(types[k].diff(
|
(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(
|
|
||||||
getattr(r, k, None),
|
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
|
return entry
|
||||||
|
|
||||||
# entries
|
# entries
|
||||||
@@ -483,8 +486,8 @@ def table(Result, results, diff_results=None, *,
|
|||||||
|
|
||||||
# homogenize
|
# homogenize
|
||||||
lines = [
|
lines = [
|
||||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||||
for line in lines]
|
for line in lines]
|
||||||
|
|
||||||
# find the best widths, note that column 0 contains the names and is
|
# find the best widths, note that column 0 contains the names and is
|
||||||
# handled a bit differently
|
# handled a bit differently
|
||||||
@@ -498,11 +501,11 @@ def table(Result, results, diff_results=None, *,
|
|||||||
# print our table
|
# print our table
|
||||||
for line in lines:
|
for line in lines:
|
||||||
print('%-*s %s' % (
|
print('%-*s %s' % (
|
||||||
widths[0], line[0][0],
|
widths[0], line[0][0],
|
||||||
' '.join('%*s%-*s' % (
|
' '.join('%*s%-*s' % (
|
||||||
widths[i], x[0],
|
widths[i], x[0],
|
||||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||||
for i, x in enumerate(line[1:], 1))))
|
for i, x in enumerate(line[1:], 1))))
|
||||||
|
|
||||||
|
|
||||||
def annotate(Result, results, *,
|
def annotate(Result, results, *,
|
||||||
@@ -529,14 +532,14 @@ def annotate(Result, results, *,
|
|||||||
or (branches and r.branches.a < r.branches.b)):
|
or (branches and r.branches.a < r.branches.b)):
|
||||||
if last is not None and line - last.stop <= args['context']:
|
if last is not None and line - last.stop <= args['context']:
|
||||||
last = range(
|
last = range(
|
||||||
last.start,
|
last.start,
|
||||||
line+1+args['context'])
|
line+1+args['context'])
|
||||||
else:
|
else:
|
||||||
if last is not None:
|
if last is not None:
|
||||||
spans.append((last, func))
|
spans.append((last, func))
|
||||||
last = range(
|
last = range(
|
||||||
line-args['context'],
|
line-args['context'],
|
||||||
line+1+args['context'])
|
line+1+args['context'])
|
||||||
func = r.function
|
func = r.function
|
||||||
if last is not None:
|
if last is not None:
|
||||||
spans.append((last, func))
|
spans.append((last, func))
|
||||||
@@ -552,11 +555,11 @@ def annotate(Result, results, *,
|
|||||||
if skipped:
|
if skipped:
|
||||||
skipped = False
|
skipped = False
|
||||||
print('%s@@ %s:%d: %s @@%s' % (
|
print('%s@@ %s:%d: %s @@%s' % (
|
||||||
'\x1b[36m' if args['color'] else '',
|
'\x1b[36m' if args['color'] else '',
|
||||||
path,
|
path,
|
||||||
i+1,
|
i+1,
|
||||||
next(iter(f for _, f in spans)),
|
next(iter(f for _, f in spans)),
|
||||||
'\x1b[m' if args['color'] else ''))
|
'\x1b[m' if args['color'] else ''))
|
||||||
|
|
||||||
# build line
|
# build line
|
||||||
if line.endswith('\n'):
|
if line.endswith('\n'):
|
||||||
@@ -565,11 +568,11 @@ def annotate(Result, results, *,
|
|||||||
if i+1 in table:
|
if i+1 in table:
|
||||||
r = table[i+1]
|
r = table[i+1]
|
||||||
line = '%-*s // %s hits%s' % (
|
line = '%-*s // %s hits%s' % (
|
||||||
args['width'],
|
args['width'],
|
||||||
line,
|
line,
|
||||||
r.hits,
|
r.hits,
|
||||||
', %s branches' % (r.branches,)
|
', %s branches' % (r.branches,)
|
||||||
if int(r.branches.b) else '')
|
if int(r.branches.b) else '')
|
||||||
|
|
||||||
if args['color']:
|
if args['color']:
|
||||||
if lines and int(r.hits) == 0:
|
if lines and int(r.hits) == 0:
|
||||||
@@ -612,11 +615,11 @@ def main(gcda_paths, *,
|
|||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
results.append(CovResult(
|
results.append(CovResult(
|
||||||
**{k: r[k] for k in CovResult._by
|
**{k: r[k] for k in CovResult._by
|
||||||
if k in r and r[k].strip()},
|
if k in r and r[k].strip()},
|
||||||
**{k: r[k]
|
**{k: r[k]
|
||||||
for k in CovResult._fields
|
for k in CovResult._fields
|
||||||
if k in r and r[k].strip()}))
|
if k in r and r[k].strip()}))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -628,25 +631,27 @@ def main(gcda_paths, *,
|
|||||||
if sort:
|
if sort:
|
||||||
for k, reverse in reversed(sort):
|
for k, reverse in reversed(sort):
|
||||||
results.sort(
|
results.sort(
|
||||||
key=lambda r: tuple(
|
key=lambda r: tuple(
|
||||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||||
for k in ([k] if k else CovResult._sort)),
|
for k in ([k] if k else CovResult._sort)),
|
||||||
reverse=reverse ^ (not k or k in CovResult._fields))
|
reverse=reverse ^ (not k or k in CovResult._fields))
|
||||||
|
|
||||||
# write results to CSV
|
# write results to CSV
|
||||||
if args.get('output'):
|
if args.get('output'):
|
||||||
with openio(args['output'], 'w') as f:
|
with openio(args['output'], 'w') as f:
|
||||||
writer = csv.DictWriter(f,
|
writer = csv.DictWriter(f,
|
||||||
(by if by is not None else CovResult._by)
|
(by if by is not None else CovResult._by)
|
||||||
+ [k for k in (
|
+ [k for k in (
|
||||||
fields if fields is not None else CovResult._fields)])
|
fields if fields is not None
|
||||||
|
else CovResult._fields)])
|
||||||
writer.writeheader()
|
writer.writeheader()
|
||||||
for r in results:
|
for r in results:
|
||||||
writer.writerow(
|
writer.writerow(
|
||||||
{k: getattr(r, k) for k in (
|
{k: getattr(r, k) for k in (
|
||||||
by if by is not None else CovResult._by)}
|
by if by is not None else CovResult._by)}
|
||||||
| {k: getattr(r, k) for k in (
|
| {k: getattr(r, k) for k in (
|
||||||
fields if fields is not None else CovResult._fields)})
|
fields if fields is not None
|
||||||
|
else CovResult._fields)})
|
||||||
|
|
||||||
# find previous results?
|
# find previous results?
|
||||||
if args.get('diff'):
|
if args.get('diff'):
|
||||||
@@ -664,19 +669,17 @@ def main(gcda_paths, *,
|
|||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
diff_results.append(CovResult(
|
diff_results.append(CovResult(
|
||||||
**{k: r[k] for k in CovResult._by
|
**{k: r[k] for k in CovResult._by
|
||||||
if k in r and r[k].strip()},
|
if k in r and r[k].strip()},
|
||||||
**{k: r[k]
|
**{k: r[k] for k in CovResult._fields
|
||||||
for k in CovResult._fields
|
if k in r and r[k].strip()}))
|
||||||
if k in r and r[k].strip()}))
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# fold
|
# fold
|
||||||
diff_results = fold(CovResult, diff_results,
|
diff_results = fold(CovResult, diff_results, by=by, defines=defines)
|
||||||
by=by, defines=defines)
|
|
||||||
|
|
||||||
# print table
|
# print table
|
||||||
if not args.get('quiet'):
|
if not args.get('quiet'):
|
||||||
@@ -688,13 +691,13 @@ def main(gcda_paths, *,
|
|||||||
else:
|
else:
|
||||||
# print table
|
# print table
|
||||||
table(CovResult, results,
|
table(CovResult, results,
|
||||||
diff_results if args.get('diff') else None,
|
diff_results if args.get('diff') else None,
|
||||||
by=by if by is not None else ['function'],
|
by=by if by is not None else ['function'],
|
||||||
fields=fields if fields is not None
|
fields=fields if fields is not None
|
||||||
else ['lines', 'branches'] if not hits
|
else ['lines', 'branches'] if not hits
|
||||||
else ['calls', 'hits'],
|
else ['calls', 'hits'],
|
||||||
sort=sort,
|
sort=sort,
|
||||||
**args)
|
**args)
|
||||||
|
|
||||||
# catch lack of coverage
|
# catch lack of coverage
|
||||||
if args.get('error_on_lines') and any(
|
if args.get('error_on_lines') and any(
|
||||||
@@ -709,132 +712,133 @@ if __name__ == "__main__":
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Find coverage info after running tests.",
|
description="Find coverage info after running tests.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'gcda_paths',
|
'gcda_paths',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
help="Input *.gcda files.")
|
help="Input *.gcda files.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-v', '--verbose',
|
'-v', '--verbose',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Output commands that run behind the scenes.")
|
help="Output commands that run behind the scenes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-q', '--quiet',
|
'-q', '--quiet',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Don't show anything, useful with -o.")
|
help="Don't show anything, useful with -o.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-o', '--output',
|
'-o', '--output',
|
||||||
help="Specify CSV file to store results.")
|
help="Specify CSV file to store results.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-u', '--use',
|
'-u', '--use',
|
||||||
help="Don't parse anything, use this CSV file.")
|
help="Don't parse anything, use this CSV file.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-d', '--diff',
|
'-d', '--diff',
|
||||||
help="Specify CSV file to diff against.")
|
help="Specify CSV file to diff against.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-a', '--all',
|
'-a', '--all',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show all, not just the ones that changed.")
|
help="Show all, not just the ones that changed.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-p', '--percent',
|
'-p', '--percent',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Only show percentage change, not a full diff.")
|
help="Only show percentage change, not a full diff.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--by',
|
'-b', '--by',
|
||||||
action='append',
|
action='append',
|
||||||
choices=CovResult._by,
|
choices=CovResult._by,
|
||||||
help="Group by this field.")
|
help="Group by this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-f', '--field',
|
'-f', '--field',
|
||||||
dest='fields',
|
dest='fields',
|
||||||
action='append',
|
action='append',
|
||||||
choices=CovResult._fields,
|
choices=CovResult._fields,
|
||||||
help="Show this field.")
|
help="Show this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-D', '--define',
|
'-D', '--define',
|
||||||
dest='defines',
|
dest='defines',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs: (
|
lambda k, vs: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
{v.strip() for v in vs.split(',')})
|
{v.strip() for v in vs.split(',')})
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Only include results where this field is this value.")
|
help="Only include results where this field is this value.")
|
||||||
class AppendSort(argparse.Action):
|
class AppendSort(argparse.Action):
|
||||||
def __call__(self, parser, namespace, value, option):
|
def __call__(self, parser, namespace, value, option):
|
||||||
if namespace.sort is None:
|
if namespace.sort is None:
|
||||||
namespace.sort = []
|
namespace.sort = []
|
||||||
namespace.sort.append((value, True if option == '-S' else False))
|
namespace.sort.append((value, True if option == '-S' else False))
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-s', '--sort',
|
'-s', '--sort',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
action=AppendSort,
|
action=AppendSort,
|
||||||
help="Sort by this field.")
|
help="Sort by this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-S', '--reverse-sort',
|
'-S', '--reverse-sort',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
action=AppendSort,
|
action=AppendSort,
|
||||||
help="Sort by this field, but backwards.")
|
help="Sort by this field, but backwards.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-Y', '--summary',
|
'-Y', '--summary',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Only show the total.")
|
help="Only show the total.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-F', '--source',
|
'-F', '--source',
|
||||||
dest='sources',
|
dest='sources',
|
||||||
action='append',
|
action='append',
|
||||||
help="Only consider definitions in this file. Defaults to anything "
|
help="Only consider definitions in this file. Defaults to "
|
||||||
"in the current directory.")
|
"anything in the current directory.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--everything',
|
'--everything',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Include builtin and libc specific symbols.")
|
help="Include builtin and libc specific symbols.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--hits',
|
'--hits',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show total hits instead of coverage.")
|
help="Show total hits instead of coverage.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-A', '--annotate',
|
'-A', '--annotate',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show source files annotated with coverage info.")
|
help="Show source files annotated with coverage info.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-L', '--lines',
|
'-L', '--lines',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show uncovered lines.")
|
help="Show uncovered lines.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-B', '--branches',
|
'-B', '--branches',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show uncovered branches.")
|
help="Show uncovered branches.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-C', '--context',
|
'-C', '--context',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
default=3,
|
default=3,
|
||||||
help="Show n additional lines of context. Defaults to 3.")
|
help="Show n additional lines of context. Defaults to 3.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-W', '--width',
|
'-W', '--width',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
default=80,
|
default=80,
|
||||||
help="Assume source is styled with this many columns. Defaults to 80.")
|
help="Assume source is styled with this many columns. Defaults "
|
||||||
|
"to 80.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--color',
|
'--color',
|
||||||
choices=['never', 'always', 'auto'],
|
choices=['never', 'always', 'auto'],
|
||||||
default='auto',
|
default='auto',
|
||||||
help="When to use terminal colors. Defaults to 'auto'.")
|
help="When to use terminal colors. Defaults to 'auto'.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-e', '--error-on-lines',
|
'-e', '--error-on-lines',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Error if any lines are not covered.")
|
help="Error if any lines are not covered.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-E', '--error-on-branches',
|
'-E', '--error-on-branches',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Error if any branches are not covered.")
|
help="Error if any branches are not covered.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--gcov-path',
|
'--gcov-path',
|
||||||
default=GCOV_PATH,
|
default=GCOV_PATH,
|
||||||
type=lambda x: x.split(),
|
type=lambda x: x.split(),
|
||||||
help="Path to the gcov executable, may include paths. "
|
help="Path to the gcov executable, may include paths. "
|
||||||
"Defaults to %r." % GCOV_PATH)
|
"Defaults to %r." % GCOV_PATH)
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+14
-13
@@ -57,24 +57,25 @@ def main(paths, **args):
|
|||||||
else:
|
else:
|
||||||
print('%08x' % crc)
|
print('%08x' % crc)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Calculates crc32cs.",
|
description="Calculates crc32cs.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'paths',
|
'paths',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
help="Paths to read. Reads stdin by default.")
|
help="Paths to read. Reads stdin by default.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-x', '--hex',
|
'-x', '--hex',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Interpret as a sequence of hex bytes.")
|
help="Interpret as a sequence of hex bytes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-s', '--string',
|
'-s', '--string',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Interpret as strings.")
|
help="Interpret as strings.")
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+207
-202
@@ -115,11 +115,11 @@ class DataResult(co.namedtuple('DataResult', [
|
|||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
def __new__(cls, file='', function='', size=0):
|
def __new__(cls, file='', function='', size=0):
|
||||||
return super().__new__(cls, file, function,
|
return super().__new__(cls, file, function,
|
||||||
RInt(size))
|
RInt(size))
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
return DataResult(self.file, self.function,
|
return DataResult(self.file, self.function,
|
||||||
self.size + other.size)
|
self.size + other.size)
|
||||||
|
|
||||||
|
|
||||||
def openio(path, mode='r', buffering=-1):
|
def openio(path, mode='r', buffering=-1):
|
||||||
@@ -140,18 +140,18 @@ def collect(obj_paths, *,
|
|||||||
everything=False,
|
everything=False,
|
||||||
**args):
|
**args):
|
||||||
size_pattern = re.compile(
|
size_pattern = re.compile(
|
||||||
'^(?P<size>[0-9a-fA-F]+)' +
|
'^(?P<size>[0-9a-fA-F]+)'
|
||||||
' (?P<type>[%s])' % re.escape(nm_types) +
|
+ ' (?P<type>[%s])' % re.escape(nm_types)
|
||||||
' (?P<func>.+?)$')
|
+ ' (?P<func>.+?)$')
|
||||||
line_pattern = re.compile(
|
line_pattern = re.compile(
|
||||||
'^\s+(?P<no>[0-9]+)'
|
'^\s+(?P<no>[0-9]+)'
|
||||||
'(?:\s+(?P<dir>[0-9]+))?'
|
'(?:\s+(?P<dir>[0-9]+))?'
|
||||||
'\s+.*'
|
'\s+.*'
|
||||||
'\s+(?P<path>[^\s]+)$')
|
'\s+(?P<path>[^\s]+)$')
|
||||||
info_pattern = re.compile(
|
info_pattern = re.compile(
|
||||||
'^(?:.*(?P<tag>DW_TAG_[a-z_]+).*'
|
'^(?:.*(?P<tag>DW_TAG_[a-z_]+).*'
|
||||||
'|.*DW_AT_name.*:\s*(?P<name>[^:\s]+)\s*'
|
'|.*DW_AT_name.*:\s*(?P<name>[^:\s]+)\s*'
|
||||||
'|.*DW_AT_decl_file.*:\s*(?P<file>[0-9]+)\s*)$')
|
'|.*DW_AT_decl_file.*:\s*(?P<file>[0-9]+)\s*)$')
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
for path in obj_paths:
|
for path in obj_paths:
|
||||||
@@ -165,11 +165,11 @@ def collect(obj_paths, *,
|
|||||||
if args.get('verbose'):
|
if args.get('verbose'):
|
||||||
print(' '.join(shlex.quote(c) for c in cmd))
|
print(' '.join(shlex.quote(c) for c in cmd))
|
||||||
proc = sp.Popen(cmd,
|
proc = sp.Popen(cmd,
|
||||||
stdout=sp.PIPE,
|
stdout=sp.PIPE,
|
||||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||||
universal_newlines=True,
|
universal_newlines=True,
|
||||||
errors='replace',
|
errors='replace',
|
||||||
close_fds=False)
|
close_fds=False)
|
||||||
for line in proc.stdout:
|
for line in proc.stdout:
|
||||||
m = size_pattern.match(line)
|
m = size_pattern.match(line)
|
||||||
if m:
|
if m:
|
||||||
@@ -178,8 +178,8 @@ def collect(obj_paths, *,
|
|||||||
if not everything and func.startswith('__'):
|
if not everything and func.startswith('__'):
|
||||||
continue
|
continue
|
||||||
results_.append(DataResult(
|
results_.append(DataResult(
|
||||||
file, func,
|
file, func,
|
||||||
int(m.group('size'), 16)))
|
int(m.group('size'), 16)))
|
||||||
proc.wait()
|
proc.wait()
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
if not args.get('verbose'):
|
if not args.get('verbose'):
|
||||||
@@ -196,11 +196,11 @@ def collect(obj_paths, *,
|
|||||||
if args.get('verbose'):
|
if args.get('verbose'):
|
||||||
print(' '.join(shlex.quote(c) for c in cmd))
|
print(' '.join(shlex.quote(c) for c in cmd))
|
||||||
proc = sp.Popen(cmd,
|
proc = sp.Popen(cmd,
|
||||||
stdout=sp.PIPE,
|
stdout=sp.PIPE,
|
||||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||||
universal_newlines=True,
|
universal_newlines=True,
|
||||||
errors='replace',
|
errors='replace',
|
||||||
close_fds=False)
|
close_fds=False)
|
||||||
for line in proc.stdout:
|
for line in proc.stdout:
|
||||||
# note that files contain references to dirs, which we
|
# note that files contain references to dirs, which we
|
||||||
# dereference as soon as we see them as each file table follows a
|
# 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'))
|
dir = int(m.group('dir'))
|
||||||
if dir in dirs:
|
if dir in dirs:
|
||||||
files[int(m.group('no'))] = os.path.join(
|
files[int(m.group('no'))] = os.path.join(
|
||||||
dirs[dir],
|
dirs[dir],
|
||||||
m.group('path'))
|
m.group('path'))
|
||||||
else:
|
else:
|
||||||
files[int(m.group('no'))] = m.group('path')
|
files[int(m.group('no'))] = m.group('path')
|
||||||
proc.wait()
|
proc.wait()
|
||||||
@@ -241,11 +241,11 @@ def collect(obj_paths, *,
|
|||||||
if args.get('verbose'):
|
if args.get('verbose'):
|
||||||
print(' '.join(shlex.quote(c) for c in cmd))
|
print(' '.join(shlex.quote(c) for c in cmd))
|
||||||
proc = sp.Popen(cmd,
|
proc = sp.Popen(cmd,
|
||||||
stdout=sp.PIPE,
|
stdout=sp.PIPE,
|
||||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||||
universal_newlines=True,
|
universal_newlines=True,
|
||||||
errors='replace',
|
errors='replace',
|
||||||
close_fds=False)
|
close_fds=False)
|
||||||
for line in proc.stdout:
|
for line in proc.stdout:
|
||||||
# state machine here to find definitions
|
# state machine here to find definitions
|
||||||
m = info_pattern.match(line)
|
m = info_pattern.match(line)
|
||||||
@@ -279,17 +279,16 @@ def collect(obj_paths, *,
|
|||||||
file = defs[r.function]
|
file = defs[r.function]
|
||||||
else:
|
else:
|
||||||
_, file = max(
|
_, file = max(
|
||||||
defs.items(),
|
defs.items(),
|
||||||
key=lambda d: difflib.SequenceMatcher(None,
|
key=lambda d: difflib.SequenceMatcher(None,
|
||||||
d[0],
|
d[0],
|
||||||
r.function, False).ratio())
|
r.function, False).ratio())
|
||||||
else:
|
else:
|
||||||
file = r.file
|
file = r.file
|
||||||
|
|
||||||
# ignore filtered sources
|
# ignore filtered sources
|
||||||
if sources is not None:
|
if sources is not None:
|
||||||
if not any(
|
if not any(os.path.abspath(file) == os.path.abspath(s)
|
||||||
os.path.abspath(file) == os.path.abspath(s)
|
|
||||||
for s in sources):
|
for s in sources):
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
@@ -319,7 +318,7 @@ def fold(Result, results, by=None, defines=[]):
|
|||||||
for k in it.chain(by or [], (k for k, _ in 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:
|
if k not in Result._by and k not in Result._fields:
|
||||||
print("error: could not find field %r?" % k,
|
print("error: could not find field %r?" % k,
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# filter by matching defines
|
# filter by matching defines
|
||||||
@@ -368,52 +367,55 @@ def table(Result, results, diff_results=None, *,
|
|||||||
|
|
||||||
# organize by name
|
# organize by name
|
||||||
table = {
|
table = {
|
||||||
','.join(str(getattr(r, k) or '') for k in by): r
|
','.join(str(getattr(r, k) or '') for k in by): r
|
||||||
for r in results}
|
for r in results}
|
||||||
diff_table = {
|
diff_table = {
|
||||||
','.join(str(getattr(r, k) or '') for k in by): r
|
','.join(str(getattr(r, k) or '') for k in by): r
|
||||||
for r in diff_results or []}
|
for r in diff_results or []}
|
||||||
names = [name
|
names = [name
|
||||||
for name in table.keys() | diff_table.keys()
|
for name in table.keys() | diff_table.keys()
|
||||||
if diff_results is None
|
if diff_results is None
|
||||||
or all_
|
or all_
|
||||||
or any(
|
or any(
|
||||||
types[k].ratio(
|
types[k].ratio(
|
||||||
getattr(table.get(name), k, None),
|
getattr(table.get(name), k, None),
|
||||||
getattr(diff_table.get(name), k, None))
|
getattr(diff_table.get(name), k, None))
|
||||||
for k in fields)]
|
for k in fields)]
|
||||||
|
|
||||||
# sort again, now with diff info, note that python's sort is stable
|
# sort again, now with diff info, note that python's sort is stable
|
||||||
names.sort()
|
names.sort()
|
||||||
if diff_results is not None:
|
if diff_results is not None:
|
||||||
names.sort(key=lambda n: tuple(
|
names.sort(
|
||||||
types[k].ratio(
|
key=lambda n: tuple(
|
||||||
getattr(table.get(n), k, None),
|
types[k].ratio(
|
||||||
getattr(diff_table.get(n), k, None))
|
getattr(table.get(n), k, None),
|
||||||
for k in fields),
|
getattr(diff_table.get(n), k, None))
|
||||||
reverse=True)
|
for k in fields),
|
||||||
|
reverse=True)
|
||||||
if sort:
|
if sort:
|
||||||
for k, reverse in reversed(sort):
|
for k, reverse in reversed(sort):
|
||||||
names.sort(
|
names.sort(
|
||||||
key=lambda n: tuple(
|
key=lambda n: tuple(
|
||||||
(getattr(table[n], k),)
|
(getattr(table[n], k),)
|
||||||
if getattr(table.get(n), k, None) is not None else ()
|
if getattr(table.get(n), k, None) is not None
|
||||||
for k in ([k] if k else [
|
else ()
|
||||||
k for k in Result._sort if k in fields])),
|
for k in (
|
||||||
reverse=reverse ^ (not k or k in Result._fields))
|
[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
|
# build up our lines
|
||||||
lines = []
|
lines = []
|
||||||
|
|
||||||
# header
|
# header
|
||||||
header = [
|
header = ['%s%s' % (
|
||||||
'%s%s' % (
|
','.join(by),
|
||||||
','.join(by),
|
' (%d added, %d removed)' % (
|
||||||
' (%d added, %d removed)' % (
|
sum(1 for n in table if n not in diff_table),
|
||||||
sum(1 for n in table if n not in diff_table),
|
sum(1 for n in diff_table if n not in table))
|
||||||
sum(1 for n in diff_table if n not in table))
|
if diff_results is not None and not percent else '')
|
||||||
if diff_results is not None and not percent else '')
|
|
||||||
if not summary else '']
|
if not summary else '']
|
||||||
if diff_results is None:
|
if diff_results is None:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
@@ -436,43 +438,43 @@ def table(Result, results, diff_results=None, *,
|
|||||||
if diff_results is None:
|
if diff_results is None:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(getattr(r, k).table(),
|
(getattr(r, k).table(),
|
||||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
elif percent:
|
elif percent:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(getattr(r, k).table()
|
(getattr(r, k).table()
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none,
|
else types[k].none,
|
||||||
(lambda t: ['+∞%'] if t == +mt.inf
|
(lambda t: ['+∞%'] if t == +mt.inf
|
||||||
else ['-∞%'] if t == -mt.inf
|
else ['-∞%'] if t == -mt.inf
|
||||||
else ['%+.1f%%' % (100*t)])(
|
else ['%+.1f%%' % (100*t)])(
|
||||||
types[k].ratio(
|
types[k].ratio(
|
||||||
getattr(r, k, None),
|
getattr(r, k, None),
|
||||||
getattr(diff_r, k, None)))))
|
getattr(diff_r, k, None)))))
|
||||||
else:
|
else:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(getattr(diff_r, k).table()
|
entry.append(getattr(diff_r, k).table()
|
||||||
if getattr(diff_r, k, None) is not None
|
if getattr(diff_r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(getattr(r, k).table()
|
entry.append(getattr(r, k).table()
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(types[k].diff(
|
(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(
|
|
||||||
getattr(r, k, None),
|
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
|
return entry
|
||||||
|
|
||||||
# entries
|
# entries
|
||||||
@@ -495,8 +497,8 @@ def table(Result, results, diff_results=None, *,
|
|||||||
|
|
||||||
# homogenize
|
# homogenize
|
||||||
lines = [
|
lines = [
|
||||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||||
for line in lines]
|
for line in lines]
|
||||||
|
|
||||||
# find the best widths, note that column 0 contains the names and is
|
# find the best widths, note that column 0 contains the names and is
|
||||||
# handled a bit differently
|
# handled a bit differently
|
||||||
@@ -510,11 +512,11 @@ def table(Result, results, diff_results=None, *,
|
|||||||
# print our table
|
# print our table
|
||||||
for line in lines:
|
for line in lines:
|
||||||
print('%-*s %s' % (
|
print('%-*s %s' % (
|
||||||
widths[0], line[0][0],
|
widths[0], line[0][0],
|
||||||
' '.join('%*s%-*s' % (
|
' '.join('%*s%-*s' % (
|
||||||
widths[i], x[0],
|
widths[i], x[0],
|
||||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||||
for i, x in enumerate(line[1:], 1))))
|
for i, x in enumerate(line[1:], 1))))
|
||||||
|
|
||||||
|
|
||||||
def main(obj_paths, *,
|
def main(obj_paths, *,
|
||||||
@@ -537,10 +539,10 @@ def main(obj_paths, *,
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
results.append(DataResult(
|
results.append(DataResult(
|
||||||
**{k: r[k] for k in DataResult._by
|
**{k: r[k] for k in DataResult._by
|
||||||
if k in r and r[k].strip()},
|
if k in r and r[k].strip()},
|
||||||
**{k: r[k] for k in DataResult._fields
|
**{k: r[k] for k in DataResult._fields
|
||||||
if k in r and r[k].strip()}))
|
if k in r and r[k].strip()}))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -552,25 +554,27 @@ def main(obj_paths, *,
|
|||||||
if sort:
|
if sort:
|
||||||
for k, reverse in reversed(sort):
|
for k, reverse in reversed(sort):
|
||||||
results.sort(
|
results.sort(
|
||||||
key=lambda r: tuple(
|
key=lambda r: tuple(
|
||||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||||
for k in ([k] if k else DataResult._sort)),
|
for k in ([k] if k else DataResult._sort)),
|
||||||
reverse=reverse ^ (not k or k in DataResult._fields))
|
reverse=reverse ^ (not k or k in DataResult._fields))
|
||||||
|
|
||||||
# write results to CSV
|
# write results to CSV
|
||||||
if args.get('output'):
|
if args.get('output'):
|
||||||
with openio(args['output'], 'w') as f:
|
with openio(args['output'], 'w') as f:
|
||||||
writer = csv.DictWriter(f,
|
writer = csv.DictWriter(f,
|
||||||
(by if by is not None else DataResult._by)
|
(by if by is not None else DataResult._by)
|
||||||
+ [k for k in (
|
+ [k for k in (
|
||||||
fields if fields is not None else DataResult._fields)])
|
fields if fields is not None
|
||||||
|
else DataResult._fields)])
|
||||||
writer.writeheader()
|
writer.writeheader()
|
||||||
for r in results:
|
for r in results:
|
||||||
writer.writerow(
|
writer.writerow(
|
||||||
{k: getattr(r, k) for k in (
|
{k: getattr(r, k) for k in (
|
||||||
by if by is not None else DataResult._by)}
|
by if by is not None else DataResult._by)}
|
||||||
| {k: getattr(r, k) for k in (
|
| {k: getattr(r, k) for k in (
|
||||||
fields if fields is not None else DataResult._fields)})
|
fields if fields is not None
|
||||||
|
else DataResult._fields)})
|
||||||
|
|
||||||
# find previous results?
|
# find previous results?
|
||||||
if args.get('diff'):
|
if args.get('diff'):
|
||||||
@@ -588,10 +592,10 @@ def main(obj_paths, *,
|
|||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
diff_results.append(DataResult(
|
diff_results.append(DataResult(
|
||||||
**{k: r[k] for k in DataResult._by
|
**{k: r[k] for k in DataResult._by
|
||||||
if k in r and r[k].strip()},
|
if k in r and r[k].strip()},
|
||||||
**{k: r[k] for k in DataResult._fields
|
**{k: r[k] for k in DataResult._fields
|
||||||
if k in r and r[k].strip()}))
|
if k in r and r[k].strip()}))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
@@ -603,115 +607,116 @@ def main(obj_paths, *,
|
|||||||
# print table
|
# print table
|
||||||
if not args.get('quiet'):
|
if not args.get('quiet'):
|
||||||
table(DataResult, results,
|
table(DataResult, results,
|
||||||
diff_results if args.get('diff') else None,
|
diff_results if args.get('diff') else None,
|
||||||
by=by if by is not None else ['function'],
|
by=by if by is not None else ['function'],
|
||||||
fields=fields,
|
fields=fields,
|
||||||
sort=sort,
|
sort=sort,
|
||||||
**args)
|
**args)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Find data size at the function level.",
|
description="Find data size at the function level.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'obj_paths',
|
'obj_paths',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
help="Input *.o files.")
|
help="Input *.o files.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-v', '--verbose',
|
'-v', '--verbose',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Output commands that run behind the scenes.")
|
help="Output commands that run behind the scenes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-q', '--quiet',
|
'-q', '--quiet',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Don't show anything, useful with -o.")
|
help="Don't show anything, useful with -o.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-o', '--output',
|
'-o', '--output',
|
||||||
help="Specify CSV file to store results.")
|
help="Specify CSV file to store results.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-u', '--use',
|
'-u', '--use',
|
||||||
help="Don't parse anything, use this CSV file.")
|
help="Don't parse anything, use this CSV file.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-d', '--diff',
|
'-d', '--diff',
|
||||||
help="Specify CSV file to diff against.")
|
help="Specify CSV file to diff against.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-a', '--all',
|
'-a', '--all',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show all, not just the ones that changed.")
|
help="Show all, not just the ones that changed.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-p', '--percent',
|
'-p', '--percent',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Only show percentage change, not a full diff.")
|
help="Only show percentage change, not a full diff.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--by',
|
'-b', '--by',
|
||||||
action='append',
|
action='append',
|
||||||
choices=DataResult._by,
|
choices=DataResult._by,
|
||||||
help="Group by this field.")
|
help="Group by this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-f', '--field',
|
'-f', '--field',
|
||||||
dest='fields',
|
dest='fields',
|
||||||
action='append',
|
action='append',
|
||||||
choices=DataResult._fields,
|
choices=DataResult._fields,
|
||||||
help="Show this field.")
|
help="Show this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-D', '--define',
|
'-D', '--define',
|
||||||
dest='defines',
|
dest='defines',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs: (
|
lambda k, vs: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
{v.strip() for v in vs.split(',')})
|
{v.strip() for v in vs.split(',')})
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Only include results where this field is this value.")
|
help="Only include results where this field is this value.")
|
||||||
class AppendSort(argparse.Action):
|
class AppendSort(argparse.Action):
|
||||||
def __call__(self, parser, namespace, value, option):
|
def __call__(self, parser, namespace, value, option):
|
||||||
if namespace.sort is None:
|
if namespace.sort is None:
|
||||||
namespace.sort = []
|
namespace.sort = []
|
||||||
namespace.sort.append((value, True if option == '-S' else False))
|
namespace.sort.append((value, True if option == '-S' else False))
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-s', '--sort',
|
'-s', '--sort',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
action=AppendSort,
|
action=AppendSort,
|
||||||
help="Sort by this field.")
|
help="Sort by this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-S', '--reverse-sort',
|
'-S', '--reverse-sort',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
action=AppendSort,
|
action=AppendSort,
|
||||||
help="Sort by this field, but backwards.")
|
help="Sort by this field, but backwards.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-Y', '--summary',
|
'-Y', '--summary',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Only show the total.")
|
help="Only show the total.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-F', '--source',
|
'-F', '--source',
|
||||||
dest='sources',
|
dest='sources',
|
||||||
action='append',
|
action='append',
|
||||||
help="Only consider definitions in this file. Defaults to anything "
|
help="Only consider definitions in this file. Defaults to "
|
||||||
"in the current directory.")
|
"anything in the current directory.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--everything',
|
'--everything',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Include builtin and libc specific symbols.")
|
help="Include builtin and libc specific symbols.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--nm-types',
|
'--nm-types',
|
||||||
default=NM_TYPES,
|
default=NM_TYPES,
|
||||||
help="Type of symbols to report, this uses the same single-character "
|
help="Type of symbols to report, this uses the same "
|
||||||
"type-names emitted by nm. Defaults to %r." % NM_TYPES)
|
"single-character type-names emitted by nm. Defaults to "
|
||||||
|
"%r." % NM_TYPES)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--nm-path',
|
'--nm-path',
|
||||||
type=lambda x: x.split(),
|
type=lambda x: x.split(),
|
||||||
default=NM_PATH,
|
default=NM_PATH,
|
||||||
help="Path to the nm executable, may include flags. "
|
help="Path to the nm executable, may include flags. "
|
||||||
"Defaults to %r." % NM_PATH)
|
"Defaults to %r." % NM_PATH)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--objdump-path',
|
'--objdump-path',
|
||||||
type=lambda x: x.split(),
|
type=lambda x: x.split(),
|
||||||
default=OBJDUMP_PATH,
|
default=OBJDUMP_PATH,
|
||||||
help="Path to the objdump executable, may include flags. "
|
help="Path to the objdump executable, may include flags. "
|
||||||
"Defaults to %r." % OBJDUMP_PATH)
|
"Defaults to %r." % OBJDUMP_PATH)
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+51
-49
@@ -66,12 +66,12 @@ def rbydaddr(s):
|
|||||||
def xxd(data, width=16):
|
def xxd(data, width=16):
|
||||||
for i in range(0, len(data), width):
|
for i in range(0, len(data), width):
|
||||||
yield '%-*s %-*s' % (
|
yield '%-*s %-*s' % (
|
||||||
3*width,
|
3*width,
|
||||||
' '.join('%02x' % b for b in data[i:i+width]),
|
' '.join('%02x' % b for b in data[i:i+width]),
|
||||||
width,
|
width,
|
||||||
''.join(
|
''.join(
|
||||||
b if b >= ' ' and b <= '~' else '.'
|
b if b >= ' ' and b <= '~' else '.'
|
||||||
for b in map(chr, data[i:i+width])))
|
for b in map(chr, data[i:i+width])))
|
||||||
|
|
||||||
def crc32c(data, crc=0):
|
def crc32c(data, crc=0):
|
||||||
crc ^= 0xffffffff
|
crc ^= 0xffffffff
|
||||||
@@ -99,7 +99,7 @@ def main(disk, block=None, *,
|
|||||||
|
|
||||||
if len(block) > 1:
|
if len(block) > 1:
|
||||||
print("error: more than one block address?",
|
print("error: more than one block address?",
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
block = block[0]
|
block = block[0]
|
||||||
@@ -112,17 +112,18 @@ def main(disk, block=None, *,
|
|||||||
|
|
||||||
# block may also encode an offset
|
# block may also encode an offset
|
||||||
block, off, size = (
|
block, off, size = (
|
||||||
block[0] if isinstance(block, tuple) else block,
|
block[0] if isinstance(block, tuple) else block,
|
||||||
off[0] if isinstance(off, tuple)
|
off[0] if isinstance(off, tuple)
|
||||||
else off if off is not None
|
else off if off is not None
|
||||||
else size[0] if isinstance(size, tuple) and len(size) > 1
|
else size[0] if isinstance(size, tuple) and len(size) > 1
|
||||||
else block[1] if isinstance(block, tuple)
|
else block[1] if isinstance(block, tuple)
|
||||||
else None,
|
else None,
|
||||||
size[1] - size[0] if isinstance(size, tuple) and len(size) > 1
|
size[1] - size[0] if isinstance(size, tuple) and len(size) > 1
|
||||||
else size[0] if isinstance(size, tuple)
|
else size[0] if isinstance(size, tuple)
|
||||||
else size if size is not None
|
else size if size is not None
|
||||||
else off[1] - off[0] if isinstance(off, tuple) and len(off) > 1
|
else off[1] - off[0]
|
||||||
else block_size)
|
if isinstance(off, tuple) and len(off) > 1
|
||||||
|
else block_size)
|
||||||
|
|
||||||
# read the block
|
# read the block
|
||||||
f.seek((block * block_size) + (off or 0))
|
f.seek((block * block_size) + (off or 0))
|
||||||
@@ -133,50 +134,51 @@ def main(disk, block=None, *,
|
|||||||
|
|
||||||
# print the header
|
# print the header
|
||||||
print('block %s, size %d, cksum %08x' % (
|
print('block %s, size %d, cksum %08x' % (
|
||||||
'0x%x.%x' % (block, off)
|
'0x%x.%x' % (block, off)
|
||||||
if off is not None
|
if off is not None
|
||||||
else '0x%x' % block,
|
else '0x%x' % block,
|
||||||
size,
|
size,
|
||||||
cksum))
|
cksum))
|
||||||
|
|
||||||
# render the hex view
|
# render the hex view
|
||||||
for o, line in enumerate(xxd(data)):
|
for o, line in enumerate(xxd(data)):
|
||||||
print('%08x: %s' % ((off or 0) + 16*o, line))
|
print('%08x: %s' % ((off or 0) + 16*o, line))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Debug block devices.",
|
description="Debug block devices.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'disk',
|
'disk',
|
||||||
help="File containing the block device.")
|
help="File containing the block device.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'block',
|
'block',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=rbydaddr,
|
type=rbydaddr,
|
||||||
help="Block address.")
|
help="Block address.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--block-size',
|
'-b', '--block-size',
|
||||||
type=bdgeom,
|
type=bdgeom,
|
||||||
help="Block size/geometry in bytes.")
|
help="Block size/geometry in bytes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--block-count',
|
'--block-count',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Block count in blocks.")
|
help="Block count in blocks.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--off',
|
'--off',
|
||||||
type=lambda x: tuple(
|
type=lambda x: tuple(
|
||||||
int(x, 0) if x.strip() else None
|
int(x, 0) if x.strip() else None
|
||||||
for x in x.split(',')),
|
for x in x.split(',')),
|
||||||
help="Show a specific offset, may be a range.")
|
help="Show a specific offset, may be a range.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-n', '--size',
|
'-n', '--size',
|
||||||
type=lambda x: tuple(
|
type=lambda x: tuple(
|
||||||
int(x, 0) if x.strip() else None
|
int(x, 0) if x.strip() else None
|
||||||
for x in x.split(',')),
|
for x in x.split(',')),
|
||||||
help="Show this many bytes, may be a range.")
|
help="Show this many bytes, may be a range.")
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+229
-220
@@ -58,14 +58,14 @@ COLORS = ['33', '34', '32', '90']
|
|||||||
|
|
||||||
CHARS_DOTS = " .':"
|
CHARS_DOTS = " .':"
|
||||||
CHARS_BRAILLE = (
|
CHARS_BRAILLE = (
|
||||||
'⠀⢀⡀⣀⠠⢠⡠⣠⠄⢄⡄⣄⠤⢤⡤⣤' '⠐⢐⡐⣐⠰⢰⡰⣰⠔⢔⡔⣔⠴⢴⡴⣴'
|
'⠀⢀⡀⣀⠠⢠⡠⣠⠄⢄⡄⣄⠤⢤⡤⣤' '⠐⢐⡐⣐⠰⢰⡰⣰⠔⢔⡔⣔⠴⢴⡴⣴'
|
||||||
'⠂⢂⡂⣂⠢⢢⡢⣢⠆⢆⡆⣆⠦⢦⡦⣦' '⠒⢒⡒⣒⠲⢲⡲⣲⠖⢖⡖⣖⠶⢶⡶⣶'
|
'⠂⢂⡂⣂⠢⢢⡢⣢⠆⢆⡆⣆⠦⢦⡦⣦' '⠒⢒⡒⣒⠲⢲⡲⣲⠖⢖⡖⣖⠶⢶⡶⣶'
|
||||||
'⠈⢈⡈⣈⠨⢨⡨⣨⠌⢌⡌⣌⠬⢬⡬⣬' '⠘⢘⡘⣘⠸⢸⡸⣸⠜⢜⡜⣜⠼⢼⡼⣼'
|
'⠈⢈⡈⣈⠨⢨⡨⣨⠌⢌⡌⣌⠬⢬⡬⣬' '⠘⢘⡘⣘⠸⢸⡸⣸⠜⢜⡜⣜⠼⢼⡼⣼'
|
||||||
'⠊⢊⡊⣊⠪⢪⡪⣪⠎⢎⡎⣎⠮⢮⡮⣮' '⠚⢚⡚⣚⠺⢺⡺⣺⠞⢞⡞⣞⠾⢾⡾⣾'
|
'⠊⢊⡊⣊⠪⢪⡪⣪⠎⢎⡎⣎⠮⢮⡮⣮' '⠚⢚⡚⣚⠺⢺⡺⣺⠞⢞⡞⣞⠾⢾⡾⣾'
|
||||||
'⠁⢁⡁⣁⠡⢡⡡⣡⠅⢅⡅⣅⠥⢥⡥⣥' '⠑⢑⡑⣑⠱⢱⡱⣱⠕⢕⡕⣕⠵⢵⡵⣵'
|
'⠁⢁⡁⣁⠡⢡⡡⣡⠅⢅⡅⣅⠥⢥⡥⣥' '⠑⢑⡑⣑⠱⢱⡱⣱⠕⢕⡕⣕⠵⢵⡵⣵'
|
||||||
'⠃⢃⡃⣃⠣⢣⡣⣣⠇⢇⡇⣇⠧⢧⡧⣧' '⠓⢓⡓⣓⠳⢳⡳⣳⠗⢗⡗⣗⠷⢷⡷⣷'
|
'⠃⢃⡃⣃⠣⢣⡣⣣⠇⢇⡇⣇⠧⢧⡧⣧' '⠓⢓⡓⣓⠳⢳⡳⣳⠗⢗⡗⣗⠷⢷⡷⣷'
|
||||||
'⠉⢉⡉⣉⠩⢩⡩⣩⠍⢍⡍⣍⠭⢭⡭⣭' '⠙⢙⡙⣙⠹⢹⡹⣹⠝⢝⡝⣝⠽⢽⡽⣽'
|
'⠉⢉⡉⣉⠩⢩⡩⣩⠍⢍⡍⣍⠭⢭⡭⣭' '⠙⢙⡙⣙⠹⢹⡹⣹⠝⢝⡝⣝⠽⢽⡽⣽'
|
||||||
'⠋⢋⡋⣋⠫⢫⡫⣫⠏⢏⡏⣏⠯⢯⡯⣯' '⠛⢛⡛⣛⠻⢻⡻⣻⠟⢟⡟⣟⠿⢿⡿⣿')
|
'⠋⢋⡋⣋⠫⢫⡫⣫⠏⢏⡏⣏⠯⢯⡯⣯' '⠛⢛⡛⣛⠻⢻⡻⣻⠟⢟⡟⣟⠿⢿⡿⣿')
|
||||||
|
|
||||||
|
|
||||||
# some ways of block geometry representations
|
# 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, 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+b_x_, y+b_y_, a_x, a_y, b_x-b_x_, b_y-b_y_)
|
||||||
yield from hilbert_(
|
yield from hilbert_(
|
||||||
x+(a_x-a_dx)+(b_x_-b_dx), y+(a_y-a_dy)+(b_y_-b_dy),
|
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_))
|
-b_x_, -b_y_, -(a_x-a_x_), -(a_y-a_y_))
|
||||||
|
|
||||||
if width >= height:
|
if width >= height:
|
||||||
curve = hilbert_(0, 0, +width, 0, 0, +height)
|
curve = hilbert_(0, 0, +width, 0, 0, +height)
|
||||||
@@ -293,10 +293,10 @@ class Pixel(int):
|
|||||||
btree=False,
|
btree=False,
|
||||||
data=False):
|
data=False):
|
||||||
return super().__new__(cls,
|
return super().__new__(cls,
|
||||||
state
|
state
|
||||||
| (1 if mdir else 0)
|
| (1 if mdir else 0)
|
||||||
| (2 if btree else 0)
|
| (2 if btree else 0)
|
||||||
| (4 if data else 0))
|
| (4 if data else 0))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_mdir(self):
|
def is_mdir(self):
|
||||||
@@ -367,8 +367,8 @@ class Pixel(int):
|
|||||||
# apply colors
|
# apply colors
|
||||||
if f and color:
|
if f and color:
|
||||||
c = '%s%s\x1b[m' % (
|
c = '%s%s\x1b[m' % (
|
||||||
''.join('\x1b[%sm' % f_ for f_ in f),
|
''.join('\x1b[%sm' % f_ for f_ in f),
|
||||||
c)
|
c)
|
||||||
|
|
||||||
return c
|
return c
|
||||||
|
|
||||||
@@ -434,25 +434,25 @@ class Bmap:
|
|||||||
block -= self._block_window.start
|
block -= self._block_window.start
|
||||||
|
|
||||||
size = (max(self._off_window.start,
|
size = (max(self._off_window.start,
|
||||||
min(self._off_window.stop, off+size))
|
min(self._off_window.stop, off+size))
|
||||||
- max(self._off_window.start,
|
- max(self._off_window.start,
|
||||||
min(self._off_window.stop, off)))
|
min(self._off_window.stop, off)))
|
||||||
off = (max(self._off_window.start,
|
off = (max(self._off_window.start,
|
||||||
min(self._off_window.stop, off))
|
min(self._off_window.stop, off))
|
||||||
- self._off_window.start)
|
- self._off_window.start)
|
||||||
if size == 0:
|
if size == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
# map to our block space
|
# map to our block space
|
||||||
range_ = range(
|
range_ = range(
|
||||||
block*len(self._off_window) + off,
|
block*len(self._off_window) + off,
|
||||||
block*len(self._off_window) + off+size)
|
block*len(self._off_window) + off+size)
|
||||||
range_ = range(
|
range_ = range(
|
||||||
(range_.start*len(self.pixels)) // self._window,
|
(range_.start*len(self.pixels)) // self._window,
|
||||||
(range_.stop*len(self.pixels)) // self._window)
|
(range_.stop*len(self.pixels)) // self._window)
|
||||||
range_ = range(
|
range_ = range(
|
||||||
range_.start,
|
range_.start,
|
||||||
max(range_.stop, range_.start+1))
|
max(range_.stop, range_.start+1))
|
||||||
|
|
||||||
# apply the op
|
# apply the op
|
||||||
for i in range_:
|
for i in range_:
|
||||||
@@ -476,9 +476,9 @@ class Bmap:
|
|||||||
width=None,
|
width=None,
|
||||||
height=None):
|
height=None):
|
||||||
block_size = (block_size if block_size is not 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
|
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
|
width = width if width is not None else self.width
|
||||||
height = height if height is not None else self.height
|
height = height if height is not None else self.height
|
||||||
|
|
||||||
@@ -496,17 +496,17 @@ class Bmap:
|
|||||||
for x in range(width*height):
|
for x in range(width*height):
|
||||||
# map into our old bd space
|
# map into our old bd space
|
||||||
range_ = range(
|
range_ = range(
|
||||||
(x*self._window) // (width*height),
|
(x*self._window) // (width*height),
|
||||||
((x+1)*self._window) // (width*height))
|
((x+1)*self._window) // (width*height))
|
||||||
range_ = range(
|
range_ = range(
|
||||||
range_.start,
|
range_.start,
|
||||||
max(range_.stop, range_.start+1))
|
max(range_.stop, range_.start+1))
|
||||||
|
|
||||||
# aggregate state
|
# aggregate state
|
||||||
pixels.append(ft.reduce(
|
pixels.append(ft.reduce(
|
||||||
Pixel.__or__,
|
Pixel.__or__,
|
||||||
self.pixels[range_.start:range_.stop],
|
self.pixels[range_.start:range_.stop],
|
||||||
Pixel()))
|
Pixel()))
|
||||||
|
|
||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
@@ -552,12 +552,12 @@ class Bmap:
|
|||||||
byte_p |= 1 << i
|
byte_p |= 1 << i
|
||||||
|
|
||||||
line.append(best_p.draw(
|
line.append(best_p.draw(
|
||||||
CHARS_BRAILLE[byte_p],
|
CHARS_BRAILLE[byte_p],
|
||||||
braille=True,
|
braille=True,
|
||||||
mdirs=mdirs,
|
mdirs=mdirs,
|
||||||
btrees=btrees,
|
btrees=btrees,
|
||||||
datas=datas,
|
datas=datas,
|
||||||
**args))
|
**args))
|
||||||
elif dots:
|
elif dots:
|
||||||
# encode into a byte
|
# encode into a byte
|
||||||
for x in range(self.width):
|
for x in range(self.width):
|
||||||
@@ -572,19 +572,19 @@ class Bmap:
|
|||||||
byte_p |= 1 << i
|
byte_p |= 1 << i
|
||||||
|
|
||||||
line.append(best_p.draw(
|
line.append(best_p.draw(
|
||||||
CHARS_DOTS[byte_p],
|
CHARS_DOTS[byte_p],
|
||||||
dots=True,
|
dots=True,
|
||||||
mdirs=mdirs,
|
mdirs=mdirs,
|
||||||
btrees=btrees,
|
btrees=btrees,
|
||||||
datas=datas,
|
datas=datas,
|
||||||
**args))
|
**args))
|
||||||
else:
|
else:
|
||||||
for x in range(self.width):
|
for x in range(self.width):
|
||||||
line.append(grid[x + row*self.width].draw(
|
line.append(grid[x + row*self.width].draw(
|
||||||
mdirs=mdirs,
|
mdirs=mdirs,
|
||||||
btrees=btrees,
|
btrees=btrees,
|
||||||
datas=datas,
|
datas=datas,
|
||||||
**args))
|
**args))
|
||||||
|
|
||||||
return ''.join(line)
|
return ''.join(line)
|
||||||
|
|
||||||
@@ -610,9 +610,9 @@ class Rbyd:
|
|||||||
return '0x%x.%x' % (self.block, self.trunk)
|
return '0x%x.%x' % (self.block, self.trunk)
|
||||||
else:
|
else:
|
||||||
return '0x{%x,%s}.%x' % (
|
return '0x{%x,%s}.%x' % (
|
||||||
self.block,
|
self.block,
|
||||||
','.join('%x' % block for block in self.redund_blocks),
|
','.join('%x' % block for block in self.redund_blocks),
|
||||||
self.trunk)
|
self.trunk)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fetch(cls, f, block_size, blocks, trunk=None):
|
def fetch(cls, f, block_size, blocks, trunk=None):
|
||||||
@@ -621,21 +621,23 @@ class Rbyd:
|
|||||||
|
|
||||||
if len(blocks) > 1:
|
if len(blocks) > 1:
|
||||||
# fetch all blocks
|
# 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
|
# determine most recent revision
|
||||||
i = 0
|
i = 0
|
||||||
for i_, rbyd in enumerate(rbyds):
|
for i_, rbyd in enumerate(rbyds):
|
||||||
# compare with sequence arithmetic
|
# compare with sequence arithmetic
|
||||||
if rbyd and (
|
if rbyd and (
|
||||||
not rbyds[i]
|
not rbyds[i]
|
||||||
or not ((rbyd.rev - rbyds[i].rev) & 0x80000000)
|
or not ((rbyd.rev - rbyds[i].rev) & 0x80000000)
|
||||||
or (rbyd.rev == rbyds[i].rev
|
or (rbyd.rev == rbyds[i].rev
|
||||||
and rbyd.trunk > rbyds[i].trunk)):
|
and rbyd.trunk > rbyds[i].trunk)):
|
||||||
i = i_
|
i = i_
|
||||||
# keep track of the other blocks
|
# keep track of the other blocks
|
||||||
rbyd = rbyds[i]
|
rbyd = rbyds[i]
|
||||||
rbyd.redund_blocks = [rbyds[(i+1+j) % len(rbyds)].block
|
rbyd.redund_blocks = [
|
||||||
for j in range(len(rbyds)-1)]
|
rbyds[(i+1+j) % len(rbyds)].block
|
||||||
|
for j in range(len(rbyds)-1)]
|
||||||
return rbyd
|
return rbyd
|
||||||
else:
|
else:
|
||||||
# block may encode a trunk
|
# block may encode a trunk
|
||||||
@@ -789,7 +791,9 @@ class Rbyd:
|
|||||||
|
|
||||||
done = not tag_ or (rid_, tag_) < (rid, tag)
|
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):
|
def __bool__(self):
|
||||||
return bool(self.trunk)
|
return bool(self.trunk)
|
||||||
@@ -834,7 +838,7 @@ class Rbyd:
|
|||||||
w = 0
|
w = 0
|
||||||
for i in it.count():
|
for i in it.count():
|
||||||
done, rid__, tag, w_, j, d, data, _ = rbyd.lookup(
|
done, rid__, tag, w_, j, d, data, _ = rbyd.lookup(
|
||||||
rid_, tag+0x1)
|
rid_, tag+0x1)
|
||||||
if done or (i != 0 and rid__ != rid_):
|
if done or (i != 0 and rid__ != rid_):
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -880,14 +884,15 @@ class Rbyd:
|
|||||||
|
|
||||||
# lookup our mbid
|
# lookup our mbid
|
||||||
done, mbid, mw, rbyd, rid, tags, path = mtree.btree_lookup(
|
done, mbid, mw, rbyd, rid, tags, path = mtree.btree_lookup(
|
||||||
f, block_size, mbid)
|
f, block_size, mbid)
|
||||||
if done:
|
if done:
|
||||||
return True, -1, 0, None
|
return True, -1, 0, None
|
||||||
|
|
||||||
mdir = next(((tag, j, d, data)
|
mdir = next(
|
||||||
for tag, j, d, data in tags
|
((tag, j, d, data)
|
||||||
if tag == TAG_MDIR),
|
for tag, j, d, data in tags
|
||||||
None)
|
if tag == TAG_MDIR),
|
||||||
|
None)
|
||||||
if not mdir:
|
if not mdir:
|
||||||
return True, -1, 0, None
|
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):
|
if any(isinstance(b, list) and len(b) > 1 for b in block):
|
||||||
print("error: more than one block address?",
|
print("error: more than one block address?",
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
if isinstance(block[0], list):
|
if isinstance(block[0], list):
|
||||||
block = (block[0][0], *block[1:])
|
block = (block[0][0], *block[1:])
|
||||||
@@ -1034,16 +1039,17 @@ def main(disk, mroots=None, *,
|
|||||||
|
|
||||||
# create our block device representation
|
# create our block device representation
|
||||||
bmap = Bmap(
|
bmap = Bmap(
|
||||||
block_size=block_size,
|
block_size=block_size,
|
||||||
block_count=block_count,
|
block_count=block_count,
|
||||||
block_window=block_window,
|
block_window=block_window,
|
||||||
off_window=off_window,
|
off_window=off_window,
|
||||||
# scale if we're printing with dots or braille
|
# scale if we're printing with dots or braille
|
||||||
width=2*width_ if braille else width_,
|
width=2*width_ if braille else width_,
|
||||||
height=max(1,
|
height=max(
|
||||||
4*height_ if braille
|
1,
|
||||||
else 2*height_ if dots
|
4*height_ if braille
|
||||||
else height_))
|
else 2*height_ if dots
|
||||||
|
else height_))
|
||||||
|
|
||||||
# keep track of how many blocks are in use
|
# keep track of how many blocks are in use
|
||||||
mdirs_ = 0
|
mdirs_ = 0
|
||||||
@@ -1063,16 +1069,16 @@ def main(disk, mroots=None, *,
|
|||||||
block_size = f.tell()
|
block_size = f.tell()
|
||||||
block_count = 1
|
block_count = 1
|
||||||
bmap.resize(
|
bmap.resize(
|
||||||
block_size=block_size,
|
block_size=block_size,
|
||||||
block_count=block_count)
|
block_count=block_count)
|
||||||
|
|
||||||
# if block_count is omitted, derive the block_count from our file size
|
# if block_count is omitted, derive the block_count from our file size
|
||||||
if block_count is None:
|
if block_count is None:
|
||||||
f.seek(0, os.SEEK_END)
|
f.seek(0, os.SEEK_END)
|
||||||
block_count = f.tell() // block_size
|
block_count = f.tell() // block_size
|
||||||
bmap.resize(
|
bmap.resize(
|
||||||
block_size=block_size,
|
block_size=block_size,
|
||||||
block_count=block_count)
|
block_count=block_count)
|
||||||
|
|
||||||
#### traverse the filesystem
|
#### traverse the filesystem
|
||||||
|
|
||||||
@@ -1090,7 +1096,7 @@ def main(disk, mroots=None, *,
|
|||||||
# mark mroots in our bmap
|
# mark mroots in our bmap
|
||||||
for block in mroot.blocks:
|
for block in mroot.blocks:
|
||||||
bmap.mdir(block,
|
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;
|
mdirs_ += 1;
|
||||||
|
|
||||||
# find any file btrees in our mroot
|
# find any file btrees in our mroot
|
||||||
@@ -1129,7 +1135,8 @@ def main(disk, mroots=None, *,
|
|||||||
# mark mdir in our bmap
|
# mark mdir in our bmap
|
||||||
for block in mdir.blocks:
|
for block in mdir.blocks:
|
||||||
bmap.mdir(block,
|
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
|
mdirs_ += 1
|
||||||
|
|
||||||
# find any file btrees in our mdir
|
# find any file btrees in our mdir
|
||||||
@@ -1153,8 +1160,8 @@ def main(disk, mroots=None, *,
|
|||||||
ppath = []
|
ppath = []
|
||||||
while True:
|
while True:
|
||||||
done, mbid, mw, rbyd, rid, tags, path = mtree.btree_lookup(
|
done, mbid, mw, rbyd, rid, tags, path = mtree.btree_lookup(
|
||||||
f, block_size, mbid+1,
|
f, block_size, mbid+1,
|
||||||
depth=args.get('depth', mdepth)-mdepth)
|
depth=args.get('depth', mdepth)-mdepth)
|
||||||
if done:
|
if done:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -1176,8 +1183,8 @@ def main(disk, mroots=None, *,
|
|||||||
d, (mid_, w_, rbyd_, rid_, tags_) = x
|
d, (mid_, w_, rbyd_, rid_, tags_) = x
|
||||||
for block in rbyd_.blocks:
|
for block in rbyd_.blocks:
|
||||||
bmap.btree(block,
|
bmap.btree(block,
|
||||||
rbyd_.eoff if args.get('in_use')
|
rbyd_.eoff if args.get('in_use')
|
||||||
else block_size)
|
else block_size)
|
||||||
btrees_ += 1
|
btrees_ += 1
|
||||||
ppath = path
|
ppath = path
|
||||||
|
|
||||||
@@ -1190,10 +1197,11 @@ def main(disk, mroots=None, *,
|
|||||||
mdir__ = None
|
mdir__ = None
|
||||||
if (not args.get('depth')
|
if (not args.get('depth')
|
||||||
or mdepth+len(path) < args.get('depth')):
|
or mdepth+len(path) < args.get('depth')):
|
||||||
mdir__ = next(((tag, j, d, data)
|
mdir__ = next(
|
||||||
for tag, j, d, data in tags
|
((tag, j, d, data)
|
||||||
if tag == TAG_MDIR),
|
for tag, j, d, data in tags
|
||||||
None)
|
if tag == TAG_MDIR),
|
||||||
|
None)
|
||||||
|
|
||||||
if mdir__:
|
if mdir__:
|
||||||
# fetch the mdir
|
# fetch the mdir
|
||||||
@@ -1208,8 +1216,8 @@ def main(disk, mroots=None, *,
|
|||||||
# mark mdir in our bmap
|
# mark mdir in our bmap
|
||||||
for block in mdir_.blocks:
|
for block in mdir_.blocks:
|
||||||
bmap.mdir(block, 0,
|
bmap.mdir(block, 0,
|
||||||
mdir_.eoff if args.get('in_use')
|
mdir_.eoff if args.get('in_use')
|
||||||
else block_size)
|
else block_size)
|
||||||
mdirs_ += 1
|
mdirs_ += 1
|
||||||
|
|
||||||
# find any file btrees in our mdir
|
# find any file btrees in our mdir
|
||||||
@@ -1233,8 +1241,8 @@ def main(disk, mroots=None, *,
|
|||||||
size, block, off = frombptr(data)
|
size, block, off = frombptr(data)
|
||||||
# mark block in our bmap
|
# mark block in our bmap
|
||||||
bmap.data(block,
|
bmap.data(block,
|
||||||
off if args.get('in_use') else 0,
|
off if args.get('in_use') else 0,
|
||||||
size if args.get('in_use') else block_size)
|
size if args.get('in_use') else block_size)
|
||||||
datas_ += 1
|
datas_ += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -1258,9 +1266,9 @@ def main(disk, mroots=None, *,
|
|||||||
ppath = []
|
ppath = []
|
||||||
while True:
|
while True:
|
||||||
(done, bid, w, rbyd, rid, tags, path
|
(done, bid, w, rbyd, rid, tags, path
|
||||||
) = btree.btree_lookup(
|
) = btree.btree_lookup(
|
||||||
f, block_size, bid+1,
|
f, block_size, bid+1,
|
||||||
depth=args.get('depth', mdepth)-mdepth)
|
depth=args.get('depth', mdepth)-mdepth)
|
||||||
if done:
|
if done:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -1285,8 +1293,8 @@ def main(disk, mroots=None, *,
|
|||||||
continue
|
continue
|
||||||
for block in rbyd_.blocks:
|
for block in rbyd_.blocks:
|
||||||
bmap.btree(block,
|
bmap.btree(block,
|
||||||
rbyd_.eoff if args.get('in_use')
|
rbyd_.eoff if args.get('in_use')
|
||||||
else block_size)
|
else block_size)
|
||||||
btrees_ += 1
|
btrees_ += 1
|
||||||
ppath = path
|
ppath = path
|
||||||
|
|
||||||
@@ -1299,10 +1307,11 @@ def main(disk, mroots=None, *,
|
|||||||
bptr__ = None
|
bptr__ = None
|
||||||
if (not args.get('depth')
|
if (not args.get('depth')
|
||||||
or mdepth+len(path) < args.get('depth')):
|
or mdepth+len(path) < args.get('depth')):
|
||||||
bptr__ = next(((tag, j, d, data)
|
bptr__ = next(
|
||||||
for tag, j, d, data in tags
|
((tag, j, d, data)
|
||||||
if tag & 0xfff == TAG_BLOCK),
|
for tag, j, d, data in tags
|
||||||
None)
|
if tag & 0xfff == TAG_BLOCK),
|
||||||
|
None)
|
||||||
|
|
||||||
if bptr__:
|
if bptr__:
|
||||||
# fetch the block
|
# fetch the block
|
||||||
@@ -1311,8 +1320,8 @@ def main(disk, mroots=None, *,
|
|||||||
|
|
||||||
# mark blocks in our bmap
|
# mark blocks in our bmap
|
||||||
bmap.data(block,
|
bmap.data(block,
|
||||||
off if args.get('in_use') else 0,
|
off if args.get('in_use') else 0,
|
||||||
size if args.get('in_use') else block_size)
|
size if args.get('in_use') else block_size)
|
||||||
datas_ += 1
|
datas_ += 1
|
||||||
|
|
||||||
#### actual rendering begins here
|
#### actual rendering begins here
|
||||||
@@ -1320,29 +1329,29 @@ def main(disk, mroots=None, *,
|
|||||||
# print some information about the bmap
|
# print some information about the bmap
|
||||||
if not no_header:
|
if not no_header:
|
||||||
print('bd %dx%d%s%s%s' % (
|
print('bd %dx%d%s%s%s' % (
|
||||||
block_size, block_count,
|
block_size, block_count,
|
||||||
', %6s mdir' % ('%.1f%%' % (100*mdirs_ / block_count))
|
', %6s mdir' % ('%.1f%%' % (100*mdirs_ / block_count))
|
||||||
if mdirs else '',
|
if mdirs else '',
|
||||||
', %6s btree' % ('%.1f%%' % (100*btrees_ / block_count))
|
', %6s btree' % ('%.1f%%' % (100*btrees_ / block_count))
|
||||||
if btrees else '',
|
if btrees else '',
|
||||||
', %6s data' % ('%.1f%%' % (100*datas_ / block_count))
|
', %6s data' % ('%.1f%%' % (100*datas_ / block_count))
|
||||||
if datas else ''))
|
if datas else ''))
|
||||||
|
|
||||||
# and then print the bmap
|
# and then print the bmap
|
||||||
for row in range(
|
for row in range(
|
||||||
mt.ceil(bmap.height/4) if braille
|
mt.ceil(bmap.height/4) if braille
|
||||||
else mt.ceil(bmap.height/2) if dots
|
else mt.ceil(bmap.height/2) if dots
|
||||||
else bmap.height):
|
else bmap.height):
|
||||||
line = bmap.draw(row,
|
line = bmap.draw(row,
|
||||||
mdirs=mdirs,
|
mdirs=mdirs,
|
||||||
btrees=btrees,
|
btrees=btrees,
|
||||||
datas=datas,
|
datas=datas,
|
||||||
color=color,
|
color=color,
|
||||||
dots=dots,
|
dots=dots,
|
||||||
braille=braille,
|
braille=braille,
|
||||||
hilbert=hilbert,
|
hilbert=hilbert,
|
||||||
lebesgue=lebesgue,
|
lebesgue=lebesgue,
|
||||||
**args)
|
**args)
|
||||||
print(line)
|
print(line)
|
||||||
|
|
||||||
if args.get('error_on_corrupt') and corrupted:
|
if args.get('error_on_corrupt') and corrupted:
|
||||||
@@ -1353,122 +1362,122 @@ if __name__ == "__main__":
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Render currently used blocks in a littlefs image.",
|
description="Render currently used blocks in a littlefs image.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'disk',
|
'disk',
|
||||||
help="File containing the block device.")
|
help="File containing the block device.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'mroots',
|
'mroots',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
type=rbydaddr,
|
type=rbydaddr,
|
||||||
help="Block address of the mroots. Defaults to 0x{0,1}.")
|
help="Block address of the mroots. Defaults to 0x{0,1}.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--block-size',
|
'-b', '--block-size',
|
||||||
type=bdgeom,
|
type=bdgeom,
|
||||||
help="Block size/geometry in bytes.")
|
help="Block size/geometry in bytes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--block-count',
|
'--block-count',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Block count in blocks.")
|
help="Block count in blocks.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-@', '--block',
|
'-@', '--block',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: tuple(
|
type=lambda x: tuple(
|
||||||
rbydaddr(x) if x.strip() else None
|
rbydaddr(x) if x.strip() else None
|
||||||
for x in x.split(',')),
|
for x in x.split(',')),
|
||||||
help="Optional block to show, may be a range.")
|
help="Optional block to show, may be a range.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--off',
|
'--off',
|
||||||
type=lambda x: tuple(
|
type=lambda x: tuple(
|
||||||
int(x, 0) if x.strip() else None
|
int(x, 0) if x.strip() else None
|
||||||
for x in x.split(',')),
|
for x in x.split(',')),
|
||||||
help="Show a specific offset, may be a range.")
|
help="Show a specific offset, may be a range.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--size',
|
'--size',
|
||||||
type=lambda x: tuple(
|
type=lambda x: tuple(
|
||||||
int(x, 0) if x.strip() else None
|
int(x, 0) if x.strip() else None
|
||||||
for x in x.split(',')),
|
for x in x.split(',')),
|
||||||
help="Show this many bytes, may be a range.")
|
help="Show this many bytes, may be a range.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-M', '--mdirs',
|
'-M', '--mdirs',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Render mdir blocks.")
|
help="Render mdir blocks.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-B', '--btrees',
|
'-B', '--btrees',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Render btree blocks.")
|
help="Render btree blocks.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-D', '--datas',
|
'-D', '--datas',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Render data blocks.")
|
help="Render data blocks.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-N', '--no-header',
|
'-N', '--no-header',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Don't show the header.")
|
help="Don't show the header.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--color',
|
'--color',
|
||||||
choices=['never', 'always', 'auto'],
|
choices=['never', 'always', 'auto'],
|
||||||
default='auto',
|
default='auto',
|
||||||
help="When to use terminal colors. Defaults to 'auto'.")
|
help="When to use terminal colors. Defaults to 'auto'.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-:', '--dots',
|
'-:', '--dots',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Use 1x2 ascii dot characters.")
|
help="Use 1x2 ascii dot characters.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-⣿', '--braille',
|
'-⣿', '--braille',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Use 2x4 unicode braille characters. Note that braille characters "
|
help="Use 2x4 unicode braille characters. Note that braille "
|
||||||
"sometimes suffer from inconsistent widths.")
|
"characters sometimes suffer from inconsistent widths.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--chars',
|
'--chars',
|
||||||
help="Characters to use for mdir, btree, data, unused blocks.")
|
help="Characters to use for mdir, btree, data, unused blocks.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--colors',
|
'--colors',
|
||||||
type=lambda x: [x.strip() for x in x.split(',')],
|
type=lambda x: [x.strip() for x in x.split(',')],
|
||||||
help="Colors to use for mdir, btree, data, unused blocks.")
|
help="Colors to use for mdir, btree, data, unused blocks.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-W', '--width',
|
'-W', '--width',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
const=0,
|
const=0,
|
||||||
help="Width in columns. 0 uses the terminal width. Defaults to "
|
help="Width in columns. 0 uses the terminal width. Defaults to "
|
||||||
"min(terminal, 80).")
|
"min(terminal, 80).")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-H', '--height',
|
'-H', '--height',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
const=0,
|
const=0,
|
||||||
help="Height in rows. 0 uses the terminal height. Defaults to 1.")
|
help="Height in rows. 0 uses the terminal height. Defaults to 1.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-n', '--lines',
|
'-n', '--lines',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
const=0,
|
const=0,
|
||||||
help="Show this many lines of history. 0 uses the terminal height. "
|
help="Show this many lines of history. 0 uses the terminal "
|
||||||
"Defaults to 5.")
|
"height. Defaults to 5.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-U', '--hilbert',
|
'-U', '--hilbert',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Render as a space-filling Hilbert curve.")
|
help="Render as a space-filling Hilbert curve.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-Z', '--lebesgue',
|
'-Z', '--lebesgue',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Render as a space-filling Z-curve.")
|
help="Render as a space-filling Z-curve.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-i', '--in-use',
|
'-i', '--in-use',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show how much of each block is in use.")
|
help="Show how much of each block is in use.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-z', '--depth',
|
'-z', '--depth',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
const=0,
|
const=0,
|
||||||
help="Depth of the filesystem tree to parse.")
|
help="Depth of the filesystem tree to parse.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-e', '--error-on-corrupt',
|
'-e', '--error-on-corrupt',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Error if the filesystem is corrupt.")
|
help="Error if the filesystem is corrupt.")
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+202
-195
@@ -154,108 +154,108 @@ def frombranch(data):
|
|||||||
def xxd(data, width=16):
|
def xxd(data, width=16):
|
||||||
for i in range(0, len(data), width):
|
for i in range(0, len(data), width):
|
||||||
yield '%-*s %-*s' % (
|
yield '%-*s %-*s' % (
|
||||||
3*width,
|
3*width,
|
||||||
' '.join('%02x' % b for b in data[i:i+width]),
|
' '.join('%02x' % b for b in data[i:i+width]),
|
||||||
width,
|
width,
|
||||||
''.join(
|
''.join(
|
||||||
b if b >= ' ' and b <= '~' else '.'
|
b if b >= ' ' and b <= '~' else '.'
|
||||||
for b in map(chr, data[i:i+width])))
|
for b in map(chr, data[i:i+width])))
|
||||||
|
|
||||||
def tagrepr(tag, w=None, size=None, off=None):
|
def tagrepr(tag, w=None, size=None, off=None):
|
||||||
if (tag & 0x6fff) == TAG_NULL:
|
if (tag & 0x6fff) == TAG_NULL:
|
||||||
return '%snull%s%s' % (
|
return '%snull%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %d' % size if size else '')
|
' %d' % size if size else '')
|
||||||
elif (tag & 0x6f00) == TAG_CONFIG:
|
elif (tag & 0x6f00) == TAG_CONFIG:
|
||||||
return '%s%s%s%s' % (
|
return '%s%s%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
'magic' if (tag & 0xfff) == TAG_MAGIC
|
'magic' if (tag & 0xfff) == TAG_MAGIC
|
||||||
else 'version' if (tag & 0xfff) == TAG_VERSION
|
else 'version' if (tag & 0xfff) == TAG_VERSION
|
||||||
else 'rcompat' if (tag & 0xfff) == TAG_RCOMPAT
|
else 'rcompat' if (tag & 0xfff) == TAG_RCOMPAT
|
||||||
else 'wcompat' if (tag & 0xfff) == TAG_WCOMPAT
|
else 'wcompat' if (tag & 0xfff) == TAG_WCOMPAT
|
||||||
else 'ocompat' if (tag & 0xfff) == TAG_OCOMPAT
|
else 'ocompat' if (tag & 0xfff) == TAG_OCOMPAT
|
||||||
else 'geometry' if (tag & 0xfff) == TAG_GEOMETRY
|
else 'geometry' if (tag & 0xfff) == TAG_GEOMETRY
|
||||||
else 'namelimit' if (tag & 0xfff) == TAG_NAMELIMIT
|
else 'namelimit' if (tag & 0xfff) == TAG_NAMELIMIT
|
||||||
else 'filelimit' if (tag & 0xfff) == TAG_FILELIMIT
|
else 'filelimit' if (tag & 0xfff) == TAG_FILELIMIT
|
||||||
else 'config 0x%02x' % (tag & 0xff),
|
else 'config 0x%02x' % (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x6f00) == TAG_GDELTA:
|
elif (tag & 0x6f00) == TAG_GDELTA:
|
||||||
return '%s%s%s%s' % (
|
return '%s%s%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
'grmdelta' if (tag & 0xfff) == TAG_GRMDELTA
|
'grmdelta' if (tag & 0xfff) == TAG_GRMDELTA
|
||||||
else 'gdelta 0x%02x' % (tag & 0xff),
|
else 'gdelta 0x%02x' % (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x6f00) == TAG_NAME:
|
elif (tag & 0x6f00) == TAG_NAME:
|
||||||
return '%s%s%s%s' % (
|
return '%s%s%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
'name' if (tag & 0xfff) == TAG_NAME
|
'name' if (tag & 0xfff) == TAG_NAME
|
||||||
else 'reg' if (tag & 0xfff) == TAG_REG
|
else 'reg' if (tag & 0xfff) == TAG_REG
|
||||||
else 'dir' if (tag & 0xfff) == TAG_DIR
|
else 'dir' if (tag & 0xfff) == TAG_DIR
|
||||||
else 'orphan' if (tag & 0xfff) == TAG_ORPHAN
|
else 'orphan' if (tag & 0xfff) == TAG_ORPHAN
|
||||||
else 'bookmark' if (tag & 0xfff) == TAG_BOOKMARK
|
else 'bookmark' if (tag & 0xfff) == TAG_BOOKMARK
|
||||||
else 'name 0x%02x' % (tag & 0xff),
|
else 'name 0x%02x' % (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x6f00) == TAG_STRUCT:
|
elif (tag & 0x6f00) == TAG_STRUCT:
|
||||||
return '%s%s%s%s' % (
|
return '%s%s%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
'data' if (tag & 0xfff) == TAG_DATA
|
'data' if (tag & 0xfff) == TAG_DATA
|
||||||
else 'block' if (tag & 0xfff) == TAG_BLOCK
|
else 'block' if (tag & 0xfff) == TAG_BLOCK
|
||||||
else 'bshrub' if (tag & 0xfff) == TAG_BSHRUB
|
else 'bshrub' if (tag & 0xfff) == TAG_BSHRUB
|
||||||
else 'btree' if (tag & 0xfff) == TAG_BTREE
|
else 'btree' if (tag & 0xfff) == TAG_BTREE
|
||||||
else 'mroot' if (tag & 0xfff) == TAG_MROOT
|
else 'mroot' if (tag & 0xfff) == TAG_MROOT
|
||||||
else 'mdir' if (tag & 0xfff) == TAG_MDIR
|
else 'mdir' if (tag & 0xfff) == TAG_MDIR
|
||||||
else 'mtree' if (tag & 0xfff) == TAG_MTREE
|
else 'mtree' if (tag & 0xfff) == TAG_MTREE
|
||||||
else 'did' if (tag & 0xfff) == TAG_DID
|
else 'did' if (tag & 0xfff) == TAG_DID
|
||||||
else 'branch' if (tag & 0xfff) == TAG_BRANCH
|
else 'branch' if (tag & 0xfff) == TAG_BRANCH
|
||||||
else 'struct 0x%02x' % (tag & 0xff),
|
else 'struct 0x%02x' % (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x6e00) == TAG_ATTR:
|
elif (tag & 0x6e00) == TAG_ATTR:
|
||||||
return '%s%sattr 0x%02x%s%s' % (
|
return '%s%sattr 0x%02x%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
's' if tag & 0x100 else 'u',
|
's' if tag & 0x100 else 'u',
|
||||||
((tag & 0x100) >> 1) ^ (tag & 0xff),
|
((tag & 0x100) >> 1) ^ (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif tag & TAG_ALT:
|
elif tag & TAG_ALT:
|
||||||
return 'alt%s%s%s%s%s' % (
|
return 'alt%s%s%s%s%s' % (
|
||||||
'r' if tag & TAG_R else 'b',
|
'r' if tag & TAG_R else 'b',
|
||||||
'a' if tag & 0x0fff == 0 and tag & TAG_GT
|
'a' if tag & 0x0fff == 0 and tag & TAG_GT
|
||||||
else 'n' if tag & 0x0fff == 0
|
else 'n' if tag & 0x0fff == 0
|
||||||
else 'gt' if tag & TAG_GT
|
else 'gt' if tag & TAG_GT
|
||||||
else 'le',
|
else 'le',
|
||||||
' 0x%x' % (tag & 0x0fff) if tag & 0x0fff != 0 else '',
|
' 0x%x' % (tag & 0x0fff) if tag & 0x0fff != 0 else '',
|
||||||
' w%d' % w if w is not None else '',
|
' w%d' % w if w is not None else '',
|
||||||
' 0x%x' % (0xffffffff & (off-size))
|
' 0x%x' % (0xffffffff & (off-size))
|
||||||
if size and off is not None
|
if size and off is not None
|
||||||
else ' -%d' % size if size
|
else ' -%d' % size if size
|
||||||
else '')
|
else '')
|
||||||
elif (tag & 0x7f00) == TAG_CKSUM:
|
elif (tag & 0x7f00) == TAG_CKSUM:
|
||||||
return 'cksum%s%s%s%s%s' % (
|
return 'cksum%s%s%s%s%s' % (
|
||||||
'q' if not tag & 0xfc and tag & TAG_Q else '',
|
'q' if not tag & 0xfc and tag & TAG_Q else '',
|
||||||
'p' if not tag & 0xfc and tag & TAG_P else '',
|
'p' if not tag & 0xfc and tag & TAG_P else '',
|
||||||
' 0x%02x' % (tag & 0xff) if tag & 0xfc else '',
|
' 0x%02x' % (tag & 0xff) if tag & 0xfc else '',
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x7f00) == TAG_NOTE:
|
elif (tag & 0x7f00) == TAG_NOTE:
|
||||||
return 'note%s%s%s' % (
|
return 'note%s%s%s' % (
|
||||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x7f00) == TAG_ECKSUM:
|
elif (tag & 0x7f00) == TAG_ECKSUM:
|
||||||
return 'ecksum%s%s%s' % (
|
return 'ecksum%s%s%s' % (
|
||||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
else:
|
else:
|
||||||
return '0x%04x%s%s' % (
|
return '0x%04x%s%s' % (
|
||||||
tag,
|
tag,
|
||||||
' w%d' % w if w is not None else '',
|
' w%d' % w if w is not None else '',
|
||||||
' %d' % size if size is not None else '')
|
' %d' % size if size is not None else '')
|
||||||
|
|
||||||
|
|
||||||
# this type is used for tree representations
|
# this type is used for tree representations
|
||||||
@@ -278,9 +278,9 @@ class Rbyd:
|
|||||||
return '0x%x.%x' % (self.block, self.trunk)
|
return '0x%x.%x' % (self.block, self.trunk)
|
||||||
else:
|
else:
|
||||||
return '0x{%x,%s}.%x' % (
|
return '0x{%x,%s}.%x' % (
|
||||||
self.block,
|
self.block,
|
||||||
','.join('%x' % block for block in self.redund_blocks),
|
','.join('%x' % block for block in self.redund_blocks),
|
||||||
self.trunk)
|
self.trunk)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fetch(cls, f, block_size, blocks, trunk=None):
|
def fetch(cls, f, block_size, blocks, trunk=None):
|
||||||
@@ -289,21 +289,23 @@ class Rbyd:
|
|||||||
|
|
||||||
if len(blocks) > 1:
|
if len(blocks) > 1:
|
||||||
# fetch all blocks
|
# 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
|
# determine most recent revision
|
||||||
i = 0
|
i = 0
|
||||||
for i_, rbyd in enumerate(rbyds):
|
for i_, rbyd in enumerate(rbyds):
|
||||||
# compare with sequence arithmetic
|
# compare with sequence arithmetic
|
||||||
if rbyd and (
|
if rbyd and (
|
||||||
not rbyds[i]
|
not rbyds[i]
|
||||||
or not ((rbyd.rev - rbyds[i].rev) & 0x80000000)
|
or not ((rbyd.rev - rbyds[i].rev) & 0x80000000)
|
||||||
or (rbyd.rev == rbyds[i].rev
|
or (rbyd.rev == rbyds[i].rev
|
||||||
and rbyd.trunk > rbyds[i].trunk)):
|
and rbyd.trunk > rbyds[i].trunk)):
|
||||||
i = i_
|
i = i_
|
||||||
# keep track of the other blocks
|
# keep track of the other blocks
|
||||||
rbyd = rbyds[i]
|
rbyd = rbyds[i]
|
||||||
rbyd.redund_blocks = [rbyds[(i+1+j) % len(rbyds)].block
|
rbyd.redund_blocks = [
|
||||||
for j in range(len(rbyds)-1)]
|
rbyds[(i+1+j) % len(rbyds)].block
|
||||||
|
for j in range(len(rbyds)-1)]
|
||||||
return rbyd
|
return rbyd
|
||||||
else:
|
else:
|
||||||
# block may encode a trunk
|
# block may encode a trunk
|
||||||
@@ -457,7 +459,9 @@ class Rbyd:
|
|||||||
|
|
||||||
done = not tag_ or (rid_, tag_) < (rid, tag)
|
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):
|
def __bool__(self):
|
||||||
return bool(self.trunk)
|
return bool(self.trunk)
|
||||||
@@ -549,8 +553,8 @@ class Rbyd:
|
|||||||
else:
|
else:
|
||||||
if 'h' not in alts[j_]:
|
if 'h' not in alts[j_]:
|
||||||
alts[j_]['h'] = max(
|
alts[j_]['h'] = max(
|
||||||
rec_height(alts[j_]['f']),
|
rec_height(alts[j_]['f']),
|
||||||
rec_height(alts[j_]['nf'])) + 1
|
rec_height(alts[j_]['nf'])) + 1
|
||||||
return alts[j_]['h']
|
return alts[j_]['h']
|
||||||
|
|
||||||
for j_ in alts.keys():
|
for j_ in alts.keys():
|
||||||
@@ -614,10 +618,10 @@ def main(disk, roots=None, *,
|
|||||||
# fetch the root
|
# fetch the root
|
||||||
btree = Rbyd.fetch(f, block_size, roots, trunk)
|
btree = Rbyd.fetch(f, block_size, roots, trunk)
|
||||||
print('btree %s w%d, rev %08x, cksum %08x' % (
|
print('btree %s w%d, rev %08x, cksum %08x' % (
|
||||||
btree.addr(),
|
btree.addr(),
|
||||||
btree.weight,
|
btree.weight,
|
||||||
btree.rev,
|
btree.rev,
|
||||||
btree.cksum))
|
btree.cksum))
|
||||||
|
|
||||||
# look up a bid, while keeping track of the search path
|
# look up a bid, while keeping track of the search path
|
||||||
def btree_lookup(bid, *,
|
def btree_lookup(bid, *,
|
||||||
@@ -642,7 +646,7 @@ def main(disk, roots=None, *,
|
|||||||
w = 0
|
w = 0
|
||||||
for i in it.count():
|
for i in it.count():
|
||||||
done, rid__, tag, w_, j, d, data, _ = rbyd.lookup(
|
done, rid__, tag, w_, j, d, data, _ = rbyd.lookup(
|
||||||
rid_, tag+0x1)
|
rid_, tag+0x1)
|
||||||
if done or (i != 0 and rid__ != rid_):
|
if done or (i != 0 and rid__ != rid_):
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -683,7 +687,7 @@ def main(disk, roots=None, *,
|
|||||||
bid = -1
|
bid = -1
|
||||||
while True:
|
while True:
|
||||||
done, bid, w, rbyd, rid, tags, path = btree_lookup(
|
done, bid, w, rbyd, rid, tags, path = btree_lookup(
|
||||||
bid+1, depth=args.get('depth'))
|
bid+1, depth=args.get('depth'))
|
||||||
if done:
|
if done:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -698,7 +702,7 @@ def main(disk, roots=None, *,
|
|||||||
bid = -1
|
bid = -1
|
||||||
while True:
|
while True:
|
||||||
done, bid, w, rbyd, rid, tags, path = btree_lookup(
|
done, bid, w, rbyd, rid, tags, path = btree_lookup(
|
||||||
bid+1, depth=args.get('depth'))
|
bid+1, depth=args.get('depth'))
|
||||||
if done:
|
if done:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -731,8 +735,8 @@ def main(disk, roots=None, *,
|
|||||||
# connect our branch to the rbyd's root
|
# connect our branch to the rbyd's root
|
||||||
if leaf is not None:
|
if leaf is not None:
|
||||||
root = min(rtree,
|
root = min(rtree,
|
||||||
key=lambda branch: branch.d,
|
key=lambda branch: branch.d,
|
||||||
default=None)
|
default=None)
|
||||||
|
|
||||||
if root is not None:
|
if root is not None:
|
||||||
r_rid, r_tag = root.a
|
r_rid, r_tag = root.a
|
||||||
@@ -758,9 +762,10 @@ def main(disk, roots=None, *,
|
|||||||
|
|
||||||
d_ += max(bdepths.get(d, 0), 1)
|
d_ += max(bdepths.get(d, 0), 1)
|
||||||
leaf = (bid-(w-1), d, rid-(w-1),
|
leaf = (bid-(w-1), d, rid-(w-1),
|
||||||
next((tag for tag, _, _, _ in tags
|
next(
|
||||||
if tag & 0xfff == TAG_BRANCH),
|
(tag for tag, _, _, _ in tags
|
||||||
TAG_BRANCH))
|
if tag & 0xfff == TAG_BRANCH),
|
||||||
|
TAG_BRANCH))
|
||||||
|
|
||||||
# remap branches to leaves if we aren't showing inner branches
|
# remap branches to leaves if we aren't showing inner branches
|
||||||
if not args.get('inner'):
|
if not args.get('inner'):
|
||||||
@@ -813,7 +818,7 @@ def main(disk, roots=None, *,
|
|||||||
bid = -1
|
bid = -1
|
||||||
while True:
|
while True:
|
||||||
done, bid, w, rbyd, rid, tags, path = btree_lookup(
|
done, bid, w, rbyd, rid, tags, path = btree_lookup(
|
||||||
bid+1, depth=args.get('depth'))
|
bid+1, depth=args.get('depth'))
|
||||||
if done:
|
if done:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -836,7 +841,7 @@ def main(disk, roots=None, *,
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
b = (bid-(w-1), d, rid-(w-1),
|
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
|
# remap branches to leaves if we aren't showing
|
||||||
# inner branches
|
# inner branches
|
||||||
@@ -846,8 +851,8 @@ def main(disk, roots=None, *,
|
|||||||
if not tags:
|
if not tags:
|
||||||
continue
|
continue
|
||||||
branches[b] = (
|
branches[b] = (
|
||||||
bid-(w-1), len(path)-1, rid-(w-1),
|
bid-(w-1), len(path)-1, rid-(w-1),
|
||||||
(name if name else tags[0])[0])
|
(name if name else tags[0])[0])
|
||||||
b = branches[b]
|
b = branches[b]
|
||||||
|
|
||||||
# found entry point?
|
# found entry point?
|
||||||
@@ -905,16 +910,16 @@ def main(disk, roots=None, *,
|
|||||||
was = None
|
was = None
|
||||||
for d in range(t_depth):
|
for d in range(t_depth):
|
||||||
t, c, was = branchrepr(
|
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' % (
|
trunk.append('%s%s%s%s' % (
|
||||||
'\x1b[33m' if color and c == 'y'
|
'\x1b[33m' if color and c == 'y'
|
||||||
else '\x1b[31m' if color and c == 'r'
|
else '\x1b[31m' if color and c == 'r'
|
||||||
else '\x1b[90m' if color and c == 'b'
|
else '\x1b[90m' if color and c == 'b'
|
||||||
else '',
|
else '',
|
||||||
t,
|
t,
|
||||||
('>' if was else ' ') if d == t_depth-1 else '',
|
('>' if was else ' ') if d == t_depth-1 else '',
|
||||||
'\x1b[m' if color and c else ''))
|
'\x1b[m' if color and c else ''))
|
||||||
|
|
||||||
return '%s ' % ''.join(trunk)
|
return '%s ' % ''.join(trunk)
|
||||||
|
|
||||||
@@ -931,39 +936,41 @@ def main(disk, roots=None, *,
|
|||||||
# show human-readable representation
|
# show human-readable representation
|
||||||
for i, (tag, j, d, data) in enumerate(tags):
|
for i, (tag, j, d, data) in enumerate(tags):
|
||||||
print('%10s %s%*s %-*s %s' % (
|
print('%10s %s%*s %-*s %s' % (
|
||||||
'%04x.%04x:' % (rbyd.block, rbyd.trunk)
|
'%04x.%04x:' % (rbyd.block, rbyd.trunk)
|
||||||
if prbyd is None or rbyd != prbyd
|
if prbyd is None or rbyd != prbyd
|
||||||
else '',
|
else '',
|
||||||
treerepr(bid, w, bd, rid, tag)
|
treerepr(bid, w, bd, rid, tag)
|
||||||
if args.get('tree')
|
if args.get('tree')
|
||||||
or args.get('rbyd')
|
or args.get('rbyd')
|
||||||
or args.get('btree') else '',
|
or args.get('btree')
|
||||||
2*w_width+1, '' if i != 0
|
else '',
|
||||||
else '%d-%d' % (bid-(w-1), bid) if w > 1
|
2*w_width+1, '' if i != 0
|
||||||
else bid if w > 0
|
else '%d-%d' % (bid-(w-1), bid) if w > 1
|
||||||
else '',
|
else bid if w > 0
|
||||||
21+w_width, tagrepr(
|
else '',
|
||||||
tag, w if i == 0 else 0, len(data), None),
|
21+w_width, tagrepr(
|
||||||
next(xxd(data, 8), '')
|
tag, w if i == 0 else 0, len(data), None),
|
||||||
if not args.get('raw') and not args.get('no_truncate')
|
next(xxd(data, 8), '')
|
||||||
else ''))
|
if not args.get('raw')
|
||||||
|
and not args.get('no_truncate')
|
||||||
|
else ''))
|
||||||
prbyd = rbyd
|
prbyd = rbyd
|
||||||
|
|
||||||
# show on-disk encoding of tags/data
|
# show on-disk encoding of tags/data
|
||||||
if args.get('raw'):
|
if args.get('raw'):
|
||||||
for o, line in enumerate(xxd(rbyd.data[j:j+d])):
|
for o, line in enumerate(xxd(rbyd.data[j:j+d])):
|
||||||
print('%9s: %*s%*s %s' % (
|
print('%9s: %*s%*s %s' % (
|
||||||
'%04x' % (j + o*16),
|
'%04x' % (j + o*16),
|
||||||
t_width, '',
|
t_width, '',
|
||||||
2*w_width+1, '',
|
2*w_width+1, '',
|
||||||
line))
|
line))
|
||||||
if args.get('raw') or args.get('no_truncate'):
|
if args.get('raw') or args.get('no_truncate'):
|
||||||
for o, line in enumerate(xxd(data)):
|
for o, line in enumerate(xxd(data)):
|
||||||
print('%9s: %*s%*s %s' % (
|
print('%9s: %*s%*s %s' % (
|
||||||
'%04x' % (j+d + o*16),
|
'%04x' % (j+d + o*16),
|
||||||
t_width, '',
|
t_width, '',
|
||||||
2*w_width+1, '',
|
2*w_width+1, '',
|
||||||
line))
|
line))
|
||||||
|
|
||||||
|
|
||||||
# traverse and print entries
|
# traverse and print entries
|
||||||
@@ -973,7 +980,7 @@ def main(disk, roots=None, *,
|
|||||||
corrupted = False
|
corrupted = False
|
||||||
while True:
|
while True:
|
||||||
done, bid, w, rbyd, rid, tags, path = btree_lookup(
|
done, bid, w, rbyd, rid, tags, path = btree_lookup(
|
||||||
bid+1, depth=args.get('depth'))
|
bid+1, depth=args.get('depth'))
|
||||||
if done:
|
if done:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -997,11 +1004,11 @@ def main(disk, roots=None, *,
|
|||||||
# corrupted? try to keep printing the tree
|
# corrupted? try to keep printing the tree
|
||||||
if not rbyd:
|
if not rbyd:
|
||||||
print('%04x.%04x: %*s%s%s%s' % (
|
print('%04x.%04x: %*s%s%s%s' % (
|
||||||
rbyd.block, rbyd.trunk,
|
rbyd.block, rbyd.trunk,
|
||||||
t_width, '',
|
t_width, '',
|
||||||
'\x1b[31m' if color else '',
|
'\x1b[31m' if color else '',
|
||||||
'(corrupted rbyd %s)' % rbyd.addr(),
|
'(corrupted rbyd %s)' % rbyd.addr(),
|
||||||
'\x1b[m' if color else ''))
|
'\x1b[m' if color else ''))
|
||||||
prbyd = rbyd
|
prbyd = rbyd
|
||||||
corrupted = True
|
corrupted = True
|
||||||
continue
|
continue
|
||||||
@@ -1020,8 +1027,8 @@ def main(disk, roots=None, *,
|
|||||||
|
|
||||||
if name is not None:
|
if name is not None:
|
||||||
tags = [name] + [(tag, j, d, data)
|
tags = [name] + [(tag, j, d, data)
|
||||||
for tag, j, d, data in tags
|
for tag, j, d, data in tags
|
||||||
if tag & 0x7f00 != TAG_NAME]
|
if tag & 0x7f00 != TAG_NAME]
|
||||||
|
|
||||||
# show the branch
|
# show the branch
|
||||||
dbg_branch(bid, w, rbyd, rid, tags, len(path)-1)
|
dbg_branch(bid, w, rbyd, rid, tags, len(path)-1)
|
||||||
@@ -1034,67 +1041,67 @@ if __name__ == "__main__":
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Debug rbyd B-trees.",
|
description="Debug rbyd B-trees.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'disk',
|
'disk',
|
||||||
help="File containing the block device.")
|
help="File containing the block device.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'roots',
|
'roots',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
type=rbydaddr,
|
type=rbydaddr,
|
||||||
help="Block address of the roots of the tree.")
|
help="Block address of the roots of the tree.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--block-size',
|
'-b', '--block-size',
|
||||||
type=bdgeom,
|
type=bdgeom,
|
||||||
help="Block size/geometry in bytes.")
|
help="Block size/geometry in bytes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--block-count',
|
'--block-count',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Block count in blocks.")
|
help="Block count in blocks.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--trunk',
|
'--trunk',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Use this offset as the trunk of the tree.")
|
help="Use this offset as the trunk of the tree.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--color',
|
'--color',
|
||||||
choices=['never', 'always', 'auto'],
|
choices=['never', 'always', 'auto'],
|
||||||
default='auto',
|
default='auto',
|
||||||
help="When to use terminal colors. Defaults to 'auto'.")
|
help="When to use terminal colors. Defaults to 'auto'.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-r', '--raw',
|
'-r', '--raw',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show the raw data including tag encodings.")
|
help="Show the raw data including tag encodings.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-T', '--no-truncate',
|
'-T', '--no-truncate',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Don't truncate, show the full contents.")
|
help="Don't truncate, show the full contents.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-t', '--tree',
|
'-t', '--tree',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show the underlying rbyd trees.")
|
help="Show the underlying rbyd trees.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-B', '--btree',
|
'-B', '--btree',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show the B-tree.")
|
help="Show the B-tree.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-R', '--rbyd',
|
'-R', '--rbyd',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show the full underlying rbyd trees.")
|
help="Show the full underlying rbyd trees.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-i', '--inner',
|
'-i', '--inner',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show inner branches.")
|
help="Show inner branches.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-z', '--depth',
|
'-z', '--depth',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
const=0,
|
const=0,
|
||||||
help="Depth of tree to show.")
|
help="Depth of tree to show.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-e', '--error-on-corrupt',
|
'-e', '--error-on-corrupt',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Error if B-tree is corrupt.")
|
help="Error if B-tree is corrupt.")
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+48
-45
@@ -66,12 +66,12 @@ def rbydaddr(s):
|
|||||||
def xxd(data, width=16):
|
def xxd(data, width=16):
|
||||||
for i in range(0, len(data), width):
|
for i in range(0, len(data), width):
|
||||||
yield '%-*s %-*s' % (
|
yield '%-*s %-*s' % (
|
||||||
3*width,
|
3*width,
|
||||||
' '.join('%02x' % b for b in data[i:i+width]),
|
' '.join('%02x' % b for b in data[i:i+width]),
|
||||||
width,
|
width,
|
||||||
''.join(
|
''.join(
|
||||||
b if b >= ' ' and b <= '~' else '.'
|
b if b >= ' ' and b <= '~' else '.'
|
||||||
for b in map(chr, data[i:i+width])))
|
for b in map(chr, data[i:i+width])))
|
||||||
|
|
||||||
def crc32c(data, crc=0):
|
def crc32c(data, crc=0):
|
||||||
crc ^= 0xffffffff
|
crc ^= 0xffffffff
|
||||||
@@ -105,19 +105,21 @@ def main(disk, blocks=None, *,
|
|||||||
|
|
||||||
# blocks may also encode offsets
|
# blocks may also encode offsets
|
||||||
blocks, offs, size = (
|
blocks, offs, size = (
|
||||||
[block[0] if isinstance(block, tuple) else block
|
[block[0] if isinstance(block, tuple) else block
|
||||||
for block in blocks],
|
for block in blocks],
|
||||||
[off[0] if isinstance(off, tuple)
|
[off[0] if isinstance(off, tuple)
|
||||||
else off if off is not None
|
else off if off is not None
|
||||||
else size[0] if isinstance(size, tuple) and len(size) > 1
|
else size[0]
|
||||||
else block[1] if isinstance(block, tuple)
|
if isinstance(size, tuple) and len(size) > 1
|
||||||
else None
|
else block[1] if isinstance(block, tuple)
|
||||||
for block in blocks],
|
else None
|
||||||
size[1] - size[0] if isinstance(size, tuple) and len(size) > 1
|
for block in blocks],
|
||||||
else size[0] if isinstance(size, tuple)
|
size[1] - size[0] if isinstance(size, tuple) and len(size) > 1
|
||||||
else size if size is not None
|
else size[0] if isinstance(size, tuple)
|
||||||
else off[1] - off[0] if isinstance(off, tuple) and len(off) > 1
|
else size if size is not None
|
||||||
else block_size)
|
else off[1] - off[0]
|
||||||
|
if isinstance(off, tuple) and len(off) > 1
|
||||||
|
else block_size)
|
||||||
|
|
||||||
# cat the blocks
|
# cat the blocks
|
||||||
for block, off in zip(blocks, offs):
|
for block, off in zip(blocks, offs):
|
||||||
@@ -126,40 +128,41 @@ def main(disk, blocks=None, *,
|
|||||||
sys.stdout.buffer.write(data)
|
sys.stdout.buffer.write(data)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Cat data from a block device.",
|
description="Cat data from a block device.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'disk',
|
'disk',
|
||||||
help="File containing the block device.")
|
help="File containing the block device.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'blocks',
|
'blocks',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
type=rbydaddr,
|
type=rbydaddr,
|
||||||
help="Block address.")
|
help="Block address.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--block-size',
|
'-b', '--block-size',
|
||||||
type=bdgeom,
|
type=bdgeom,
|
||||||
help="Block size/geometry in bytes.")
|
help="Block size/geometry in bytes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--block-count',
|
'--block-count',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Block count in blocks.")
|
help="Block count in blocks.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--off',
|
'--off',
|
||||||
type=lambda x: tuple(
|
type=lambda x: tuple(
|
||||||
int(x, 0) if x.strip() else None
|
int(x, 0) if x.strip() else None
|
||||||
for x in x.split(',')),
|
for x in x.split(',')),
|
||||||
help="Show a specific offset, may be a range.")
|
help="Show a specific offset, may be a range.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-n', '--size',
|
'-n', '--size',
|
||||||
type=lambda x: tuple(
|
type=lambda x: tuple(
|
||||||
int(x, 0) if x.strip() else None
|
int(x, 0) if x.strip() else None
|
||||||
for x in x.split(',')),
|
for x in x.split(',')),
|
||||||
help="Show this many bytes, may be a range.")
|
help="Show this many bytes, may be a range.")
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+14
-13
@@ -37,9 +37,9 @@ def main(errs, *,
|
|||||||
# print
|
# print
|
||||||
for n, e, h in ERRS:
|
for n, e, h in ERRS:
|
||||||
print('%-*s %-*s %s' % (
|
print('%-*s %-*s %s' % (
|
||||||
w[0], 'LFS_ERR_'+n,
|
w[0], 'LFS_ERR_'+n,
|
||||||
w[1], e,
|
w[1], e,
|
||||||
h))
|
h))
|
||||||
|
|
||||||
# find these errors
|
# find these errors
|
||||||
else:
|
else:
|
||||||
@@ -77,20 +77,21 @@ def main(errs, *,
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
print('%s ?' % err)
|
print('%s ?' % err)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Decode littlefs error codes.",
|
description="Decode littlefs error codes.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'errs',
|
'errs',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
help="Error codes or error names to decode.")
|
help="Error codes or error names to decode.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-l', '--list',
|
'-l', '--list',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="List all known error codes.")
|
help="List all known error codes.")
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+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):
|
def xxd(data, width=16):
|
||||||
for i in range(0, len(data), width):
|
for i in range(0, len(data), width):
|
||||||
yield '%-*s %-*s' % (
|
yield '%-*s %-*s' % (
|
||||||
3*width,
|
3*width,
|
||||||
' '.join('%02x' % b for b in data[i:i+width]),
|
' '.join('%02x' % b for b in data[i:i+width]),
|
||||||
width,
|
width,
|
||||||
''.join(
|
''.join(
|
||||||
b if b >= ' ' and b <= '~' else '.'
|
b if b >= ' ' and b <= '~' else '.'
|
||||||
for b in map(chr, data[i:i+width])))
|
for b in map(chr, data[i:i+width])))
|
||||||
|
|
||||||
def tagrepr(tag, w=None, size=None, off=None):
|
def tagrepr(tag, w=None, size=None, off=None):
|
||||||
if (tag & 0x6fff) == TAG_NULL:
|
if (tag & 0x6fff) == TAG_NULL:
|
||||||
return '%snull%s%s' % (
|
return '%snull%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %d' % size if size else '')
|
' %d' % size if size else '')
|
||||||
elif (tag & 0x6f00) == TAG_CONFIG:
|
elif (tag & 0x6f00) == TAG_CONFIG:
|
||||||
return '%s%s%s%s' % (
|
return '%s%s%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
'magic' if (tag & 0xfff) == TAG_MAGIC
|
'magic' if (tag & 0xfff) == TAG_MAGIC
|
||||||
else 'version' if (tag & 0xfff) == TAG_VERSION
|
else 'version' if (tag & 0xfff) == TAG_VERSION
|
||||||
else 'rcompat' if (tag & 0xfff) == TAG_RCOMPAT
|
else 'rcompat' if (tag & 0xfff) == TAG_RCOMPAT
|
||||||
else 'wcompat' if (tag & 0xfff) == TAG_WCOMPAT
|
else 'wcompat' if (tag & 0xfff) == TAG_WCOMPAT
|
||||||
else 'ocompat' if (tag & 0xfff) == TAG_OCOMPAT
|
else 'ocompat' if (tag & 0xfff) == TAG_OCOMPAT
|
||||||
else 'geometry' if (tag & 0xfff) == TAG_GEOMETRY
|
else 'geometry' if (tag & 0xfff) == TAG_GEOMETRY
|
||||||
else 'namelimit' if (tag & 0xfff) == TAG_NAMELIMIT
|
else 'namelimit' if (tag & 0xfff) == TAG_NAMELIMIT
|
||||||
else 'filelimit' if (tag & 0xfff) == TAG_FILELIMIT
|
else 'filelimit' if (tag & 0xfff) == TAG_FILELIMIT
|
||||||
else 'config 0x%02x' % (tag & 0xff),
|
else 'config 0x%02x' % (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x6f00) == TAG_GDELTA:
|
elif (tag & 0x6f00) == TAG_GDELTA:
|
||||||
return '%s%s%s%s' % (
|
return '%s%s%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
'grmdelta' if (tag & 0xfff) == TAG_GRMDELTA
|
'grmdelta' if (tag & 0xfff) == TAG_GRMDELTA
|
||||||
else 'gdelta 0x%02x' % (tag & 0xff),
|
else 'gdelta 0x%02x' % (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x6f00) == TAG_NAME:
|
elif (tag & 0x6f00) == TAG_NAME:
|
||||||
return '%s%s%s%s' % (
|
return '%s%s%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
'name' if (tag & 0xfff) == TAG_NAME
|
'name' if (tag & 0xfff) == TAG_NAME
|
||||||
else 'reg' if (tag & 0xfff) == TAG_REG
|
else 'reg' if (tag & 0xfff) == TAG_REG
|
||||||
else 'dir' if (tag & 0xfff) == TAG_DIR
|
else 'dir' if (tag & 0xfff) == TAG_DIR
|
||||||
else 'orphan' if (tag & 0xfff) == TAG_ORPHAN
|
else 'orphan' if (tag & 0xfff) == TAG_ORPHAN
|
||||||
else 'bookmark' if (tag & 0xfff) == TAG_BOOKMARK
|
else 'bookmark' if (tag & 0xfff) == TAG_BOOKMARK
|
||||||
else 'name 0x%02x' % (tag & 0xff),
|
else 'name 0x%02x' % (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x6f00) == TAG_STRUCT:
|
elif (tag & 0x6f00) == TAG_STRUCT:
|
||||||
return '%s%s%s%s' % (
|
return '%s%s%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
'data' if (tag & 0xfff) == TAG_DATA
|
'data' if (tag & 0xfff) == TAG_DATA
|
||||||
else 'block' if (tag & 0xfff) == TAG_BLOCK
|
else 'block' if (tag & 0xfff) == TAG_BLOCK
|
||||||
else 'bshrub' if (tag & 0xfff) == TAG_BSHRUB
|
else 'bshrub' if (tag & 0xfff) == TAG_BSHRUB
|
||||||
else 'btree' if (tag & 0xfff) == TAG_BTREE
|
else 'btree' if (tag & 0xfff) == TAG_BTREE
|
||||||
else 'mroot' if (tag & 0xfff) == TAG_MROOT
|
else 'mroot' if (tag & 0xfff) == TAG_MROOT
|
||||||
else 'mdir' if (tag & 0xfff) == TAG_MDIR
|
else 'mdir' if (tag & 0xfff) == TAG_MDIR
|
||||||
else 'mtree' if (tag & 0xfff) == TAG_MTREE
|
else 'mtree' if (tag & 0xfff) == TAG_MTREE
|
||||||
else 'did' if (tag & 0xfff) == TAG_DID
|
else 'did' if (tag & 0xfff) == TAG_DID
|
||||||
else 'branch' if (tag & 0xfff) == TAG_BRANCH
|
else 'branch' if (tag & 0xfff) == TAG_BRANCH
|
||||||
else 'struct 0x%02x' % (tag & 0xff),
|
else 'struct 0x%02x' % (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x6e00) == TAG_ATTR:
|
elif (tag & 0x6e00) == TAG_ATTR:
|
||||||
return '%s%sattr 0x%02x%s%s' % (
|
return '%s%sattr 0x%02x%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
's' if tag & 0x100 else 'u',
|
's' if tag & 0x100 else 'u',
|
||||||
((tag & 0x100) >> 1) ^ (tag & 0xff),
|
((tag & 0x100) >> 1) ^ (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif tag & TAG_ALT:
|
elif tag & TAG_ALT:
|
||||||
return 'alt%s%s%s%s%s' % (
|
return 'alt%s%s%s%s%s' % (
|
||||||
'r' if tag & TAG_R else 'b',
|
'r' if tag & TAG_R else 'b',
|
||||||
'a' if tag & 0x0fff == 0 and tag & TAG_GT
|
'a' if tag & 0x0fff == 0 and tag & TAG_GT
|
||||||
else 'n' if tag & 0x0fff == 0
|
else 'n' if tag & 0x0fff == 0
|
||||||
else 'gt' if tag & TAG_GT
|
else 'gt' if tag & TAG_GT
|
||||||
else 'le',
|
else 'le',
|
||||||
' 0x%x' % (tag & 0x0fff) if tag & 0x0fff != 0 else '',
|
' 0x%x' % (tag & 0x0fff) if tag & 0x0fff != 0 else '',
|
||||||
' w%d' % w if w is not None else '',
|
' w%d' % w if w is not None else '',
|
||||||
' 0x%x' % (0xffffffff & (off-size))
|
' 0x%x' % (0xffffffff & (off-size))
|
||||||
if size and off is not None
|
if size and off is not None
|
||||||
else ' -%d' % size if size
|
else ' -%d' % size if size
|
||||||
else '')
|
else '')
|
||||||
elif (tag & 0x7f00) == TAG_CKSUM:
|
elif (tag & 0x7f00) == TAG_CKSUM:
|
||||||
return 'cksum%s%s%s%s%s' % (
|
return 'cksum%s%s%s%s%s' % (
|
||||||
'q' if not tag & 0xfc and tag & TAG_Q else '',
|
'q' if not tag & 0xfc and tag & TAG_Q else '',
|
||||||
'p' if not tag & 0xfc and tag & TAG_P else '',
|
'p' if not tag & 0xfc and tag & TAG_P else '',
|
||||||
' 0x%02x' % (tag & 0xff) if tag & 0xfc else '',
|
' 0x%02x' % (tag & 0xff) if tag & 0xfc else '',
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x7f00) == TAG_NOTE:
|
elif (tag & 0x7f00) == TAG_NOTE:
|
||||||
return 'note%s%s%s' % (
|
return 'note%s%s%s' % (
|
||||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x7f00) == TAG_ECKSUM:
|
elif (tag & 0x7f00) == TAG_ECKSUM:
|
||||||
return 'ecksum%s%s%s' % (
|
return 'ecksum%s%s%s' % (
|
||||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
else:
|
else:
|
||||||
return '0x%04x%s%s' % (
|
return '0x%04x%s%s' % (
|
||||||
tag,
|
tag,
|
||||||
' w%d' % w if w is not None else '',
|
' w%d' % w if w is not None else '',
|
||||||
' %d' % size if size is not None else '')
|
' %d' % size if size is not None else '')
|
||||||
|
|
||||||
|
|
||||||
# this type is used for tree representations
|
# this type is used for tree representations
|
||||||
@@ -293,9 +293,9 @@ class Rbyd:
|
|||||||
return '0x%x.%x' % (self.block, self.trunk)
|
return '0x%x.%x' % (self.block, self.trunk)
|
||||||
else:
|
else:
|
||||||
return '0x{%x,%s}.%x' % (
|
return '0x{%x,%s}.%x' % (
|
||||||
self.block,
|
self.block,
|
||||||
','.join('%x' % block for block in self.redund_blocks),
|
','.join('%x' % block for block in self.redund_blocks),
|
||||||
self.trunk)
|
self.trunk)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fetch(cls, f, block_size, blocks, trunk=None):
|
def fetch(cls, f, block_size, blocks, trunk=None):
|
||||||
@@ -304,21 +304,23 @@ class Rbyd:
|
|||||||
|
|
||||||
if len(blocks) > 1:
|
if len(blocks) > 1:
|
||||||
# fetch all blocks
|
# 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
|
# determine most recent revision
|
||||||
i = 0
|
i = 0
|
||||||
for i_, rbyd in enumerate(rbyds):
|
for i_, rbyd in enumerate(rbyds):
|
||||||
# compare with sequence arithmetic
|
# compare with sequence arithmetic
|
||||||
if rbyd and (
|
if rbyd and (
|
||||||
not rbyds[i]
|
not rbyds[i]
|
||||||
or not ((rbyd.rev - rbyds[i].rev) & 0x80000000)
|
or not ((rbyd.rev - rbyds[i].rev) & 0x80000000)
|
||||||
or (rbyd.rev == rbyds[i].rev
|
or (rbyd.rev == rbyds[i].rev
|
||||||
and rbyd.trunk > rbyds[i].trunk)):
|
and rbyd.trunk > rbyds[i].trunk)):
|
||||||
i = i_
|
i = i_
|
||||||
# keep track of the other blocks
|
# keep track of the other blocks
|
||||||
rbyd = rbyds[i]
|
rbyd = rbyds[i]
|
||||||
rbyd.redund_blocks = [rbyds[(i+1+j) % len(rbyds)].block
|
rbyd.redund_blocks = [
|
||||||
for j in range(len(rbyds)-1)]
|
rbyds[(i+1+j) % len(rbyds)].block
|
||||||
|
for j in range(len(rbyds)-1)]
|
||||||
return rbyd
|
return rbyd
|
||||||
else:
|
else:
|
||||||
# block may encode a trunk
|
# block may encode a trunk
|
||||||
@@ -472,7 +474,9 @@ class Rbyd:
|
|||||||
|
|
||||||
done = not tag_ or (rid_, tag_) < (rid, tag)
|
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):
|
def __bool__(self):
|
||||||
return bool(self.trunk)
|
return bool(self.trunk)
|
||||||
@@ -564,8 +568,8 @@ class Rbyd:
|
|||||||
else:
|
else:
|
||||||
if 'h' not in alts[j_]:
|
if 'h' not in alts[j_]:
|
||||||
alts[j_]['h'] = max(
|
alts[j_]['h'] = max(
|
||||||
rec_height(alts[j_]['f']),
|
rec_height(alts[j_]['f']),
|
||||||
rec_height(alts[j_]['nf'])) + 1
|
rec_height(alts[j_]['nf'])) + 1
|
||||||
return alts[j_]['h']
|
return alts[j_]['h']
|
||||||
|
|
||||||
for j_ in alts.keys():
|
for j_ in alts.keys():
|
||||||
@@ -616,7 +620,7 @@ class Rbyd:
|
|||||||
w = 0
|
w = 0
|
||||||
for i in it.count():
|
for i in it.count():
|
||||||
done, rid__, tag, w_, j, d, data, _ = rbyd.lookup(
|
done, rid__, tag, w_, j, d, data, _ = rbyd.lookup(
|
||||||
rid_, tag+0x1)
|
rid_, tag+0x1)
|
||||||
if done or (i != 0 and rid__ != rid_):
|
if done or (i != 0 and rid__ != rid_):
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -659,7 +663,7 @@ class Rbyd:
|
|||||||
bid = -1
|
bid = -1
|
||||||
while True:
|
while True:
|
||||||
done, bid, w, rbyd_, rid, tags, path = self.btree_lookup(
|
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:
|
if done:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -674,7 +678,7 @@ class Rbyd:
|
|||||||
bid = -1
|
bid = -1
|
||||||
while True:
|
while True:
|
||||||
done, bid, w, rbyd_, rid, tags, path = self.btree_lookup(
|
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:
|
if done:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -707,8 +711,8 @@ class Rbyd:
|
|||||||
# connect our branch to the rbyd's root
|
# connect our branch to the rbyd's root
|
||||||
if leaf is not None:
|
if leaf is not None:
|
||||||
root = min(rtree,
|
root = min(rtree,
|
||||||
key=lambda branch: branch.d,
|
key=lambda branch: branch.d,
|
||||||
default=None)
|
default=None)
|
||||||
|
|
||||||
if root is not None:
|
if root is not None:
|
||||||
r_rid, r_tag = root.a
|
r_rid, r_tag = root.a
|
||||||
@@ -734,9 +738,10 @@ class Rbyd:
|
|||||||
|
|
||||||
d_ += max(bdepths.get(d, 0), 1)
|
d_ += max(bdepths.get(d, 0), 1)
|
||||||
leaf = (bid-(w-1), d, rid-(w-1),
|
leaf = (bid-(w-1), d, rid-(w-1),
|
||||||
next((tag for tag, _, _, _ in tags
|
next(
|
||||||
if tag & 0xfff == TAG_BRANCH),
|
(tag for tag, _, _, _ in tags
|
||||||
TAG_BRANCH))
|
if tag & 0xfff == TAG_BRANCH),
|
||||||
|
TAG_BRANCH))
|
||||||
|
|
||||||
# remap branches to leaves if we aren't showing inner branches
|
# remap branches to leaves if we aren't showing inner branches
|
||||||
if not inner:
|
if not inner:
|
||||||
@@ -793,7 +798,7 @@ class Rbyd:
|
|||||||
bid = -1
|
bid = -1
|
||||||
while True:
|
while True:
|
||||||
done, bid, w, rbyd, rid, tags, path = self.btree_lookup(
|
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:
|
if done:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -816,7 +821,7 @@ class Rbyd:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
b = (bid-(w-1), d, rid-(w-1),
|
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
|
# remap branches to leaves if we aren't showing
|
||||||
# inner branches
|
# inner branches
|
||||||
@@ -826,8 +831,8 @@ class Rbyd:
|
|||||||
if not tags:
|
if not tags:
|
||||||
continue
|
continue
|
||||||
branches[b] = (
|
branches[b] = (
|
||||||
bid-(w-1), len(path)-1, rid-(w-1),
|
bid-(w-1), len(path)-1, rid-(w-1),
|
||||||
(name if name else tags[0])[0])
|
(name if name else tags[0])[0])
|
||||||
b = branches[b]
|
b = branches[b]
|
||||||
|
|
||||||
# found entry point?
|
# found entry point?
|
||||||
@@ -935,8 +940,8 @@ def main(disk, mroots=None, *,
|
|||||||
mbid = -1
|
mbid = -1
|
||||||
while True:
|
while True:
|
||||||
done, mbid, mw, rbyd, rid, tags, path = mtree.btree_lookup(
|
done, mbid, mw, rbyd, rid, tags, path = mtree.btree_lookup(
|
||||||
f, block_size, mbid+1,
|
f, block_size, mbid+1,
|
||||||
depth=args.get('depth', mdepth)-mdepth)
|
depth=args.get('depth', mdepth)-mdepth)
|
||||||
if done:
|
if done:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -947,10 +952,11 @@ def main(disk, mroots=None, *,
|
|||||||
mdir__ = None
|
mdir__ = None
|
||||||
if (not args.get('depth')
|
if (not args.get('depth')
|
||||||
or mdepth+len(path) < args.get('depth')):
|
or mdepth+len(path) < args.get('depth')):
|
||||||
mdir__ = next(((tag, j, d, data)
|
mdir__ = next(
|
||||||
for tag, j, d, data in tags
|
((tag, j, d, data)
|
||||||
if tag == TAG_MDIR),
|
for tag, j, d, data in tags
|
||||||
None)
|
if tag == TAG_MDIR),
|
||||||
|
None)
|
||||||
|
|
||||||
if mdir__:
|
if mdir__:
|
||||||
# fetch the mdir
|
# fetch the mdir
|
||||||
@@ -981,8 +987,8 @@ def main(disk, mroots=None, *,
|
|||||||
# connect branch to our root
|
# connect branch to our root
|
||||||
if d > 0:
|
if d > 0:
|
||||||
root = min(rtree,
|
root = min(rtree,
|
||||||
key=lambda branch: branch.d,
|
key=lambda branch: branch.d,
|
||||||
default=None)
|
default=None)
|
||||||
|
|
||||||
if root:
|
if root:
|
||||||
r_rid, r_tag = root.a
|
r_rid, r_tag = root.a
|
||||||
@@ -1026,8 +1032,8 @@ def main(disk, mroots=None, *,
|
|||||||
|
|
||||||
# connect branch to our root
|
# connect branch to our root
|
||||||
root = min(rtree,
|
root = min(rtree,
|
||||||
key=lambda branch: branch.d,
|
key=lambda branch: branch.d,
|
||||||
default=None)
|
default=None)
|
||||||
|
|
||||||
if root:
|
if root:
|
||||||
r_rid, r_tag = root.a
|
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
|
# compute the mtree's rbyd-tree if there is one
|
||||||
if mtree:
|
if mtree:
|
||||||
tree_, tdepth = mtree.btree_tree(
|
tree_, tdepth = mtree.btree_tree(
|
||||||
f, block_size,
|
f, block_size,
|
||||||
depth=args.get('depth', mdepth)-mdepth,
|
depth=args.get('depth', mdepth)-mdepth,
|
||||||
inner=args.get('inner'),
|
inner=args.get('inner'),
|
||||||
rbyd=args.get('rbyd'))
|
rbyd=args.get('rbyd'))
|
||||||
|
|
||||||
# connect a branch to the root of the tree
|
# connect a branch to the root of the tree
|
||||||
root = min(tree_, key=lambda branch: branch.d, default=None)
|
root = min(tree_, key=lambda branch: branch.d, default=None)
|
||||||
@@ -1086,8 +1092,8 @@ def main(disk, mroots=None, *,
|
|||||||
mbid = -1
|
mbid = -1
|
||||||
while True:
|
while True:
|
||||||
done, mbid, mw, rbyd, rid, tags, path = mtree.btree_lookup(
|
done, mbid, mw, rbyd, rid, tags, path = mtree.btree_lookup(
|
||||||
f, block_size, mbid+1,
|
f, block_size, mbid+1,
|
||||||
depth=args.get('depth', mdepth)-mdepth)
|
depth=args.get('depth', mdepth)-mdepth)
|
||||||
if done:
|
if done:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -1098,10 +1104,11 @@ def main(disk, mroots=None, *,
|
|||||||
mdir__ = None
|
mdir__ = None
|
||||||
if (not args.get('depth')
|
if (not args.get('depth')
|
||||||
or mdepth+len(path) < args.get('depth')):
|
or mdepth+len(path) < args.get('depth')):
|
||||||
mdir__ = next(((tag, j, d, data)
|
mdir__ = next(
|
||||||
for tag, j, d, data in tags
|
((tag, j, d, data)
|
||||||
if tag == TAG_MDIR),
|
for tag, j, d, data in tags
|
||||||
None)
|
if tag == TAG_MDIR),
|
||||||
|
None)
|
||||||
|
|
||||||
if mdir__:
|
if mdir__:
|
||||||
# fetch the mdir
|
# fetch the mdir
|
||||||
@@ -1116,8 +1123,8 @@ def main(disk, mroots=None, *,
|
|||||||
mbid = -1
|
mbid = -1
|
||||||
while True:
|
while True:
|
||||||
done, mbid, mw, rbyd, rid, tags, path = mtree.btree_lookup(
|
done, mbid, mw, rbyd, rid, tags, path = mtree.btree_lookup(
|
||||||
f, block_size, mbid+1,
|
f, block_size, mbid+1,
|
||||||
depth=args.get('depth', mdepth)-mdepth)
|
depth=args.get('depth', mdepth)-mdepth)
|
||||||
if done:
|
if done:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -1128,10 +1135,11 @@ def main(disk, mroots=None, *,
|
|||||||
mdir__ = None
|
mdir__ = None
|
||||||
if (not args.get('depth')
|
if (not args.get('depth')
|
||||||
or mdepth+len(path) < args.get('depth')):
|
or mdepth+len(path) < args.get('depth')):
|
||||||
mdir__ = next(((tag, j, d, data)
|
mdir__ = next(
|
||||||
for tag, j, d, data in tags
|
((tag, j, d, data)
|
||||||
if tag == TAG_MDIR),
|
for tag, j, d, data in tags
|
||||||
None)
|
if tag == TAG_MDIR),
|
||||||
|
None)
|
||||||
|
|
||||||
if mdir__:
|
if mdir__:
|
||||||
# fetch the mdir
|
# fetch the mdir
|
||||||
@@ -1143,19 +1151,19 @@ def main(disk, mroots=None, *,
|
|||||||
|
|
||||||
# connect the root to the mtree
|
# connect the root to the mtree
|
||||||
branch = max(
|
branch = max(
|
||||||
(branch for branch in tree
|
(branch for branch in tree
|
||||||
if branch.b[0] == mbid-(mw-1)),
|
if branch.b[0] == mbid-(mw-1)),
|
||||||
key=lambda branch: branch.d,
|
|
||||||
default=None)
|
|
||||||
if branch:
|
|
||||||
root = min(rtree,
|
|
||||||
key=lambda branch: branch.d,
|
key=lambda branch: branch.d,
|
||||||
default=None)
|
default=None)
|
||||||
|
if branch:
|
||||||
|
root = min(rtree,
|
||||||
|
key=lambda branch: branch.d,
|
||||||
|
default=None)
|
||||||
if root:
|
if root:
|
||||||
r_rid, r_tag = root.a
|
r_rid, r_tag = root.a
|
||||||
else:
|
else:
|
||||||
_, r_rid, r_tag, _, _, _, _, _ = (
|
_, r_rid, r_tag, _, _, _, _, _ = (
|
||||||
mdir_.lookup(-1, 0x1))
|
mdir_.lookup(-1, 0x1))
|
||||||
tree.add(TBranch(
|
tree.add(TBranch(
|
||||||
a=branch.b,
|
a=branch.b,
|
||||||
b=(mbid-(mw-1), len(path), 0, r_rid, r_tag),
|
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
|
# keep track of the original bids, unfortunately because we
|
||||||
# store the bids in the branches we overwrite these
|
# store the bids in the branches we overwrite these
|
||||||
tree = {(branch.b[0] - branch.b[2], branch)
|
tree = {(branch.b[0] - branch.b[2], branch)
|
||||||
for branch in tree}
|
for branch in tree}
|
||||||
|
|
||||||
for bd in reversed(range(b_depth-1)):
|
for bd in reversed(range(b_depth-1)):
|
||||||
# find leaf-roots at this level
|
# 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
|
# compute the mtree's B-tree if there is one
|
||||||
if mtree:
|
if mtree:
|
||||||
tree_, tdepth = mtree.btree_btree(
|
tree_, tdepth = mtree.btree_btree(
|
||||||
f, block_size,
|
f, block_size,
|
||||||
depth=args.get('depth', mdepth)-mdepth,
|
depth=args.get('depth', mdepth)-mdepth,
|
||||||
inner=args.get('inner'))
|
inner=args.get('inner'))
|
||||||
|
|
||||||
# connect a branch to the root of the tree
|
# connect a branch to the root of the tree
|
||||||
root = min(tree_, key=lambda branch: branch.d, default=None)
|
root = min(tree_, key=lambda branch: branch.d, default=None)
|
||||||
@@ -1306,8 +1314,8 @@ def main(disk, mroots=None, *,
|
|||||||
while True:
|
while True:
|
||||||
done, mbid, mw, rbyd, rid, tags, path = (
|
done, mbid, mw, rbyd, rid, tags, path = (
|
||||||
mtree.btree_lookup(
|
mtree.btree_lookup(
|
||||||
f, block_size, mbid+1,
|
f, block_size, mbid+1,
|
||||||
depth=args.get('depth', mdepth)-mdepth))
|
depth=args.get('depth', mdepth)-mdepth))
|
||||||
if done:
|
if done:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -1318,10 +1326,11 @@ def main(disk, mroots=None, *,
|
|||||||
mdir__ = None
|
mdir__ = None
|
||||||
if (not args.get('depth')
|
if (not args.get('depth')
|
||||||
or mdepth+len(path) < args.get('depth')):
|
or mdepth+len(path) < args.get('depth')):
|
||||||
mdir__ = next(((tag, j, d, data)
|
mdir__ = next(
|
||||||
for tag, j, d, data in tags
|
((tag, j, d, data)
|
||||||
if tag == TAG_MDIR),
|
for tag, j, d, data in tags
|
||||||
None)
|
if tag == TAG_MDIR),
|
||||||
|
None)
|
||||||
|
|
||||||
if mdir__:
|
if mdir__:
|
||||||
# fetch the mdir
|
# fetch the mdir
|
||||||
@@ -1332,7 +1341,7 @@ def main(disk, mroots=None, *,
|
|||||||
# find the first entry in the mdir, map branches
|
# find the first entry in the mdir, map branches
|
||||||
# to this entry
|
# to this entry
|
||||||
done, rid, tag, _, j, d, data, _ = (
|
done, rid, tag, _, j, d, data, _ = (
|
||||||
mdir_.lookup(-1, 0x1))
|
mdir_.lookup(-1, 0x1))
|
||||||
|
|
||||||
tree_ = set()
|
tree_ = set()
|
||||||
for branch in tree:
|
for branch in tree:
|
||||||
@@ -1373,8 +1382,8 @@ def main(disk, mroots=None, *,
|
|||||||
for branch in tree):
|
for branch in tree):
|
||||||
return '+-', branch.c, branch.c
|
return '+-', branch.c, branch.c
|
||||||
elif any(branch.d == d
|
elif any(branch.d == d
|
||||||
and x > min(branch.a, branch.b)
|
and x > min(branch.a, branch.b)
|
||||||
and x < max(branch.a, branch.b)
|
and x < max(branch.a, branch.b)
|
||||||
for branch in tree):
|
for branch in tree):
|
||||||
return '|-', branch.c, branch.c
|
return '|-', branch.c, branch.c
|
||||||
elif branch.a < branch.b:
|
elif branch.a < branch.b:
|
||||||
@@ -1397,17 +1406,18 @@ def main(disk, mroots=None, *,
|
|||||||
was = None
|
was = None
|
||||||
for d in range(t_depth):
|
for d in range(t_depth):
|
||||||
t, c, was = branchrepr(
|
t, c, was = branchrepr(
|
||||||
(mbid-max(mw-1, 0), md, mrid-max(mw-1, 0), rid, tag),
|
(mbid-max(mw-1, 0), md,
|
||||||
d, was)
|
mrid-max(mw-1, 0), rid, tag),
|
||||||
|
d, was)
|
||||||
|
|
||||||
trunk.append('%s%s%s%s' % (
|
trunk.append('%s%s%s%s' % (
|
||||||
'\x1b[33m' if color and c == 'y'
|
'\x1b[33m' if color and c == 'y'
|
||||||
else '\x1b[31m' if color and c == 'r'
|
else '\x1b[31m' if color and c == 'r'
|
||||||
else '\x1b[90m' if color and c == 'b'
|
else '\x1b[90m' if color and c == 'b'
|
||||||
else '',
|
else '',
|
||||||
t,
|
t,
|
||||||
('>' if was else ' ') if d == t_depth-1 else '',
|
('>' if was else ' ') if d == t_depth-1 else '',
|
||||||
'\x1b[m' if color and c else ''))
|
'\x1b[m' if color and c else ''))
|
||||||
|
|
||||||
return '%s ' % ''.join(trunk)
|
return '%s ' % ''.join(trunk)
|
||||||
|
|
||||||
@@ -1416,41 +1426,45 @@ def main(disk, mroots=None, *,
|
|||||||
for i, (rid, tag, w, j, d, data) in enumerate(mdir):
|
for i, (rid, tag, w, j, d, data) in enumerate(mdir):
|
||||||
# show human-readable tag representation
|
# show human-readable tag representation
|
||||||
print('%12s %s%s' % (
|
print('%12s %s%s' % (
|
||||||
'{%s}:' % ','.join('%04x' % block
|
'{%s}:' % ','.join('%04x' % block
|
||||||
for block in it.chain([mdir.block],
|
for block in it.chain(
|
||||||
mdir.redund_blocks))
|
[mdir.block],
|
||||||
if i == 0 else '',
|
mdir.redund_blocks))
|
||||||
treerepr(mbid-max(mw-1, 0), 0, md, 0, rid, tag)
|
if i == 0 else '',
|
||||||
if args.get('tree')
|
treerepr(mbid-max(mw-1, 0), 0, md, 0, rid, tag)
|
||||||
or args.get('rbyd')
|
if args.get('tree')
|
||||||
or args.get('btree') else '',
|
or args.get('rbyd')
|
||||||
'%*s %-*s%s' % (
|
or args.get('btree')
|
||||||
2*w_width+1, '%d.%d-%d' % (
|
else '',
|
||||||
mbid//mleaf_weight, rid-(w-1), rid)
|
'%*s %-*s%s' % (
|
||||||
if w > 1 else '%d.%d' % (mbid//mleaf_weight, rid)
|
2*w_width+1, '%d.%d-%d' % (
|
||||||
if w > 0 or i == 0 else '',
|
mbid//mleaf_weight, rid-(w-1), rid)
|
||||||
21+w_width, tagrepr(tag, w, len(data), j),
|
if w > 1
|
||||||
' %s' % next(xxd(data, 8), '')
|
else '%d.%d' % (mbid//mleaf_weight, rid)
|
||||||
if not args.get('raw')
|
if w > 0 or i == 0
|
||||||
and not args.get('no_truncate')
|
else '',
|
||||||
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
|
# show on-disk encoding of tags
|
||||||
if args.get('raw'):
|
if args.get('raw'):
|
||||||
for o, line in enumerate(xxd(mdir.data[j:j+d])):
|
for o, line in enumerate(xxd(mdir.data[j:j+d])):
|
||||||
print('%11s: %*s%*s %s' % (
|
print('%11s: %*s%*s %s' % (
|
||||||
'%04x' % (j + o*16),
|
'%04x' % (j + o*16),
|
||||||
t_width, '',
|
t_width, '',
|
||||||
2*w_width+1, '',
|
2*w_width+1, '',
|
||||||
line))
|
line))
|
||||||
if args.get('raw') or args.get('no_truncate'):
|
if args.get('raw') or args.get('no_truncate'):
|
||||||
if not tag & TAG_ALT:
|
if not tag & TAG_ALT:
|
||||||
for o, line in enumerate(xxd(data)):
|
for o, line in enumerate(xxd(data)):
|
||||||
print('%11s: %*s%*s %s' % (
|
print('%11s: %*s%*s %s' % (
|
||||||
'%04x' % (j+d + o*16),
|
'%04x' % (j+d + o*16),
|
||||||
t_width, '',
|
t_width, '',
|
||||||
2*w_width+1, '',
|
2*w_width+1, '',
|
||||||
line))
|
line))
|
||||||
|
|
||||||
# prbyd here means the last rendered rbyd, we update
|
# prbyd here means the last rendered rbyd, we update
|
||||||
# in dbg_branch to always print interleaved addresses
|
# in dbg_branch to always print interleaved addresses
|
||||||
@@ -1461,59 +1475,61 @@ def main(disk, mroots=None, *,
|
|||||||
# show human-readable representation
|
# show human-readable representation
|
||||||
for i, (tag, j, d, data) in enumerate(tags):
|
for i, (tag, j, d, data) in enumerate(tags):
|
||||||
print('%12s %s%*s %-*s %s' % (
|
print('%12s %s%*s %-*s %s' % (
|
||||||
'%04x.%04x:' % (rbyd.block, rbyd.trunk)
|
'%04x.%04x:' % (rbyd.block, rbyd.trunk)
|
||||||
if prbyd is None or rbyd != prbyd
|
if prbyd is None or rbyd != prbyd
|
||||||
else '',
|
else '',
|
||||||
treerepr(bid, w, bd, rid, 0, tag)
|
treerepr(bid, w, bd, rid, 0, tag)
|
||||||
if args.get('tree')
|
if args.get('tree')
|
||||||
or args.get('rbyd')
|
or args.get('rbyd')
|
||||||
or args.get('btree') else '',
|
or args.get('btree')
|
||||||
2*w_width+1, '' if i != 0
|
else '',
|
||||||
else '%d-%d' % (
|
2*w_width+1, '' if i != 0
|
||||||
|
else '%d-%d' % (
|
||||||
(bid-(w-1))//mleaf_weight,
|
(bid-(w-1))//mleaf_weight,
|
||||||
bid//mleaf_weight)
|
bid//mleaf_weight)
|
||||||
if (w//mleaf_weight) > 1
|
if (w//mleaf_weight) > 1
|
||||||
else bid//mleaf_weight if w > 0
|
else bid//mleaf_weight if w > 0
|
||||||
else '',
|
else '',
|
||||||
21+w_width, tagrepr(
|
21+w_width, tagrepr(
|
||||||
tag, w if i == 0 else 0, len(data), None),
|
tag, w if i == 0 else 0, len(data), None),
|
||||||
next(xxd(data, 8), '')
|
next(xxd(data, 8), '')
|
||||||
if not args.get('raw') and not args.get('no_truncate')
|
if not args.get('raw')
|
||||||
else ''))
|
and not args.get('no_truncate')
|
||||||
|
else ''))
|
||||||
prbyd = rbyd
|
prbyd = rbyd
|
||||||
|
|
||||||
# show on-disk encoding of tags/data
|
# show on-disk encoding of tags/data
|
||||||
if args.get('raw'):
|
if args.get('raw'):
|
||||||
for o, line in enumerate(xxd(rbyd.data[j:j+d])):
|
for o, line in enumerate(xxd(rbyd.data[j:j+d])):
|
||||||
print('%11s: %*s%*s %s' % (
|
print('%11s: %*s%*s %s' % (
|
||||||
'%04x' % (j + o*16),
|
'%04x' % (j + o*16),
|
||||||
t_width, '',
|
t_width, '',
|
||||||
2*w_width+1, '',
|
2*w_width+1, '',
|
||||||
line))
|
line))
|
||||||
if args.get('raw') or args.get('no_truncate'):
|
if args.get('raw') or args.get('no_truncate'):
|
||||||
for o, line in enumerate(xxd(data)):
|
for o, line in enumerate(xxd(data)):
|
||||||
print('%11s: %*s%*s %s' % (
|
print('%11s: %*s%*s %s' % (
|
||||||
'%04x' % (j+d + o*16),
|
'%04x' % (j+d + o*16),
|
||||||
t_width, '',
|
t_width, '',
|
||||||
2*w_width+1, '',
|
2*w_width+1, '',
|
||||||
line))
|
line))
|
||||||
|
|
||||||
|
|
||||||
#### actual debugging begins here
|
#### actual debugging begins here
|
||||||
|
|
||||||
# print some information about the mtree
|
# print some information about the mtree
|
||||||
print('mtree %s w%d.%d, rev %08x, cksum %08x' % (
|
print('mtree %s w%d.%d, rev %08x, cksum %08x' % (
|
||||||
mroot.addr(),
|
mroot.addr(),
|
||||||
bweight//mleaf_weight, 1*mleaf_weight,
|
bweight//mleaf_weight, 1*mleaf_weight,
|
||||||
mroot.rev,
|
mroot.rev,
|
||||||
mroot.cksum))
|
mroot.cksum))
|
||||||
|
|
||||||
# dynamically size the id field
|
# dynamically size the id field
|
||||||
w_width = max(
|
w_width = max(
|
||||||
mt.ceil(mt.log10(max(1, bweight//mleaf_weight)+1)),
|
mt.ceil(mt.log10(max(1, bweight//mleaf_weight)+1)),
|
||||||
mt.ceil(mt.log10(max(1, rweight)+1)),
|
mt.ceil(mt.log10(max(1, rweight)+1)),
|
||||||
# in case of -1.-1
|
# in case of -1.-1
|
||||||
2)
|
2)
|
||||||
|
|
||||||
# show each mroot
|
# show each mroot
|
||||||
prbyd = None
|
prbyd = None
|
||||||
@@ -1525,12 +1541,13 @@ def main(disk, mroots=None, *,
|
|||||||
# corrupted?
|
# corrupted?
|
||||||
if not mroot:
|
if not mroot:
|
||||||
print('{%s}: %s%s%s' % (
|
print('{%s}: %s%s%s' % (
|
||||||
','.join('%04x' % block
|
','.join('%04x' % block
|
||||||
for block in it.chain([mroot.block],
|
for block in it.chain(
|
||||||
mroot.redund_blocks)),
|
[mroot.block],
|
||||||
'\x1b[31m' if color else '',
|
mroot.redund_blocks)),
|
||||||
'(corrupted mroot %s)' % mroot.addr(),
|
'\x1b[31m' if color else '',
|
||||||
'\x1b[m' if color else ''))
|
'(corrupted mroot %s)' % mroot.addr(),
|
||||||
|
'\x1b[m' if color else ''))
|
||||||
corrupted = True
|
corrupted = True
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
@@ -1560,12 +1577,13 @@ def main(disk, mroots=None, *,
|
|||||||
# corrupted?
|
# corrupted?
|
||||||
if not mdir:
|
if not mdir:
|
||||||
print('{%s}: %s%s%s' % (
|
print('{%s}: %s%s%s' % (
|
||||||
','.join('%04x' % block
|
','.join('%04x' % block
|
||||||
for block in it.chain([mdir.block],
|
for block in it.chain(
|
||||||
mdir.redund_blocks)),
|
[mdir.block],
|
||||||
'\x1b[31m' if color else '',
|
mdir.redund_blocks)),
|
||||||
'(corrupted mdir %s)' % mdir.addr(),
|
'\x1b[31m' if color else '',
|
||||||
'\x1b[m' if color else ''))
|
'(corrupted mdir %s)' % mdir.addr(),
|
||||||
|
'\x1b[m' if color else ''))
|
||||||
corrupted = True
|
corrupted = True
|
||||||
else:
|
else:
|
||||||
# show the mdir
|
# show the mdir
|
||||||
@@ -1582,8 +1600,8 @@ def main(disk, mroots=None, *,
|
|||||||
mbid = -1
|
mbid = -1
|
||||||
while True:
|
while True:
|
||||||
done, mbid, mw, rbyd, rid, tags, path = mtree.btree_lookup(
|
done, mbid, mw, rbyd, rid, tags, path = mtree.btree_lookup(
|
||||||
f, block_size, mbid+1,
|
f, block_size, mbid+1,
|
||||||
depth=args.get('depth', mdepth)-mdepth)
|
depth=args.get('depth', mdepth)-mdepth)
|
||||||
if done:
|
if done:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -1607,11 +1625,11 @@ def main(disk, mroots=None, *,
|
|||||||
# corrupted? try to keep printing the tree
|
# corrupted? try to keep printing the tree
|
||||||
if not rbyd:
|
if not rbyd:
|
||||||
print('%11s: %*s%s%s%s' % (
|
print('%11s: %*s%s%s%s' % (
|
||||||
'%04x.%04x' % (rbyd.block, rbyd.trunk),
|
'%04x.%04x' % (rbyd.block, rbyd.trunk),
|
||||||
t_width, '',
|
t_width, '',
|
||||||
'\x1b[31m' if color else '',
|
'\x1b[31m' if color else '',
|
||||||
'(corrupted rbyd %s)' % rbyd.addr(),
|
'(corrupted rbyd %s)' % rbyd.addr(),
|
||||||
'\x1b[m' if color else ''))
|
'\x1b[m' if color else ''))
|
||||||
prbyd = rbyd
|
prbyd = rbyd
|
||||||
corrupted = True
|
corrupted = True
|
||||||
continue
|
continue
|
||||||
@@ -1630,17 +1648,18 @@ def main(disk, mroots=None, *,
|
|||||||
|
|
||||||
if name is not None:
|
if name is not None:
|
||||||
tags = [name] + [(tag, j, d, data)
|
tags = [name] + [(tag, j, d, data)
|
||||||
for tag, j, d, data in tags
|
for tag, j, d, data in tags
|
||||||
if tag & 0x7f00 != TAG_NAME]
|
if tag & 0x7f00 != TAG_NAME]
|
||||||
|
|
||||||
# found an mdir in the tags?
|
# found an mdir in the tags?
|
||||||
mdir__ = None
|
mdir__ = None
|
||||||
if (not args.get('depth')
|
if (not args.get('depth')
|
||||||
or mdepth+len(path) < args.get('depth')):
|
or mdepth+len(path) < args.get('depth')):
|
||||||
mdir__ = next(((tag, j, d, data)
|
mdir__ = next(
|
||||||
for tag, j, d, data in tags
|
((tag, j, d, data)
|
||||||
if tag == TAG_MDIR),
|
for tag, j, d, data in tags
|
||||||
None)
|
if tag == TAG_MDIR),
|
||||||
|
None)
|
||||||
|
|
||||||
# show other btree entries in certain cases
|
# show other btree entries in certain cases
|
||||||
if args.get('inner') or not mdir__:
|
if args.get('inner') or not mdir__:
|
||||||
@@ -1657,13 +1676,14 @@ def main(disk, mroots=None, *,
|
|||||||
# corrupted?
|
# corrupted?
|
||||||
if not mdir_:
|
if not mdir_:
|
||||||
print('{%s}: %*s%s%s%s' % (
|
print('{%s}: %*s%s%s%s' % (
|
||||||
','.join('%04x' % block
|
','.join('%04x' % block
|
||||||
for block in it.chain([mdir_.block],
|
for block in it.chain(
|
||||||
mdir_.redund_blocks)),
|
[mdir_.block],
|
||||||
t_width, '',
|
mdir_.redund_blocks)),
|
||||||
'\x1b[31m' if color else '',
|
t_width, '',
|
||||||
'(corrupted mdir %s)' % mdir_.addr(),
|
'\x1b[31m' if color else '',
|
||||||
'\x1b[m' if color else ''))
|
'(corrupted mdir %s)' % mdir_.addr(),
|
||||||
|
'\x1b[m' if color else ''))
|
||||||
corrupted = True
|
corrupted = True
|
||||||
else:
|
else:
|
||||||
# show the mdir
|
# show the mdir
|
||||||
@@ -1680,63 +1700,63 @@ if __name__ == "__main__":
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Debug littlefs's metadata tree.",
|
description="Debug littlefs's metadata tree.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'disk',
|
'disk',
|
||||||
help="File containing the block device.")
|
help="File containing the block device.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'mroots',
|
'mroots',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
type=rbydaddr,
|
type=rbydaddr,
|
||||||
help="Block address of the mroots. Defaults to 0x{0,1}.")
|
help="Block address of the mroots. Defaults to 0x{0,1}.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--block-size',
|
'-b', '--block-size',
|
||||||
type=bdgeom,
|
type=bdgeom,
|
||||||
help="Block size/geometry in bytes.")
|
help="Block size/geometry in bytes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--block-count',
|
'--block-count',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Block count in blocks.")
|
help="Block count in blocks.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--color',
|
'--color',
|
||||||
choices=['never', 'always', 'auto'],
|
choices=['never', 'always', 'auto'],
|
||||||
default='auto',
|
default='auto',
|
||||||
help="When to use terminal colors. Defaults to 'auto'.")
|
help="When to use terminal colors. Defaults to 'auto'.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-r', '--raw',
|
'-r', '--raw',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show the raw data including tag encodings.")
|
help="Show the raw data including tag encodings.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-T', '--no-truncate',
|
'-T', '--no-truncate',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Don't truncate, show the full contents.")
|
help="Don't truncate, show the full contents.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-t', '--tree',
|
'-t', '--tree',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show the underlying rbyd trees.")
|
help="Show the underlying rbyd trees.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-B', '--btree',
|
'-B', '--btree',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show the underlying B-trees.")
|
help="Show the underlying B-trees.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-R', '--rbyd',
|
'-R', '--rbyd',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show the full underlying rbyd trees.")
|
help="Show the full underlying rbyd trees.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-i', '--inner',
|
'-i', '--inner',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show inner branches.")
|
help="Show inner branches.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-z', '--depth',
|
'-z', '--depth',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
const=0,
|
const=0,
|
||||||
help="Depth of tree to show.")
|
help="Depth of tree to show.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-e', '--error-on-corrupt',
|
'-e', '--error-on-corrupt',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Error if the filesystem is corrupt.")
|
help="Error if the filesystem is corrupt.")
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+248
-239
@@ -7,6 +7,7 @@ import math as mt
|
|||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
|
|
||||||
COLORS = [
|
COLORS = [
|
||||||
'34', # blue
|
'34', # blue
|
||||||
'31', # red
|
'31', # red
|
||||||
@@ -156,108 +157,108 @@ def fromtag(data):
|
|||||||
def xxd(data, width=16):
|
def xxd(data, width=16):
|
||||||
for i in range(0, len(data), width):
|
for i in range(0, len(data), width):
|
||||||
yield '%-*s %-*s' % (
|
yield '%-*s %-*s' % (
|
||||||
3*width,
|
3*width,
|
||||||
' '.join('%02x' % b for b in data[i:i+width]),
|
' '.join('%02x' % b for b in data[i:i+width]),
|
||||||
width,
|
width,
|
||||||
''.join(
|
''.join(
|
||||||
b if b >= ' ' and b <= '~' else '.'
|
b if b >= ' ' and b <= '~' else '.'
|
||||||
for b in map(chr, data[i:i+width])))
|
for b in map(chr, data[i:i+width])))
|
||||||
|
|
||||||
def tagrepr(tag, w=None, size=None, off=None):
|
def tagrepr(tag, w=None, size=None, off=None):
|
||||||
if (tag & 0x6fff) == TAG_NULL:
|
if (tag & 0x6fff) == TAG_NULL:
|
||||||
return '%snull%s%s' % (
|
return '%snull%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %d' % size if size else '')
|
' %d' % size if size else '')
|
||||||
elif (tag & 0x6f00) == TAG_CONFIG:
|
elif (tag & 0x6f00) == TAG_CONFIG:
|
||||||
return '%s%s%s%s' % (
|
return '%s%s%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
'magic' if (tag & 0xfff) == TAG_MAGIC
|
'magic' if (tag & 0xfff) == TAG_MAGIC
|
||||||
else 'version' if (tag & 0xfff) == TAG_VERSION
|
else 'version' if (tag & 0xfff) == TAG_VERSION
|
||||||
else 'rcompat' if (tag & 0xfff) == TAG_RCOMPAT
|
else 'rcompat' if (tag & 0xfff) == TAG_RCOMPAT
|
||||||
else 'wcompat' if (tag & 0xfff) == TAG_WCOMPAT
|
else 'wcompat' if (tag & 0xfff) == TAG_WCOMPAT
|
||||||
else 'ocompat' if (tag & 0xfff) == TAG_OCOMPAT
|
else 'ocompat' if (tag & 0xfff) == TAG_OCOMPAT
|
||||||
else 'geometry' if (tag & 0xfff) == TAG_GEOMETRY
|
else 'geometry' if (tag & 0xfff) == TAG_GEOMETRY
|
||||||
else 'namelimit' if (tag & 0xfff) == TAG_NAMELIMIT
|
else 'namelimit' if (tag & 0xfff) == TAG_NAMELIMIT
|
||||||
else 'filelimit' if (tag & 0xfff) == TAG_FILELIMIT
|
else 'filelimit' if (tag & 0xfff) == TAG_FILELIMIT
|
||||||
else 'config 0x%02x' % (tag & 0xff),
|
else 'config 0x%02x' % (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x6f00) == TAG_GDELTA:
|
elif (tag & 0x6f00) == TAG_GDELTA:
|
||||||
return '%s%s%s%s' % (
|
return '%s%s%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
'grmdelta' if (tag & 0xfff) == TAG_GRMDELTA
|
'grmdelta' if (tag & 0xfff) == TAG_GRMDELTA
|
||||||
else 'gdelta 0x%02x' % (tag & 0xff),
|
else 'gdelta 0x%02x' % (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x6f00) == TAG_NAME:
|
elif (tag & 0x6f00) == TAG_NAME:
|
||||||
return '%s%s%s%s' % (
|
return '%s%s%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
'name' if (tag & 0xfff) == TAG_NAME
|
'name' if (tag & 0xfff) == TAG_NAME
|
||||||
else 'reg' if (tag & 0xfff) == TAG_REG
|
else 'reg' if (tag & 0xfff) == TAG_REG
|
||||||
else 'dir' if (tag & 0xfff) == TAG_DIR
|
else 'dir' if (tag & 0xfff) == TAG_DIR
|
||||||
else 'orphan' if (tag & 0xfff) == TAG_ORPHAN
|
else 'orphan' if (tag & 0xfff) == TAG_ORPHAN
|
||||||
else 'bookmark' if (tag & 0xfff) == TAG_BOOKMARK
|
else 'bookmark' if (tag & 0xfff) == TAG_BOOKMARK
|
||||||
else 'name 0x%02x' % (tag & 0xff),
|
else 'name 0x%02x' % (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x6f00) == TAG_STRUCT:
|
elif (tag & 0x6f00) == TAG_STRUCT:
|
||||||
return '%s%s%s%s' % (
|
return '%s%s%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
'data' if (tag & 0xfff) == TAG_DATA
|
'data' if (tag & 0xfff) == TAG_DATA
|
||||||
else 'block' if (tag & 0xfff) == TAG_BLOCK
|
else 'block' if (tag & 0xfff) == TAG_BLOCK
|
||||||
else 'bshrub' if (tag & 0xfff) == TAG_BSHRUB
|
else 'bshrub' if (tag & 0xfff) == TAG_BSHRUB
|
||||||
else 'btree' if (tag & 0xfff) == TAG_BTREE
|
else 'btree' if (tag & 0xfff) == TAG_BTREE
|
||||||
else 'mroot' if (tag & 0xfff) == TAG_MROOT
|
else 'mroot' if (tag & 0xfff) == TAG_MROOT
|
||||||
else 'mdir' if (tag & 0xfff) == TAG_MDIR
|
else 'mdir' if (tag & 0xfff) == TAG_MDIR
|
||||||
else 'mtree' if (tag & 0xfff) == TAG_MTREE
|
else 'mtree' if (tag & 0xfff) == TAG_MTREE
|
||||||
else 'did' if (tag & 0xfff) == TAG_DID
|
else 'did' if (tag & 0xfff) == TAG_DID
|
||||||
else 'branch' if (tag & 0xfff) == TAG_BRANCH
|
else 'branch' if (tag & 0xfff) == TAG_BRANCH
|
||||||
else 'struct 0x%02x' % (tag & 0xff),
|
else 'struct 0x%02x' % (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x6e00) == TAG_ATTR:
|
elif (tag & 0x6e00) == TAG_ATTR:
|
||||||
return '%s%sattr 0x%02x%s%s' % (
|
return '%s%sattr 0x%02x%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
's' if tag & 0x100 else 'u',
|
's' if tag & 0x100 else 'u',
|
||||||
((tag & 0x100) >> 1) ^ (tag & 0xff),
|
((tag & 0x100) >> 1) ^ (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif tag & TAG_ALT:
|
elif tag & TAG_ALT:
|
||||||
return 'alt%s%s%s%s%s' % (
|
return 'alt%s%s%s%s%s' % (
|
||||||
'r' if tag & TAG_R else 'b',
|
'r' if tag & TAG_R else 'b',
|
||||||
'a' if tag & 0x0fff == 0 and tag & TAG_GT
|
'a' if tag & 0x0fff == 0 and tag & TAG_GT
|
||||||
else 'n' if tag & 0x0fff == 0
|
else 'n' if tag & 0x0fff == 0
|
||||||
else 'gt' if tag & TAG_GT
|
else 'gt' if tag & TAG_GT
|
||||||
else 'le',
|
else 'le',
|
||||||
' 0x%x' % (tag & 0x0fff) if tag & 0x0fff != 0 else '',
|
' 0x%x' % (tag & 0x0fff) if tag & 0x0fff != 0 else '',
|
||||||
' w%d' % w if w is not None else '',
|
' w%d' % w if w is not None else '',
|
||||||
' 0x%x' % (0xffffffff & (off-size))
|
' 0x%x' % (0xffffffff & (off-size))
|
||||||
if size and off is not None
|
if size and off is not None
|
||||||
else ' -%d' % size if size
|
else ' -%d' % size if size
|
||||||
else '')
|
else '')
|
||||||
elif (tag & 0x7f00) == TAG_CKSUM:
|
elif (tag & 0x7f00) == TAG_CKSUM:
|
||||||
return 'cksum%s%s%s%s%s' % (
|
return 'cksum%s%s%s%s%s' % (
|
||||||
'q' if not tag & 0xfc and tag & TAG_Q else '',
|
'q' if not tag & 0xfc and tag & TAG_Q else '',
|
||||||
'p' if not tag & 0xfc and tag & TAG_P else '',
|
'p' if not tag & 0xfc and tag & TAG_P else '',
|
||||||
' 0x%02x' % (tag & 0xff) if tag & 0xfc else '',
|
' 0x%02x' % (tag & 0xff) if tag & 0xfc else '',
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x7f00) == TAG_NOTE:
|
elif (tag & 0x7f00) == TAG_NOTE:
|
||||||
return 'note%s%s%s' % (
|
return 'note%s%s%s' % (
|
||||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x7f00) == TAG_ECKSUM:
|
elif (tag & 0x7f00) == TAG_ECKSUM:
|
||||||
return 'ecksum%s%s%s' % (
|
return 'ecksum%s%s%s' % (
|
||||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
else:
|
else:
|
||||||
return '0x%04x%s%s' % (
|
return '0x%04x%s%s' % (
|
||||||
tag,
|
tag,
|
||||||
' w%d' % w if w is not None else '',
|
' w%d' % w if w is not None else '',
|
||||||
' %d' % size if size is not None else '')
|
' %d' % size if size is not None else '')
|
||||||
|
|
||||||
|
|
||||||
def dbg_log(data, block_size, rev, eoff, weight, *,
|
def dbg_log(data, block_size, rev, eoff, weight, *,
|
||||||
@@ -291,9 +292,9 @@ def dbg_log(data, block_size, rev, eoff, weight, *,
|
|||||||
x = 0
|
x = 0
|
||||||
while any(
|
while any(
|
||||||
max(a, b) >= min(a_, b_)
|
max(a, b) >= min(a_, b_)
|
||||||
and max(a_, b_) >= min(a, b)
|
and max(a_, b_) >= min(a, b)
|
||||||
and x == x_
|
and x == x_
|
||||||
for a_, b_, x_, _ in jumps[:j]):
|
for a_, b_, x_, _ in jumps[:j]):
|
||||||
x += 1
|
x += 1
|
||||||
jumps[j] = a, b, x, c
|
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:
|
for a, b, x, c in jumps:
|
||||||
c_start = (
|
c_start = (
|
||||||
'\x1b[33m' if color and c == 'y'
|
'\x1b[33m' if color and c == 'y'
|
||||||
else '\x1b[31m' if color and c == 'r'
|
else '\x1b[31m' if color and c == 'r'
|
||||||
else '\x1b[90m' if color
|
else '\x1b[90m' if color
|
||||||
else '')
|
else '')
|
||||||
c_stop = '\x1b[m' if color else ''
|
c_stop = '\x1b[m' if color else ''
|
||||||
|
|
||||||
if j == a:
|
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)
|
chars[2*x+1] = '%s|%s' % (c_start, c_stop)
|
||||||
|
|
||||||
return ''.join(chars.get(x, ' ')
|
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
|
# preprocess lifetimes
|
||||||
lifetime_width = 0
|
lifetime_width = 0
|
||||||
@@ -333,7 +334,7 @@ def dbg_log(data, block_size, rev, eoff, weight, *,
|
|||||||
self.tags = set()
|
self.tags = set()
|
||||||
self.color = COLORS[self.__class__.color_i]
|
self.color = COLORS[self.__class__.color_i]
|
||||||
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):
|
def add(self, j):
|
||||||
self.tags.add(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):
|
def checkpoint(j, weights, lifetimes, grows, shrinks, tags):
|
||||||
checkpoint_js.append(j)
|
checkpoint_js.append(j)
|
||||||
checkpoints.append((
|
checkpoints.append((
|
||||||
weights.copy(), lifetimes.copy(),
|
weights.copy(), lifetimes.copy(),
|
||||||
grows, shrinks, tags))
|
grows, shrinks, tags))
|
||||||
|
|
||||||
lower_, upper_ = 0, 0
|
lower_, upper_ = 0, 0
|
||||||
weight_ = 0
|
weight_ = 0
|
||||||
@@ -397,7 +398,7 @@ def dbg_log(data, block_size, rev, eoff, weight, *,
|
|||||||
if rid_ > 0:
|
if rid_ > 0:
|
||||||
weights[i:i+1] = [rid_, delta, weights[i]-rid_]
|
weights[i:i+1] = [rid_, delta, weights[i]-rid_]
|
||||||
lifetimes[i:i+1] = [
|
lifetimes[i:i+1] = [
|
||||||
lifetimes[i], Lifetime(j), lifetimes[i]]
|
lifetimes[i], Lifetime(j), lifetimes[i]]
|
||||||
else:
|
else:
|
||||||
weights[i:i] = [delta]
|
weights[i:i] = [delta]
|
||||||
lifetimes[i:i] = [Lifetime(j)]
|
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})
|
checkpoint(j, weights, lifetimes, set(), set(), {i})
|
||||||
|
|
||||||
lifetime_width = 2*max((
|
lifetime_width = 2*max((
|
||||||
sum(1 for lifetime in lifetimes if lifetime)
|
sum(1 for lifetime in lifetimes if lifetime)
|
||||||
for _, lifetimes, _, _, _ in checkpoints),
|
for _, lifetimes, _, _, _ in checkpoints),
|
||||||
default=0)
|
default=0)
|
||||||
|
|
||||||
def lifetimerepr(j):
|
def lifetimerepr(j):
|
||||||
x = bisect.bisect(checkpoint_js, j)-1
|
x = bisect.bisect(checkpoint_js, j)-1
|
||||||
@@ -476,12 +477,12 @@ def dbg_log(data, block_size, rev, eoff, weight, *,
|
|||||||
colors.append(lifetime.color)
|
colors.append(lifetime.color)
|
||||||
|
|
||||||
return '%s%*s' % (
|
return '%s%*s' % (
|
||||||
''.join('%s%s%s' % (
|
''.join('%s%s%s' % (
|
||||||
'\x1b[%sm' % c if color else '',
|
'\x1b[%sm' % c if color else '',
|
||||||
r,
|
r,
|
||||||
'\x1b[m' if color else '')
|
'\x1b[m' if color else '')
|
||||||
for r, c in zip(reprs, colors)),
|
for r, c in zip(reprs, colors)),
|
||||||
lifetime_width - sum(len(r) for r in reprs), '')
|
lifetime_width - sum(len(r) for r in reprs), '')
|
||||||
|
|
||||||
|
|
||||||
# dynamically size the id field
|
# dynamically size the id field
|
||||||
@@ -518,10 +519,10 @@ def dbg_log(data, block_size, rev, eoff, weight, *,
|
|||||||
# print revision count
|
# print revision count
|
||||||
if args.get('raw'):
|
if args.get('raw'):
|
||||||
print('%8s: %*s%*s %s' % (
|
print('%8s: %*s%*s %s' % (
|
||||||
'%04x' % 0,
|
'%04x' % 0,
|
||||||
lifetime_width, '',
|
lifetime_width, '',
|
||||||
2*w_width+1, '',
|
2*w_width+1, '',
|
||||||
next(xxd(data[0:4]))))
|
next(xxd(data[0:4]))))
|
||||||
|
|
||||||
# print tags
|
# print tags
|
||||||
cksum = crc32c(data[0:4])
|
cksum = crc32c(data[0:4])
|
||||||
@@ -582,44 +583,49 @@ def dbg_log(data, block_size, rev, eoff, weight, *,
|
|||||||
|
|
||||||
# show human-readable tag representation
|
# show human-readable tag representation
|
||||||
print('%s%08x:%s %*s%s%*s %-*s%s%s%s' % (
|
print('%s%08x:%s %*s%s%*s %-*s%s%s%s' % (
|
||||||
'\x1b[90m' if color and j >= eoff else '',
|
'\x1b[90m' if color and j >= eoff else '',
|
||||||
j,
|
j,
|
||||||
'\x1b[m' if color and j >= eoff else '',
|
'\x1b[m' if color and j >= eoff else '',
|
||||||
lifetime_width, lifetimerepr(j) if args.get('lifetimes') else '',
|
lifetime_width, lifetimerepr(j)
|
||||||
'\x1b[90m' if color and j >= eoff else '',
|
if args.get('lifetimes')
|
||||||
2*w_width+1, '' if (tag & 0xe000) != 0x0000
|
else '',
|
||||||
else '%d-%d' % (rid-(w-1), rid) if w > 1
|
'\x1b[90m' if color and j >= eoff else '',
|
||||||
else rid,
|
2*w_width+1, '' if (tag & 0xe000) != 0x0000
|
||||||
56+w_width, '%-*s %s' % (
|
else '%d-%d' % (rid-(w-1), rid) if w > 1
|
||||||
21+w_width, tagrepr(tag, w, size, j),
|
else rid,
|
||||||
next(xxd(data[j+d:j+d+min(size, 8)], 8), '')
|
56+w_width, '%-*s %s' % (
|
||||||
if not args.get('raw') and not args.get('no_truncate')
|
21+w_width, tagrepr(tag, w, size, j),
|
||||||
and not tag & TAG_ALT else ''),
|
next(xxd(data[j+d:j+d+min(size, 8)], 8), '')
|
||||||
' (%s)' % ', '.join(notes) if notes else '',
|
if not args.get('raw')
|
||||||
'\x1b[m' if color and j >= eoff else '',
|
and not args.get('no_truncate')
|
||||||
' %s' % jumprepr(j)
|
and not tag & TAG_ALT
|
||||||
if args.get('jumps') and not notes else ''))
|
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
|
# show on-disk encoding of tags
|
||||||
if args.get('raw'):
|
if args.get('raw'):
|
||||||
for o, line in enumerate(xxd(data[j:j+d])):
|
for o, line in enumerate(xxd(data[j:j+d])):
|
||||||
print('%s%8s: %*s%*s %s%s' % (
|
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 '',
|
'\x1b[90m' if color and j >= eoff else '',
|
||||||
'%04x' % (j+d + o*16),
|
'%04x' % (j + o*16),
|
||||||
lifetime_width, '',
|
lifetime_width, '',
|
||||||
2*w_width+1, '',
|
2*w_width+1, '',
|
||||||
line,
|
line,
|
||||||
'\x1b[m' if color and j >= eoff else ''))
|
'\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, *,
|
def dbg_tree(data, block_size, rev, trunk, weight, *,
|
||||||
@@ -757,8 +763,8 @@ def dbg_tree(data, block_size, rev, trunk, weight, *,
|
|||||||
else:
|
else:
|
||||||
if 'h' not in alts[j_]:
|
if 'h' not in alts[j_]:
|
||||||
alts[j_]['h'] = max(
|
alts[j_]['h'] = max(
|
||||||
rec_height(alts[j_]['f']),
|
rec_height(alts[j_]['f']),
|
||||||
rec_height(alts[j_]['nf'])) + 1
|
rec_height(alts[j_]['nf'])) + 1
|
||||||
return alts[j_]['h']
|
return alts[j_]['h']
|
||||||
|
|
||||||
for j_ in alts.keys():
|
for j_ in alts.keys():
|
||||||
@@ -801,8 +807,8 @@ def dbg_tree(data, block_size, rev, trunk, weight, *,
|
|||||||
for branch in tree):
|
for branch in tree):
|
||||||
return '+-', branch.c, branch.c
|
return '+-', branch.c, branch.c
|
||||||
elif any(branch.d == d
|
elif any(branch.d == d
|
||||||
and x > min(branch.a, branch.b)
|
and x > min(branch.a, branch.b)
|
||||||
and x < max(branch.a, branch.b)
|
and x < max(branch.a, branch.b)
|
||||||
for branch in tree):
|
for branch in tree):
|
||||||
return '|-', branch.c, branch.c
|
return '|-', branch.c, branch.c
|
||||||
elif branch.a < branch.b:
|
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)
|
t, c, was = branchrepr((rid, tag), d, was)
|
||||||
|
|
||||||
trunk.append('%s%s%s%s' % (
|
trunk.append('%s%s%s%s' % (
|
||||||
'\x1b[33m' if color and c == 'y'
|
'\x1b[33m' if color and c == 'y'
|
||||||
else '\x1b[31m' if color and c == 'r'
|
else '\x1b[31m' if color and c == 'r'
|
||||||
else '\x1b[90m' if color and c == 'b'
|
else '\x1b[90m' if color and c == 'b'
|
||||||
else '',
|
else '',
|
||||||
t,
|
t,
|
||||||
('>' if was else ' ') if d == t_depth-1 else '',
|
('>' if was else ' ') if d == t_depth-1 else '',
|
||||||
'\x1b[m' if color and c else ''))
|
'\x1b[m' if color and c else ''))
|
||||||
|
|
||||||
return '%s ' % ''.join(trunk)
|
return '%s ' % ''.join(trunk)
|
||||||
|
|
||||||
@@ -850,33 +856,36 @@ def dbg_tree(data, block_size, rev, trunk, weight, *,
|
|||||||
|
|
||||||
# show human-readable tag representation
|
# show human-readable tag representation
|
||||||
print('%08x: %s%*s %-*s %s' % (
|
print('%08x: %s%*s %-*s %s' % (
|
||||||
j,
|
j,
|
||||||
treerepr(rid, tag)
|
treerepr(rid, tag)
|
||||||
if args.get('tree') or args.get('rbyd') else '',
|
if args.get('tree') or args.get('rbyd')
|
||||||
2*w_width+1, '%d-%d' % (rid-(w-1), rid)
|
else '',
|
||||||
if w > 1 else rid
|
2*w_width+1, '%d-%d' % (rid-(w-1), rid) if w > 1
|
||||||
if w > 0 or i == 0 else '',
|
else rid if w > 0 or i == 0
|
||||||
21+w_width, tagrepr(tag, w, size, j),
|
else '',
|
||||||
next(xxd(data[j+d:j+d+min(size, 8)], 8), '')
|
21+w_width, tagrepr(tag, w, size, j),
|
||||||
if not args.get('raw') and not args.get('no_truncate')
|
next(xxd(data[j+d:j+d+min(size, 8)], 8), '')
|
||||||
and not tag & TAG_ALT else ''))
|
if not args.get('raw')
|
||||||
|
and not args.get('no_truncate')
|
||||||
|
and not tag & TAG_ALT
|
||||||
|
else ''))
|
||||||
|
|
||||||
# show on-disk encoding of tags
|
# show on-disk encoding of tags
|
||||||
if args.get('raw'):
|
if args.get('raw'):
|
||||||
for o, line in enumerate(xxd(data[j:j+d])):
|
for o, line in enumerate(xxd(data[j:j+d])):
|
||||||
print('%8s: %*s%*s %s' % (
|
print('%8s: %*s%*s %s' % (
|
||||||
'%04x' % (j + o*16),
|
'%04x' % (j + o*16),
|
||||||
t_width, '',
|
t_width, '',
|
||||||
2*w_width+1, '',
|
2*w_width+1, '',
|
||||||
line))
|
line))
|
||||||
if args.get('raw') or args.get('no_truncate'):
|
if args.get('raw') or args.get('no_truncate'):
|
||||||
if not tag & TAG_ALT:
|
if not tag & TAG_ALT:
|
||||||
for o, line in enumerate(xxd(data[j+d:j+d+size])):
|
for o, line in enumerate(xxd(data[j+d:j+d+size])):
|
||||||
print('%8s: %*s%*s %s' % (
|
print('%8s: %*s%*s %s' % (
|
||||||
'%04x' % (j+d + o*16),
|
'%04x' % (j+d + o*16),
|
||||||
t_width, '',
|
t_width, '',
|
||||||
2*w_width+1, '',
|
2*w_width+1, '',
|
||||||
line))
|
line))
|
||||||
|
|
||||||
|
|
||||||
def main(disk, blocks=None, *,
|
def main(disk, blocks=None, *,
|
||||||
@@ -912,12 +921,12 @@ def main(disk, blocks=None, *,
|
|||||||
|
|
||||||
# blocks may also encode trunks
|
# blocks may also encode trunks
|
||||||
blocks, trunks = (
|
blocks, trunks = (
|
||||||
[block[0] if isinstance(block, tuple) else block
|
[block[0] if isinstance(block, tuple) else block
|
||||||
for block in blocks],
|
for block in blocks],
|
||||||
[trunk if trunk is not None
|
[trunk if trunk is not None
|
||||||
else block[1] if isinstance(block, tuple)
|
else block[1] if isinstance(block, tuple)
|
||||||
else None
|
else None
|
||||||
for block in blocks])
|
for block in blocks])
|
||||||
|
|
||||||
# read each block
|
# read each block
|
||||||
datas = []
|
datas = []
|
||||||
@@ -1020,41 +1029,41 @@ def main(disk, blocks=None, *,
|
|||||||
# compare with sequence arithmetic
|
# compare with sequence arithmetic
|
||||||
if trunk_ and (
|
if trunk_ and (
|
||||||
not trunks_[i]
|
not trunks_[i]
|
||||||
or not ((rev - revs[i]) & 0x80000000)
|
or not ((rev - revs[i]) & 0x80000000)
|
||||||
or (rev == revs[i] and trunk_ > trunks_[i])):
|
or (rev == revs[i] and trunk_ > trunks_[i])):
|
||||||
i = i_
|
i = i_
|
||||||
|
|
||||||
# print contents of the winning metadata block
|
# print contents of the winning metadata block
|
||||||
block, data, rev, eoff, trunk_, weight, cksum = (
|
block, data, rev, eoff, trunk_, weight, cksum = (
|
||||||
blocks[i],
|
blocks[i],
|
||||||
datas[i],
|
datas[i],
|
||||||
revs[i],
|
revs[i],
|
||||||
eoffs[i],
|
eoffs[i],
|
||||||
trunks_[i],
|
trunks_[i],
|
||||||
weights[i],
|
weights[i],
|
||||||
cksums[i])
|
cksums[i])
|
||||||
|
|
||||||
print('rbyd %s w%d, rev %08x, size %d, cksum %08x' % (
|
print('rbyd %s w%d, rev %08x, size %d, cksum %08x' % (
|
||||||
'0x%x.%x' % (block, trunk_)
|
'0x%x.%x' % (block, trunk_)
|
||||||
if len(blocks) == 1
|
if len(blocks) == 1
|
||||||
else '0x{%x,%s}.%x' % (
|
else '0x{%x,%s}.%x' % (
|
||||||
block,
|
block,
|
||||||
','.join('%x' % blocks[(i+1+j) % len(blocks)]
|
','.join('%x' % blocks[(i+1+j) % len(blocks)]
|
||||||
for j in range(len(blocks)-1)),
|
for j in range(len(blocks)-1)),
|
||||||
trunk_),
|
trunk_),
|
||||||
weight,
|
weight,
|
||||||
rev,
|
rev,
|
||||||
eoff,
|
eoff,
|
||||||
cksum))
|
cksum))
|
||||||
|
|
||||||
if args.get('log'):
|
if args.get('log'):
|
||||||
dbg_log(data, block_size, rev, eoff, weight,
|
dbg_log(data, block_size, rev, eoff, weight,
|
||||||
color=color,
|
color=color,
|
||||||
**args)
|
**args)
|
||||||
else:
|
else:
|
||||||
dbg_tree(data, block_size, rev, trunk_, weight,
|
dbg_tree(data, block_size, rev, trunk_, weight,
|
||||||
color=color,
|
color=color,
|
||||||
**args)
|
**args)
|
||||||
|
|
||||||
if args.get('error_on_corrupt') and eoff == 0:
|
if args.get('error_on_corrupt') and eoff == 0:
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
@@ -1064,69 +1073,69 @@ if __name__ == "__main__":
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Debug rbyd metadata.",
|
description="Debug rbyd metadata.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'disk',
|
'disk',
|
||||||
help="File containing the block device.")
|
help="File containing the block device.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'blocks',
|
'blocks',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
type=rbydaddr,
|
type=rbydaddr,
|
||||||
help="Block address of metadata blocks.")
|
help="Block address of metadata blocks.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--block-size',
|
'-b', '--block-size',
|
||||||
type=bdgeom,
|
type=bdgeom,
|
||||||
help="Block size/geometry in bytes.")
|
help="Block size/geometry in bytes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--block-count',
|
'--block-count',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Block count in blocks.")
|
help="Block count in blocks.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--trunk',
|
'--trunk',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Use this offset as the trunk of the tree.")
|
help="Use this offset as the trunk of the tree.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--color',
|
'--color',
|
||||||
choices=['never', 'always', 'auto'],
|
choices=['never', 'always', 'auto'],
|
||||||
default='auto',
|
default='auto',
|
||||||
help="When to use terminal colors. Defaults to 'auto'.")
|
help="When to use terminal colors. Defaults to 'auto'.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-a', '--all',
|
'-a', '--all',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Don't stop parsing on bad commits.")
|
help="Don't stop parsing on bad commits.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-l', '--log',
|
'-l', '--log',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show the raw tags as they appear in the log.")
|
help="Show the raw tags as they appear in the log.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-r', '--raw',
|
'-r', '--raw',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show the raw data including tag encodings.")
|
help="Show the raw data including tag encodings.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-T', '--no-truncate',
|
'-T', '--no-truncate',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Don't truncate, show the full contents.")
|
help="Don't truncate, show the full contents.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-t', '--tree',
|
'-t', '--tree',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show the rbyd tree.")
|
help="Show the rbyd tree.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-R', '--rbyd',
|
'-R', '--rbyd',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show the full rbyd tree.")
|
help="Show the full rbyd tree.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-j', '--jumps',
|
'-j', '--jumps',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show alt pointer jumps in the margin.")
|
help="Show alt pointer jumps in the margin.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-g', '--lifetimes',
|
'-g', '--lifetimes',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show inserts/deletes of ids in the margin.")
|
help="Show inserts/deletes of ids in the margin.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-e', '--error-on-corrupt',
|
'-e', '--error-on-corrupt',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Error if no valid commit is found.")
|
help="Error if no valid commit is found.")
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+107
-106
@@ -121,98 +121,98 @@ def fromleb128(data):
|
|||||||
def tagrepr(tag, w=None, size=None, off=None):
|
def tagrepr(tag, w=None, size=None, off=None):
|
||||||
if (tag & 0x6fff) == TAG_NULL:
|
if (tag & 0x6fff) == TAG_NULL:
|
||||||
return '%snull%s%s' % (
|
return '%snull%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %d' % size if size else '')
|
' %d' % size if size else '')
|
||||||
elif (tag & 0x6f00) == TAG_CONFIG:
|
elif (tag & 0x6f00) == TAG_CONFIG:
|
||||||
return '%s%s%s%s' % (
|
return '%s%s%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
'magic' if (tag & 0xfff) == TAG_MAGIC
|
'magic' if (tag & 0xfff) == TAG_MAGIC
|
||||||
else 'version' if (tag & 0xfff) == TAG_VERSION
|
else 'version' if (tag & 0xfff) == TAG_VERSION
|
||||||
else 'rcompat' if (tag & 0xfff) == TAG_RCOMPAT
|
else 'rcompat' if (tag & 0xfff) == TAG_RCOMPAT
|
||||||
else 'wcompat' if (tag & 0xfff) == TAG_WCOMPAT
|
else 'wcompat' if (tag & 0xfff) == TAG_WCOMPAT
|
||||||
else 'ocompat' if (tag & 0xfff) == TAG_OCOMPAT
|
else 'ocompat' if (tag & 0xfff) == TAG_OCOMPAT
|
||||||
else 'geometry' if (tag & 0xfff) == TAG_GEOMETRY
|
else 'geometry' if (tag & 0xfff) == TAG_GEOMETRY
|
||||||
else 'namelimit' if (tag & 0xfff) == TAG_NAMELIMIT
|
else 'namelimit' if (tag & 0xfff) == TAG_NAMELIMIT
|
||||||
else 'filelimit' if (tag & 0xfff) == TAG_FILELIMIT
|
else 'filelimit' if (tag & 0xfff) == TAG_FILELIMIT
|
||||||
else 'config 0x%02x' % (tag & 0xff),
|
else 'config 0x%02x' % (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x6f00) == TAG_GDELTA:
|
elif (tag & 0x6f00) == TAG_GDELTA:
|
||||||
return '%s%s%s%s' % (
|
return '%s%s%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
'grmdelta' if (tag & 0xfff) == TAG_GRMDELTA
|
'grmdelta' if (tag & 0xfff) == TAG_GRMDELTA
|
||||||
else 'gdelta 0x%02x' % (tag & 0xff),
|
else 'gdelta 0x%02x' % (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x6f00) == TAG_NAME:
|
elif (tag & 0x6f00) == TAG_NAME:
|
||||||
return '%s%s%s%s' % (
|
return '%s%s%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
'name' if (tag & 0xfff) == TAG_NAME
|
'name' if (tag & 0xfff) == TAG_NAME
|
||||||
else 'reg' if (tag & 0xfff) == TAG_REG
|
else 'reg' if (tag & 0xfff) == TAG_REG
|
||||||
else 'dir' if (tag & 0xfff) == TAG_DIR
|
else 'dir' if (tag & 0xfff) == TAG_DIR
|
||||||
else 'orphan' if (tag & 0xfff) == TAG_ORPHAN
|
else 'orphan' if (tag & 0xfff) == TAG_ORPHAN
|
||||||
else 'bookmark' if (tag & 0xfff) == TAG_BOOKMARK
|
else 'bookmark' if (tag & 0xfff) == TAG_BOOKMARK
|
||||||
else 'name 0x%02x' % (tag & 0xff),
|
else 'name 0x%02x' % (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x6f00) == TAG_STRUCT:
|
elif (tag & 0x6f00) == TAG_STRUCT:
|
||||||
return '%s%s%s%s' % (
|
return '%s%s%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
'data' if (tag & 0xfff) == TAG_DATA
|
'data' if (tag & 0xfff) == TAG_DATA
|
||||||
else 'block' if (tag & 0xfff) == TAG_BLOCK
|
else 'block' if (tag & 0xfff) == TAG_BLOCK
|
||||||
else 'bshrub' if (tag & 0xfff) == TAG_BSHRUB
|
else 'bshrub' if (tag & 0xfff) == TAG_BSHRUB
|
||||||
else 'btree' if (tag & 0xfff) == TAG_BTREE
|
else 'btree' if (tag & 0xfff) == TAG_BTREE
|
||||||
else 'mroot' if (tag & 0xfff) == TAG_MROOT
|
else 'mroot' if (tag & 0xfff) == TAG_MROOT
|
||||||
else 'mdir' if (tag & 0xfff) == TAG_MDIR
|
else 'mdir' if (tag & 0xfff) == TAG_MDIR
|
||||||
else 'mtree' if (tag & 0xfff) == TAG_MTREE
|
else 'mtree' if (tag & 0xfff) == TAG_MTREE
|
||||||
else 'did' if (tag & 0xfff) == TAG_DID
|
else 'did' if (tag & 0xfff) == TAG_DID
|
||||||
else 'branch' if (tag & 0xfff) == TAG_BRANCH
|
else 'branch' if (tag & 0xfff) == TAG_BRANCH
|
||||||
else 'struct 0x%02x' % (tag & 0xff),
|
else 'struct 0x%02x' % (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x6e00) == TAG_ATTR:
|
elif (tag & 0x6e00) == TAG_ATTR:
|
||||||
return '%s%sattr 0x%02x%s%s' % (
|
return '%s%sattr 0x%02x%s%s' % (
|
||||||
'shrub' if tag & TAG_SHRUB else '',
|
'shrub' if tag & TAG_SHRUB else '',
|
||||||
's' if tag & 0x100 else 'u',
|
's' if tag & 0x100 else 'u',
|
||||||
((tag & 0x100) >> 1) ^ (tag & 0xff),
|
((tag & 0x100) >> 1) ^ (tag & 0xff),
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif tag & TAG_ALT:
|
elif tag & TAG_ALT:
|
||||||
return 'alt%s%s%s%s%s' % (
|
return 'alt%s%s%s%s%s' % (
|
||||||
'r' if tag & TAG_R else 'b',
|
'r' if tag & TAG_R else 'b',
|
||||||
'a' if tag & 0x0fff == 0 and tag & TAG_GT
|
'a' if tag & 0x0fff == 0 and tag & TAG_GT
|
||||||
else 'n' if tag & 0x0fff == 0
|
else 'n' if tag & 0x0fff == 0
|
||||||
else 'gt' if tag & TAG_GT
|
else 'gt' if tag & TAG_GT
|
||||||
else 'le',
|
else 'le',
|
||||||
' 0x%x' % (tag & 0x0fff) if tag & 0x0fff != 0 else '',
|
' 0x%x' % (tag & 0x0fff) if tag & 0x0fff != 0 else '',
|
||||||
' w%d' % w if w is not None else '',
|
' w%d' % w if w is not None else '',
|
||||||
' 0x%x' % (0xffffffff & (off-size))
|
' 0x%x' % (0xffffffff & (off-size))
|
||||||
if size and off is not None
|
if size and off is not None
|
||||||
else ' -%d' % size if size
|
else ' -%d' % size if size
|
||||||
else '')
|
else '')
|
||||||
elif (tag & 0x7f00) == TAG_CKSUM:
|
elif (tag & 0x7f00) == TAG_CKSUM:
|
||||||
return 'cksum%s%s%s%s%s' % (
|
return 'cksum%s%s%s%s%s' % (
|
||||||
'q' if not tag & 0xfc and tag & TAG_Q else '',
|
'q' if not tag & 0xfc and tag & TAG_Q else '',
|
||||||
'p' if not tag & 0xfc and tag & TAG_P else '',
|
'p' if not tag & 0xfc and tag & TAG_P else '',
|
||||||
' 0x%02x' % (tag & 0xff) if tag & 0xfc else '',
|
' 0x%02x' % (tag & 0xff) if tag & 0xfc else '',
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x7f00) == TAG_NOTE:
|
elif (tag & 0x7f00) == TAG_NOTE:
|
||||||
return 'note%s%s%s' % (
|
return 'note%s%s%s' % (
|
||||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
elif (tag & 0x7f00) == TAG_ECKSUM:
|
elif (tag & 0x7f00) == TAG_ECKSUM:
|
||||||
return 'ecksum%s%s%s' % (
|
return 'ecksum%s%s%s' % (
|
||||||
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
' 0x%02x' % (tag & 0xff) if tag & 0xff else '',
|
||||||
' w%d' % w if w else '',
|
' w%d' % w if w else '',
|
||||||
' %s' % size if size is not None else '')
|
' %s' % size if size is not None else '')
|
||||||
else:
|
else:
|
||||||
return '0x%04x%s%s' % (
|
return '0x%04x%s%s' % (
|
||||||
tag,
|
tag,
|
||||||
' w%d' % w if w is not None else '',
|
' w%d' % w if w is not None else '',
|
||||||
' %d' % size if size is not None else '')
|
' %d' % size if size is not None else '')
|
||||||
|
|
||||||
|
|
||||||
def list_tags():
|
def list_tags():
|
||||||
@@ -221,7 +221,7 @@ def list_tags():
|
|||||||
import re
|
import re
|
||||||
tags = []
|
tags = []
|
||||||
tag_pattern = re.compile(
|
tag_pattern = re.compile(
|
||||||
'^(?P<name>TAG_[^ ]*) *= *(?P<tag>[^ #]+) *#+ *(?P<comment>.*)$')
|
'^(?P<name>TAG_[^ ]*) *= *(?P<tag>[^ #]+) *#+ *(?P<comment>.*)$')
|
||||||
for line in inspect.getsourcelines(
|
for line in inspect.getsourcelines(
|
||||||
inspect.getmodule(inspect.currentframe()))[0]:
|
inspect.getmodule(inspect.currentframe()))[0]:
|
||||||
m = tag_pattern.match(line)
|
m = tag_pattern.match(line)
|
||||||
@@ -236,8 +236,8 @@ def list_tags():
|
|||||||
# print
|
# print
|
||||||
for n, t, c in tags:
|
for n, t, c in tags:
|
||||||
print('%-*s %s' % (
|
print('%-*s %s' % (
|
||||||
w[0], 'LFSR_'+n,
|
w[0], 'LFSR_'+n,
|
||||||
c))
|
c))
|
||||||
|
|
||||||
def dbg_tag(data):
|
def dbg_tag(data):
|
||||||
if isinstance(data, int):
|
if isinstance(data, int):
|
||||||
@@ -305,12 +305,12 @@ def main(tags, *,
|
|||||||
|
|
||||||
# blocks may also encode offsets
|
# blocks may also encode offsets
|
||||||
blocks, offs = (
|
blocks, offs = (
|
||||||
[block[0] if isinstance(block, tuple) else block
|
[block[0] if isinstance(block, tuple) else block
|
||||||
for block in blocks],
|
for block in blocks],
|
||||||
[off if off is not None
|
[off if off is not None
|
||||||
else block[1] if isinstance(block, tuple)
|
else block[1] if isinstance(block, tuple)
|
||||||
else None
|
else None
|
||||||
for block in blocks])
|
for block in blocks])
|
||||||
|
|
||||||
# read each tag
|
# read each tag
|
||||||
for block, off in zip(blocks, offs):
|
for block, off in zip(blocks, offs):
|
||||||
@@ -319,40 +319,41 @@ def main(tags, *,
|
|||||||
data = f.read(2+5+5)
|
data = f.read(2+5+5)
|
||||||
dbg_tag(data)
|
dbg_tag(data)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Decode littlefs tags.",
|
description="Decode littlefs tags.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'tags',
|
'tags',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
help="Tags to decode.")
|
help="Tags to decode.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-l', '--list',
|
'-l', '--list',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="List all known tags.")
|
help="List all known tags.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-x', '--hex',
|
'-x', '--hex',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Interpret as a sequence of hex bytes.")
|
help="Interpret as a sequence of hex bytes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-s', '--string',
|
'-s', '--string',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Interpret as strings.")
|
help="Interpret as strings.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--block-size',
|
'-b', '--block-size',
|
||||||
type=bdgeom,
|
type=bdgeom,
|
||||||
help="Block size/geometry in bytes.")
|
help="Block size/geometry in bytes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--block-count',
|
'--block-count',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Block count in blocks.")
|
help="Block count in blocks.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--off',
|
'--off',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Use this offset.")
|
help="Use this offset.")
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+20
-19
@@ -29,17 +29,17 @@ def main(paths, **args):
|
|||||||
# interpret as sequence of hex bytes
|
# interpret as sequence of hex bytes
|
||||||
if args.get('hex'):
|
if args.get('hex'):
|
||||||
print('%01x' % parity(ft.reduce(
|
print('%01x' % parity(ft.reduce(
|
||||||
op.xor,
|
op.xor,
|
||||||
bytes(int(path, 16) for path in paths),
|
bytes(int(path, 16) for path in paths),
|
||||||
0)))
|
0)))
|
||||||
|
|
||||||
# interpret as strings
|
# interpret as strings
|
||||||
elif args.get('string'):
|
elif args.get('string'):
|
||||||
for path in paths:
|
for path in paths:
|
||||||
print('%01x' % parity(ft.reduce(
|
print('%01x' % parity(ft.reduce(
|
||||||
op.xor,
|
op.xor,
|
||||||
path.encode('utf8'),
|
path.encode('utf8'),
|
||||||
0)))
|
0)))
|
||||||
|
|
||||||
# default to interpreting as paths
|
# default to interpreting as paths
|
||||||
else:
|
else:
|
||||||
@@ -63,24 +63,25 @@ def main(paths, **args):
|
|||||||
else:
|
else:
|
||||||
print('%01x' % parity(xor))
|
print('%01x' % parity(xor))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Calculates parity.",
|
description="Calculates parity.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'paths',
|
'paths',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
help="Paths to read. Reads stdin by default.")
|
help="Paths to read. Reads stdin by default.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-x', '--hex',
|
'-x', '--hex',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Interpret as a sequence of hex bytes.")
|
help="Interpret as a sequence of hex bytes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-s', '--string',
|
'-s', '--string',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Interpret as strings.")
|
help="Interpret as strings.")
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+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,
|
readed=0, proged=0, erased=0,
|
||||||
children=[]):
|
children=[]):
|
||||||
return super().__new__(cls, file, function, int(RInt(line)),
|
return super().__new__(cls, file, function, int(RInt(line)),
|
||||||
RInt(readed), RInt(proged), RInt(erased),
|
RInt(readed), RInt(proged), RInt(erased),
|
||||||
children)
|
children)
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
return PerfBdResult(self.file, self.function, self.line,
|
return PerfBdResult(self.file, self.function, self.line,
|
||||||
self.readed + other.readed,
|
self.readed + other.readed,
|
||||||
self.proged + other.proged,
|
self.proged + other.proged,
|
||||||
self.erased + other.erased,
|
self.erased + other.erased,
|
||||||
self.children + other.children)
|
self.children + other.children)
|
||||||
|
|
||||||
|
|
||||||
def openio(path, mode='r', buffering=-1):
|
def openio(path, mode='r', buffering=-1):
|
||||||
@@ -143,27 +143,27 @@ def collect_syms_and_lines(obj_path, *,
|
|||||||
objdump_path=None,
|
objdump_path=None,
|
||||||
**args):
|
**args):
|
||||||
symbol_pattern = re.compile(
|
symbol_pattern = re.compile(
|
||||||
'^(?P<addr>[0-9a-fA-F]+)'
|
'^(?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]+))?'
|
|
||||||
'\s+.*'
|
'\s+.*'
|
||||||
'\s+(?P<path>[^\s]+)'
|
'\s+(?P<size>[0-9a-fA-F]+)'
|
||||||
# matches line opcodes
|
'\s+(?P<name>[^\s]+)\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_special>Special)'
|
||||||
'|' '(?P<op_copy>Copy)'
|
'|' '(?P<op_copy>Copy)'
|
||||||
'|' '(?P<op_end>End of Sequence)'
|
'|' '(?P<op_end>End of Sequence)'
|
||||||
'|' 'File .*?to (?:entry )?(?P<op_file>\d+)'
|
'|' 'File .*?to (?:entry )?(?P<op_file>\d+)'
|
||||||
'|' 'Line .*?to (?P<op_line>[0-9]+)'
|
'|' 'Line .*?to (?P<op_line>[0-9]+)'
|
||||||
'|' '(?:Address|PC) .*?to (?P<op_addr>[0x0-9a-fA-F]+)'
|
'|' '(?:Address|PC) .*?to (?P<op_addr>[0x0-9a-fA-F]+)'
|
||||||
'|' '.' ')*'
|
'|' '.'
|
||||||
|
')*'
|
||||||
')$', re.IGNORECASE)
|
')$', re.IGNORECASE)
|
||||||
|
|
||||||
# figure out symbol addresses
|
# figure out symbol addresses
|
||||||
@@ -173,11 +173,11 @@ def collect_syms_and_lines(obj_path, *,
|
|||||||
if args.get('verbose'):
|
if args.get('verbose'):
|
||||||
print(' '.join(shlex.quote(c) for c in cmd))
|
print(' '.join(shlex.quote(c) for c in cmd))
|
||||||
proc = sp.Popen(cmd,
|
proc = sp.Popen(cmd,
|
||||||
stdout=sp.PIPE,
|
stdout=sp.PIPE,
|
||||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||||
universal_newlines=True,
|
universal_newlines=True,
|
||||||
errors='replace',
|
errors='replace',
|
||||||
close_fds=False)
|
close_fds=False)
|
||||||
for line in proc.stdout:
|
for line in proc.stdout:
|
||||||
m = symbol_pattern.match(line)
|
m = symbol_pattern.match(line)
|
||||||
if m:
|
if m:
|
||||||
@@ -222,11 +222,11 @@ def collect_syms_and_lines(obj_path, *,
|
|||||||
if args.get('verbose'):
|
if args.get('verbose'):
|
||||||
print(' '.join(shlex.quote(c) for c in cmd))
|
print(' '.join(shlex.quote(c) for c in cmd))
|
||||||
proc = sp.Popen(cmd,
|
proc = sp.Popen(cmd,
|
||||||
stdout=sp.PIPE,
|
stdout=sp.PIPE,
|
||||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||||
universal_newlines=True,
|
universal_newlines=True,
|
||||||
errors='replace',
|
errors='replace',
|
||||||
close_fds=False)
|
close_fds=False)
|
||||||
for line in proc.stdout:
|
for line in proc.stdout:
|
||||||
m = line_pattern.match(line)
|
m = line_pattern.match(line)
|
||||||
if m:
|
if m:
|
||||||
@@ -238,8 +238,8 @@ def collect_syms_and_lines(obj_path, *,
|
|||||||
dir = int(m.group('dir'))
|
dir = int(m.group('dir'))
|
||||||
if dir in dirs:
|
if dir in dirs:
|
||||||
files[int(m.group('no'))] = os.path.join(
|
files[int(m.group('no'))] = os.path.join(
|
||||||
dirs[dir],
|
dirs[dir],
|
||||||
m.group('path'))
|
m.group('path'))
|
||||||
else:
|
else:
|
||||||
files[int(m.group('no'))] = m.group('path')
|
files[int(m.group('no'))] = m.group('path')
|
||||||
else:
|
else:
|
||||||
@@ -296,25 +296,27 @@ def collect_job(path, start, stop, syms, sym_at, lines, line_at, *,
|
|||||||
depth=1,
|
depth=1,
|
||||||
**args):
|
**args):
|
||||||
trace_pattern = re.compile(
|
trace_pattern = re.compile(
|
||||||
'^(?P<file>[^:]*):(?P<line>[0-9]+):trace:\s*(?P<prefix>[^\s]*?bd_)(?:'
|
'^(?P<file>[^:]*):(?P<line>[0-9]+):trace:\s*'
|
||||||
'(?P<read>read)\('
|
'(?P<prefix>[^\s]*?bd_)(?:'
|
||||||
'\s*(?P<read_ctx>\w+)' '\s*,'
|
'(?P<read>read)\('
|
||||||
'\s*(?P<read_block>\w+)' '\s*,'
|
'\s*(?P<read_ctx>\w+)' '\s*,'
|
||||||
'\s*(?P<read_off>\w+)' '\s*,'
|
'\s*(?P<read_block>\w+)' '\s*,'
|
||||||
'\s*(?P<read_buffer>\w+)' '\s*,'
|
'\s*(?P<read_off>\w+)' '\s*,'
|
||||||
'\s*(?P<read_size>\w+)' '\s*\)'
|
'\s*(?P<read_buffer>\w+)' '\s*,'
|
||||||
'|' '(?P<prog>prog)\('
|
'\s*(?P<read_size>\w+)' '\s*\)'
|
||||||
'\s*(?P<prog_ctx>\w+)' '\s*,'
|
'|' '(?P<prog>prog)\('
|
||||||
'\s*(?P<prog_block>\w+)' '\s*,'
|
'\s*(?P<prog_ctx>\w+)' '\s*,'
|
||||||
'\s*(?P<prog_off>\w+)' '\s*,'
|
'\s*(?P<prog_block>\w+)' '\s*,'
|
||||||
'\s*(?P<prog_buffer>\w+)' '\s*,'
|
'\s*(?P<prog_off>\w+)' '\s*,'
|
||||||
'\s*(?P<prog_size>\w+)' '\s*\)'
|
'\s*(?P<prog_buffer>\w+)' '\s*,'
|
||||||
'|' '(?P<erase>erase)\('
|
'\s*(?P<prog_size>\w+)' '\s*\)'
|
||||||
'\s*(?P<erase_ctx>\w+)' '\s*,'
|
'|' '(?P<erase>erase)\('
|
||||||
'\s*(?P<erase_block>\w+)'
|
'\s*(?P<erase_ctx>\w+)' '\s*,'
|
||||||
'\s*\(\s*(?P<erase_size>\w+)\s*\)' '\s*\)' ')\s*$')
|
'\s*(?P<erase_block>\w+)'
|
||||||
|
'\s*\(\s*(?P<erase_size>\w+)\s*\)' '\s*\)'
|
||||||
|
')\s*$')
|
||||||
frame_pattern = re.compile(
|
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
|
# parse all of the trace files for read/prog/erase operations
|
||||||
last_filtered = False
|
last_filtered = False
|
||||||
@@ -338,9 +340,7 @@ def collect_job(path, start, stop, syms, sym_at, lines, line_at, *,
|
|||||||
|
|
||||||
# ignore filtered sources
|
# ignore filtered sources
|
||||||
if sources is not None:
|
if sources is not None:
|
||||||
if not any(
|
if not any(os.path.abspath(file) == os.path.abspath(s)
|
||||||
os.path.abspath(file)
|
|
||||||
== os.path.abspath(s)
|
|
||||||
for s in sources):
|
for s in sources):
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
@@ -359,10 +359,10 @@ def collect_job(path, start, stop, syms, sym_at, lines, line_at, *,
|
|||||||
file = os.path.abspath(file)
|
file = os.path.abspath(file)
|
||||||
|
|
||||||
results[(file, sym, line)] = (
|
results[(file, sym, line)] = (
|
||||||
last_readed,
|
last_readed,
|
||||||
last_proged,
|
last_proged,
|
||||||
last_erased,
|
last_erased,
|
||||||
{})
|
{})
|
||||||
else:
|
else:
|
||||||
# tail-recursively propagate measurements
|
# tail-recursively propagate measurements
|
||||||
for i in range(len(last_stack)):
|
for i in range(len(last_stack)):
|
||||||
@@ -378,10 +378,10 @@ def collect_job(path, start, stop, syms, sym_at, lines, line_at, *,
|
|||||||
else:
|
else:
|
||||||
r, p, e, children = 0, 0, 0, {}
|
r, p, e, children = 0, 0, 0, {}
|
||||||
results_[name] = (
|
results_[name] = (
|
||||||
r+last_readed,
|
r+last_readed,
|
||||||
p+last_proged,
|
p+last_proged,
|
||||||
e+last_erased,
|
e+last_erased,
|
||||||
children)
|
children)
|
||||||
|
|
||||||
# recurse
|
# recurse
|
||||||
results_ = results_[name][-1]
|
results_ = results_[name][-1]
|
||||||
@@ -444,7 +444,7 @@ def collect_job(path, start, stop, syms, sym_at, lines, line_at, *,
|
|||||||
# of reference
|
# of reference
|
||||||
if last_delta is None:
|
if last_delta is None:
|
||||||
i = bisect.bisect(lines, (last_file, last_line),
|
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:
|
if i > 0:
|
||||||
last_delta = lines[i-1][2] - addr_
|
last_delta = lines[i-1][2] - addr_
|
||||||
else:
|
else:
|
||||||
@@ -474,9 +474,9 @@ def collect_job(path, start, stop, syms, sym_at, lines, line_at, *,
|
|||||||
# filter out internal/unknown functions
|
# filter out internal/unknown functions
|
||||||
if not everything and (
|
if not everything and (
|
||||||
sym.startswith('__')
|
sym.startswith('__')
|
||||||
or sym.startswith('0')
|
or sym.startswith('0')
|
||||||
or sym.startswith('-')
|
or sym.startswith('-')
|
||||||
or sym == '_start'):
|
or sym == '_start'):
|
||||||
at_cache[addr] = None
|
at_cache[addr] = None
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -492,9 +492,8 @@ def collect_job(path, start, stop, syms, sym_at, lines, line_at, *,
|
|||||||
# ignore filtered sources
|
# ignore filtered sources
|
||||||
if sources is not None:
|
if sources is not None:
|
||||||
if not any(
|
if not any(
|
||||||
os.path.abspath(file)
|
os.path.abspath(file) == os.path.abspath(s)
|
||||||
== os.path.abspath(s)
|
for s in sources):
|
||||||
for s in sources):
|
|
||||||
at_cache[addr] = None
|
at_cache[addr] = None
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
@@ -529,8 +528,8 @@ def collect_job(path, start, stop, syms, sym_at, lines, line_at, *,
|
|||||||
results_ = []
|
results_ = []
|
||||||
for name, (r, p, e, children) in results.items():
|
for name, (r, p, e, children) in results.items():
|
||||||
results_.append(PerfBdResult(*name,
|
results_.append(PerfBdResult(*name,
|
||||||
r, p, e,
|
r, p, e,
|
||||||
children=to_results(children)))
|
children=to_results(children)))
|
||||||
return results_
|
return results_
|
||||||
|
|
||||||
return to_results(results)
|
return to_results(results)
|
||||||
@@ -573,9 +572,10 @@ def collect(obj_path, trace_paths, *,
|
|||||||
with mp.Pool(jobs) as p:
|
with mp.Pool(jobs) as p:
|
||||||
for results_ in p.imap_unordered(
|
for results_ in p.imap_unordered(
|
||||||
starapply,
|
starapply,
|
||||||
((collect_job, (path, start, stop,
|
((collect_job,
|
||||||
syms, sym_at, lines, line_at),
|
(path, start, stop,
|
||||||
args)
|
syms, sym_at, lines, line_at),
|
||||||
|
args)
|
||||||
for path, ranges in zip(trace_paths, trace_ranges)
|
for path, ranges in zip(trace_paths, trace_ranges)
|
||||||
for start, stop in ranges)):
|
for start, stop in ranges)):
|
||||||
results.extend(results_)
|
results.extend(results_)
|
||||||
@@ -583,9 +583,10 @@ def collect(obj_path, trace_paths, *,
|
|||||||
else:
|
else:
|
||||||
results = []
|
results = []
|
||||||
for path in trace_paths:
|
for path in trace_paths:
|
||||||
results.extend(collect_job(path, None, None,
|
results.extend(collect_job(
|
||||||
syms, sym_at, lines, line_at,
|
path, None, None,
|
||||||
**args))
|
syms, sym_at, lines, line_at,
|
||||||
|
**args))
|
||||||
|
|
||||||
return results
|
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)):
|
for k in it.chain(by or [], (k for k, _ in defines)):
|
||||||
if k not in Result._by and k not in Result._fields:
|
if k not in Result._by and k not in Result._fields:
|
||||||
print("error: could not find field %r?" % k,
|
print("error: could not find field %r?" % k,
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# filter by matching defines
|
# filter by matching defines
|
||||||
@@ -653,74 +654,78 @@ def table(Result, results, diff_results=None, *,
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
r = max(results_,
|
r = max(results_,
|
||||||
key=lambda r: tuple(
|
key=lambda r: tuple(
|
||||||
tuple(
|
tuple((getattr(r, k),)
|
||||||
(getattr(r, k),)
|
if getattr(r, k, None) is not None
|
||||||
if getattr(r, k, None) is not None
|
else ()
|
||||||
else ()
|
for k in (
|
||||||
for k in ([k] if k else [
|
[k] if k else [
|
||||||
k for k in Result._sort if k in fields])
|
k for k in Result._sort
|
||||||
if k in fields)
|
if k in fields])
|
||||||
for k in it.chain(hot, [None])))
|
if k in fields)
|
||||||
|
for k in it.chain(hot, [None])))
|
||||||
|
|
||||||
# found a cycle?
|
# found a cycle?
|
||||||
if tuple(getattr(r, k) for k in Result._by) in seen:
|
if tuple(getattr(r, k) for k in Result._by) in seen:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
return [r._replace(children=[])] + rec_hot(
|
return [r._replace(children=[])] + rec_hot(
|
||||||
r.children,
|
r.children,
|
||||||
seen | {tuple(getattr(r, k) for k in Result._by)})
|
seen | {tuple(getattr(r, k) for k in Result._by)})
|
||||||
|
|
||||||
results = [r._replace(children=rec_hot(r.children)) for r in results]
|
results = [r._replace(children=rec_hot(r.children)) for r in results]
|
||||||
|
|
||||||
# organize by name
|
# organize by name
|
||||||
table = {
|
table = {
|
||||||
','.join(str(getattr(r, k) or '') for k in by): r
|
','.join(str(getattr(r, k) or '') for k in by): r
|
||||||
for r in results}
|
for r in results}
|
||||||
diff_table = {
|
diff_table = {
|
||||||
','.join(str(getattr(r, k) or '') for k in by): r
|
','.join(str(getattr(r, k) or '') for k in by): r
|
||||||
for r in diff_results or []}
|
for r in diff_results or []}
|
||||||
names = [name
|
names = [name
|
||||||
for name in table.keys() | diff_table.keys()
|
for name in table.keys() | diff_table.keys()
|
||||||
if diff_results is None
|
if diff_results is None
|
||||||
or all_
|
or all_
|
||||||
or any(
|
or any(
|
||||||
types[k].ratio(
|
types[k].ratio(
|
||||||
getattr(table.get(name), k, None),
|
getattr(table.get(name), k, None),
|
||||||
getattr(diff_table.get(name), k, None))
|
getattr(diff_table.get(name), k, None))
|
||||||
for k in fields)]
|
for k in fields)]
|
||||||
|
|
||||||
# sort again, now with diff info, note that python's sort is stable
|
# sort again, now with diff info, note that python's sort is stable
|
||||||
names.sort()
|
names.sort()
|
||||||
if diff_results is not None:
|
if diff_results is not None:
|
||||||
names.sort(key=lambda n: tuple(
|
names.sort(
|
||||||
types[k].ratio(
|
key=lambda n: tuple(
|
||||||
getattr(table.get(n), k, None),
|
types[k].ratio(
|
||||||
getattr(diff_table.get(n), k, None))
|
getattr(table.get(n), k, None),
|
||||||
for k in fields),
|
getattr(diff_table.get(n), k, None))
|
||||||
reverse=True)
|
for k in fields),
|
||||||
|
reverse=True)
|
||||||
if sort:
|
if sort:
|
||||||
for k, reverse in reversed(sort):
|
for k, reverse in reversed(sort):
|
||||||
names.sort(
|
names.sort(
|
||||||
key=lambda n: tuple(
|
key=lambda n: tuple(
|
||||||
(getattr(table[n], k),)
|
(getattr(table[n], k),)
|
||||||
if getattr(table.get(n), k, None) is not None else ()
|
if getattr(table.get(n), k, None) is not None
|
||||||
for k in ([k] if k else [
|
else ()
|
||||||
k for k in Result._sort if k in fields])),
|
for k in (
|
||||||
reverse=reverse ^ (not k or k in Result._fields))
|
[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
|
# build up our lines
|
||||||
lines = []
|
lines = []
|
||||||
|
|
||||||
# header
|
# header
|
||||||
header = [
|
header = ['%s%s' % (
|
||||||
'%s%s' % (
|
','.join(by),
|
||||||
','.join(by),
|
' (%d added, %d removed)' % (
|
||||||
' (%d added, %d removed)' % (
|
sum(1 for n in table if n not in diff_table),
|
||||||
sum(1 for n in table if n not in diff_table),
|
sum(1 for n in diff_table if n not in table))
|
||||||
sum(1 for n in diff_table if n not in table))
|
if diff_results is not None and not percent else '')
|
||||||
if diff_results is not None and not percent else '')
|
|
||||||
if not summary else '']
|
if not summary else '']
|
||||||
if diff_results is None:
|
if diff_results is None:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
@@ -743,43 +748,43 @@ def table(Result, results, diff_results=None, *,
|
|||||||
if diff_results is None:
|
if diff_results is None:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(getattr(r, k).table(),
|
(getattr(r, k).table(),
|
||||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
elif percent:
|
elif percent:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(getattr(r, k).table()
|
(getattr(r, k).table()
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none,
|
else types[k].none,
|
||||||
(lambda t: ['+∞%'] if t == +mt.inf
|
(lambda t: ['+∞%'] if t == +mt.inf
|
||||||
else ['-∞%'] if t == -mt.inf
|
else ['-∞%'] if t == -mt.inf
|
||||||
else ['%+.1f%%' % (100*t)])(
|
else ['%+.1f%%' % (100*t)])(
|
||||||
types[k].ratio(
|
types[k].ratio(
|
||||||
getattr(r, k, None),
|
getattr(r, k, None),
|
||||||
getattr(diff_r, k, None)))))
|
getattr(diff_r, k, None)))))
|
||||||
else:
|
else:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(getattr(diff_r, k).table()
|
entry.append(getattr(diff_r, k).table()
|
||||||
if getattr(diff_r, k, None) is not None
|
if getattr(diff_r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(getattr(r, k).table()
|
entry.append(getattr(r, k).table()
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(types[k].diff(
|
(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(
|
|
||||||
getattr(r, k, None),
|
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
|
return entry
|
||||||
|
|
||||||
# recursive entry helper
|
# recursive entry helper
|
||||||
@@ -788,8 +793,8 @@ def table(Result, results, diff_results=None, *,
|
|||||||
# build the children table at each layer
|
# build the children table at each layer
|
||||||
results_ = fold(Result, results_, by=by)
|
results_ = fold(Result, results_, by=by)
|
||||||
table_ = {
|
table_ = {
|
||||||
','.join(str(getattr(r, k) or '') for k in by): r
|
','.join(str(getattr(r, k) or '') for k in by): r
|
||||||
for r in results_}
|
for r in results_}
|
||||||
names_ = list(table_.keys())
|
names_ = list(table_.keys())
|
||||||
|
|
||||||
# sort the children layer
|
# sort the children layer
|
||||||
@@ -797,13 +802,16 @@ def table(Result, results, diff_results=None, *,
|
|||||||
if sort:
|
if sort:
|
||||||
for k, reverse in reversed(sort):
|
for k, reverse in reversed(sort):
|
||||||
names_.sort(
|
names_.sort(
|
||||||
key=lambda n: tuple(
|
key=lambda n: tuple(
|
||||||
(getattr(table_[n], k),)
|
(getattr(table_[n], k),)
|
||||||
if getattr(table_.get(n), k, None) is not None
|
if getattr(table_.get(n), k, None)
|
||||||
else ()
|
is not None
|
||||||
for k in ([k] if k else [
|
else ()
|
||||||
k for k in Result._sort if k in fields])),
|
for k in (
|
||||||
reverse=reverse ^ (not k or k in Result._fields))
|
[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_):
|
for i, name in enumerate(names_):
|
||||||
r = table_[name]
|
r = table_[name]
|
||||||
@@ -824,14 +832,13 @@ def table(Result, results, diff_results=None, *,
|
|||||||
|
|
||||||
# recurse?
|
# recurse?
|
||||||
if depth_ > 1:
|
if depth_ > 1:
|
||||||
recurse(
|
recurse(r.children,
|
||||||
r.children,
|
depth_-1,
|
||||||
depth_-1,
|
seen | {name},
|
||||||
seen | {name},
|
(prefixes[2+is_last] + "|-> ",
|
||||||
(prefixes[2+is_last] + "|-> ",
|
prefixes[2+is_last] + "'-> ",
|
||||||
prefixes[2+is_last] + "'-> ",
|
prefixes[2+is_last] + "| ",
|
||||||
prefixes[2+is_last] + "| ",
|
prefixes[2+is_last] + " "))
|
||||||
prefixes[2+is_last] + " "))
|
|
||||||
|
|
||||||
# entries
|
# entries
|
||||||
if not summary:
|
if not summary:
|
||||||
@@ -845,14 +852,13 @@ def table(Result, results, diff_results=None, *,
|
|||||||
|
|
||||||
# recursive entries
|
# recursive entries
|
||||||
if name in table and depth > 1:
|
if name in table and depth > 1:
|
||||||
recurse(
|
recurse(table[name].children,
|
||||||
table[name].children,
|
depth-1,
|
||||||
depth-1,
|
{name},
|
||||||
{name},
|
("|-> ",
|
||||||
("|-> ",
|
"'-> ",
|
||||||
"'-> ",
|
"| ",
|
||||||
"| ",
|
" "))
|
||||||
" "))
|
|
||||||
|
|
||||||
# total
|
# total
|
||||||
r = next(iter(fold(Result, results, by=[])), None)
|
r = next(iter(fold(Result, results, by=[])), None)
|
||||||
@@ -864,8 +870,8 @@ def table(Result, results, diff_results=None, *,
|
|||||||
|
|
||||||
# homogenize
|
# homogenize
|
||||||
lines = [
|
lines = [
|
||||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||||
for line in lines]
|
for line in lines]
|
||||||
|
|
||||||
# find the best widths, note that column 0 contains the names and is
|
# find the best widths, note that column 0 contains the names and is
|
||||||
# handled a bit differently
|
# handled a bit differently
|
||||||
@@ -879,11 +885,11 @@ def table(Result, results, diff_results=None, *,
|
|||||||
# print our table
|
# print our table
|
||||||
for line in lines:
|
for line in lines:
|
||||||
print('%-*s %s' % (
|
print('%-*s %s' % (
|
||||||
widths[0], line[0][0],
|
widths[0], line[0][0],
|
||||||
' '.join('%*s%-*s' % (
|
' '.join('%*s%-*s' % (
|
||||||
widths[i], x[0],
|
widths[i], x[0],
|
||||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||||
for i, x in enumerate(line[1:], 1))))
|
for i, x in enumerate(line[1:], 1))))
|
||||||
|
|
||||||
|
|
||||||
def annotate(Result, results, *,
|
def annotate(Result, results, *,
|
||||||
@@ -944,14 +950,14 @@ def annotate(Result, results, *,
|
|||||||
or float(r.erased) / max_erased >= erase_t0):
|
or float(r.erased) / max_erased >= erase_t0):
|
||||||
if last is not None and line - last.stop <= args['context']:
|
if last is not None and line - last.stop <= args['context']:
|
||||||
last = range(
|
last = range(
|
||||||
last.start,
|
last.start,
|
||||||
line+1+args['context'])
|
line+1+args['context'])
|
||||||
else:
|
else:
|
||||||
if last is not None:
|
if last is not None:
|
||||||
spans.append((last, func))
|
spans.append((last, func))
|
||||||
last = range(
|
last = range(
|
||||||
line-args['context'],
|
line-args['context'],
|
||||||
line+1+args['context'])
|
line+1+args['context'])
|
||||||
func = r.function
|
func = r.function
|
||||||
if last is not None:
|
if last is not None:
|
||||||
spans.append((last, func))
|
spans.append((last, func))
|
||||||
@@ -967,11 +973,11 @@ def annotate(Result, results, *,
|
|||||||
if skipped:
|
if skipped:
|
||||||
skipped = False
|
skipped = False
|
||||||
print('%s@@ %s:%d: %s @@%s' % (
|
print('%s@@ %s:%d: %s @@%s' % (
|
||||||
'\x1b[36m' if args['color'] else '',
|
'\x1b[36m' if args['color'] else '',
|
||||||
path,
|
path,
|
||||||
i+1,
|
i+1,
|
||||||
next(iter(f for _, f in spans)),
|
next(iter(f for _, f in spans)),
|
||||||
'\x1b[m' if args['color'] else ''))
|
'\x1b[m' if args['color'] else ''))
|
||||||
|
|
||||||
# build line
|
# build line
|
||||||
if line.endswith('\n'):
|
if line.endswith('\n'):
|
||||||
@@ -980,11 +986,11 @@ def annotate(Result, results, *,
|
|||||||
if i+1 in table:
|
if i+1 in table:
|
||||||
r = table[i+1]
|
r = table[i+1]
|
||||||
line = '%-*s // %s readed, %s proged, %s erased' % (
|
line = '%-*s // %s readed, %s proged, %s erased' % (
|
||||||
args['width'],
|
args['width'],
|
||||||
line,
|
line,
|
||||||
r.readed,
|
r.readed,
|
||||||
r.proged,
|
r.proged,
|
||||||
r.erased)
|
r.erased)
|
||||||
|
|
||||||
if args['color']:
|
if args['color']:
|
||||||
if (float(r.readed) / max_readed >= read_t1
|
if (float(r.readed) / max_readed >= read_t1
|
||||||
@@ -1036,10 +1042,10 @@ def report(obj_path='', trace_paths=[], *,
|
|||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
results.append(PerfBdResult(
|
results.append(PerfBdResult(
|
||||||
**{k: r[k] for k in PerfBdResult._by
|
**{k: r[k] for k in PerfBdResult._by
|
||||||
if k in r and r[k].strip()},
|
if k in r and r[k].strip()},
|
||||||
**{k: r[k] for k in PerfBdResult._fields
|
**{k: r[k] for k in PerfBdResult._fields
|
||||||
if k in r and r[k].strip()}))
|
if k in r and r[k].strip()}))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -1051,25 +1057,27 @@ def report(obj_path='', trace_paths=[], *,
|
|||||||
if sort:
|
if sort:
|
||||||
for k, reverse in reversed(sort):
|
for k, reverse in reversed(sort):
|
||||||
results.sort(
|
results.sort(
|
||||||
key=lambda r: tuple(
|
key=lambda r: tuple(
|
||||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||||
for k in ([k] if k else PerfBdResult._sort)),
|
for k in ([k] if k else PerfBdResult._sort)),
|
||||||
reverse=reverse ^ (not k or k in PerfBdResult._fields))
|
reverse=reverse ^ (not k or k in PerfBdResult._fields))
|
||||||
|
|
||||||
# write results to CSV
|
# write results to CSV
|
||||||
if args.get('output'):
|
if args.get('output'):
|
||||||
with openio(args['output'], 'w') as f:
|
with openio(args['output'], 'w') as f:
|
||||||
writer = csv.DictWriter(f,
|
writer = csv.DictWriter(f,
|
||||||
(by if by is not None else PerfBdResult._by)
|
(by if by is not None else PerfBdResult._by)
|
||||||
+ [k for k in (
|
+ [k for k in (
|
||||||
fields if fields is not None else PerfBdResult._fields)])
|
fields if fields is not None
|
||||||
|
else PerfBdResult._fields)])
|
||||||
writer.writeheader()
|
writer.writeheader()
|
||||||
for r in results:
|
for r in results:
|
||||||
writer.writerow(
|
writer.writerow(
|
||||||
{k: getattr(r, k) for k in (
|
{k: getattr(r, k) for k in (
|
||||||
by if by is not None else PerfBdResult._by)}
|
by if by is not None else PerfBdResult._by)}
|
||||||
| {k: getattr(r, k) for k in (
|
| {k: getattr(r, k) for k in (
|
||||||
fields if fields is not None else PerfBdResult._fields)})
|
fields if fields is not None
|
||||||
|
else PerfBdResult._fields)})
|
||||||
|
|
||||||
# find previous results?
|
# find previous results?
|
||||||
if args.get('diff'):
|
if args.get('diff'):
|
||||||
@@ -1087,10 +1095,10 @@ def report(obj_path='', trace_paths=[], *,
|
|||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
diff_results.append(PerfBdResult(
|
diff_results.append(PerfBdResult(
|
||||||
**{k: r[k] for k in PerfBdResult._by
|
**{k: r[k] for k in PerfBdResult._by
|
||||||
if k in r and r[k].strip()},
|
if k in r and r[k].strip()},
|
||||||
**{k: r[k] for k in PerfBdResult._fields
|
**{k: r[k] for k in PerfBdResult._fields
|
||||||
if k in r and r[k].strip()}))
|
if k in r and r[k].strip()}))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
@@ -1111,11 +1119,11 @@ def report(obj_path='', trace_paths=[], *,
|
|||||||
else:
|
else:
|
||||||
# print table
|
# print table
|
||||||
table(PerfBdResult, results,
|
table(PerfBdResult, results,
|
||||||
diff_results if args.get('diff') else None,
|
diff_results if args.get('diff') else None,
|
||||||
by=by if by is not None else ['function'],
|
by=by if by is not None else ['function'],
|
||||||
fields=fields,
|
fields=fields,
|
||||||
sort=sort,
|
sort=sort,
|
||||||
**args)
|
**args)
|
||||||
|
|
||||||
|
|
||||||
def main(**args):
|
def main(**args):
|
||||||
@@ -1129,168 +1137,173 @@ if __name__ == "__main__":
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Aggregate and report call-stack propagated "
|
description="Aggregate and report call-stack propagated "
|
||||||
"block-device operations from trace output.",
|
"block-device operations from trace output.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'obj_path',
|
'obj_path',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
help="Input executable for mapping addresses to symbols.")
|
help="Input executable for mapping addresses to symbols.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'trace_paths',
|
'trace_paths',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
help="Input *.trace files.")
|
help="Input *.trace files.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-v', '--verbose',
|
'-v', '--verbose',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Output commands that run behind the scenes.")
|
help="Output commands that run behind the scenes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-q', '--quiet',
|
'-q', '--quiet',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Don't show anything, useful with -o.")
|
help="Don't show anything, useful with -o.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-o', '--output',
|
'-o', '--output',
|
||||||
help="Specify CSV file to store results.")
|
help="Specify CSV file to store results.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-u', '--use',
|
'-u', '--use',
|
||||||
help="Don't parse anything, use this CSV file.")
|
help="Don't parse anything, use this CSV file.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-d', '--diff',
|
'-d', '--diff',
|
||||||
help="Specify CSV file to diff against.")
|
help="Specify CSV file to diff against.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-a', '--all',
|
'-a', '--all',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show all, not just the ones that changed.")
|
help="Show all, not just the ones that changed.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-p', '--percent',
|
'-p', '--percent',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Only show percentage change, not a full diff.")
|
help="Only show percentage change, not a full diff.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--by',
|
'-b', '--by',
|
||||||
action='append',
|
action='append',
|
||||||
choices=PerfBdResult._by,
|
choices=PerfBdResult._by,
|
||||||
help="Group by this field.")
|
help="Group by this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-f', '--field',
|
'-f', '--field',
|
||||||
dest='fields',
|
dest='fields',
|
||||||
action='append',
|
action='append',
|
||||||
choices=PerfBdResult._fields,
|
choices=PerfBdResult._fields,
|
||||||
help="Show this field.")
|
help="Show this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-D', '--define',
|
'-D', '--define',
|
||||||
dest='defines',
|
dest='defines',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs: (
|
lambda k, vs: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
{v.strip() for v in vs.split(',')})
|
{v.strip() for v in vs.split(',')})
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Only include results where this field is this value.")
|
help="Only include results where this field is this value.")
|
||||||
class AppendSort(argparse.Action):
|
class AppendSort(argparse.Action):
|
||||||
def __call__(self, parser, namespace, value, option):
|
def __call__(self, parser, namespace, value, option):
|
||||||
if namespace.sort is None:
|
if namespace.sort is None:
|
||||||
namespace.sort = []
|
namespace.sort = []
|
||||||
namespace.sort.append((value, True if option == '-S' else False))
|
namespace.sort.append((value, True if option == '-S' else False))
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-s', '--sort',
|
'-s', '--sort',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
action=AppendSort,
|
action=AppendSort,
|
||||||
help="Sort by this field.")
|
help="Sort by this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-S', '--reverse-sort',
|
'-S', '--reverse-sort',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
action=AppendSort,
|
action=AppendSort,
|
||||||
help="Sort by this field, but backwards.")
|
help="Sort by this field, but backwards.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-Y', '--summary',
|
'-Y', '--summary',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Only show the total.")
|
help="Only show the total.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-F', '--source',
|
'-F', '--source',
|
||||||
dest='sources',
|
dest='sources',
|
||||||
action='append',
|
action='append',
|
||||||
help="Only consider definitions in this file. Defaults to anything "
|
help="Only consider definitions in this file. Defaults to "
|
||||||
"in the current directory.")
|
"anything in the current directory.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--everything',
|
'--everything',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Include builtin and libc specific symbols.")
|
help="Include builtin and libc specific symbols.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-g', '--propagate',
|
'-g', '--propagate',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Depth to propagate samples up the call-stack. 0 propagates up "
|
help="Depth to propagate samples up the call-stack. 0 propagates "
|
||||||
"to the entry point, 1 does no propagation. Defaults to 0.")
|
"up to the entry point, 1 does no propagation. Defaults to 0.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-z', '--depth',
|
'-z', '--depth',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
const=0,
|
const=0,
|
||||||
help="Depth of function calls to show. 0 shows all calls unless we "
|
help="Depth of function calls to show. 0 shows all calls unless "
|
||||||
"find a cycle. Defaults to 0.")
|
"we find a cycle. Defaults to 0.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-t', '--hot',
|
'-t', '--hot',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
action='append',
|
action='append',
|
||||||
help="Show only the hot path for each function call.")
|
help="Show only the hot path for each function call.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-A', '--annotate',
|
'-A', '--annotate',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show source files annotated with coverage info.")
|
help="Show source files annotated with coverage info.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-T', '--threshold',
|
'-T', '--threshold',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: tuple(float(x) for x in x.split(',')),
|
type=lambda x: tuple(float(x) for x in x.split(',')),
|
||||||
const=THRESHOLD,
|
const=THRESHOLD,
|
||||||
help="Show lines with any ops above this threshold as a percent of "
|
help="Show lines with any ops above this threshold as a percent "
|
||||||
"all lines. Defaults to %s." % ','.join(str(t) for t in THRESHOLD))
|
"of all lines. Defaults to "
|
||||||
|
"%s." % ','.join(str(t) for t in THRESHOLD))
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--read-threshold',
|
'--read-threshold',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: tuple(float(x) for x in x.split(',')),
|
type=lambda x: tuple(float(x) for x in x.split(',')),
|
||||||
const=THRESHOLD,
|
const=THRESHOLD,
|
||||||
help="Show lines with reads above this threshold as a percent of "
|
help="Show lines with reads above this threshold as a percent "
|
||||||
"all lines. Defaults to %s." % ','.join(str(t) for t in THRESHOLD))
|
"of all lines. Defaults to "
|
||||||
|
"%s." % ','.join(str(t) for t in THRESHOLD))
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--prog-threshold',
|
'--prog-threshold',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: tuple(float(x) for x in x.split(',')),
|
type=lambda x: tuple(float(x) for x in x.split(',')),
|
||||||
const=THRESHOLD,
|
const=THRESHOLD,
|
||||||
help="Show lines with progs above this threshold as a percent of "
|
help="Show lines with progs above this threshold as a percent "
|
||||||
"all lines. Defaults to %s." % ','.join(str(t) for t in THRESHOLD))
|
"of all lines. Defaults to "
|
||||||
|
"%s." % ','.join(str(t) for t in THRESHOLD))
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--erase-threshold',
|
'--erase-threshold',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: tuple(float(x) for x in x.split(',')),
|
type=lambda x: tuple(float(x) for x in x.split(',')),
|
||||||
const=THRESHOLD,
|
const=THRESHOLD,
|
||||||
help="Show lines with erases above this threshold as a percent of "
|
help="Show lines with erases above this threshold as a percent "
|
||||||
"all lines. Defaults to %s." % ','.join(str(t) for t in THRESHOLD))
|
"of all lines. Defaults to "
|
||||||
|
"%s." % ','.join(str(t) for t in THRESHOLD))
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-C', '--context',
|
'-C', '--context',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
default=3,
|
default=3,
|
||||||
help="Show n additional lines of context. Defaults to 3.")
|
help="Show n additional lines of context. Defaults to 3.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-W', '--width',
|
'-W', '--width',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
default=80,
|
default=80,
|
||||||
help="Assume source is styled with this many columns. Defaults to 80.")
|
help="Assume source is styled with this many columns. Defaults "
|
||||||
|
"to 80.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--color',
|
'--color',
|
||||||
choices=['never', 'always', 'auto'],
|
choices=['never', 'always', 'auto'],
|
||||||
default='auto',
|
default='auto',
|
||||||
help="When to use terminal colors. Defaults to 'auto'.")
|
help="When to use terminal colors. Defaults to 'auto'.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-j', '--jobs',
|
'-j', '--jobs',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
const=0,
|
const=0,
|
||||||
help="Number of processes to use. 0 spawns one process per core.")
|
help="Number of processes to use. 0 spawns one process per core.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--objdump-path',
|
'--objdump-path',
|
||||||
type=lambda x: x.split(),
|
type=lambda x: x.split(),
|
||||||
default=OBJDUMP_PATH,
|
default=OBJDUMP_PATH,
|
||||||
help="Path to the objdump executable, may include flags. "
|
help="Path to the objdump executable, may include flags. "
|
||||||
"Defaults to %r." % OBJDUMP_PATH)
|
"Defaults to %r." % OBJDUMP_PATH)
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+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 as mpl
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
|
||||||
# some nicer colors borrowed from Seaborn
|
# some nicer colors borrowed from Seaborn
|
||||||
# note these include a non-opaque alpha
|
# note these include a non-opaque alpha
|
||||||
COLORS = [
|
COLORS = [
|
||||||
@@ -148,8 +149,8 @@ class AutoMultipleLocator(mpl.ticker.MultipleLocator):
|
|||||||
nbins = np.clip(self.axis.get_tick_space(), 1, 9)
|
nbins = np.clip(self.axis.get_tick_space(), 1, 9)
|
||||||
|
|
||||||
# find the best power, use this as our locator's actual base
|
# find the best power, use this as our locator's actual base
|
||||||
scale = self.base
|
scale = (self.base
|
||||||
** (mt.ceil(mt.log((vmax-vmin) / (nbins+1), self.base)))
|
** (mt.ceil(mt.log((vmax-vmin) / (nbins+1), self.base))))
|
||||||
self.set_params(scale)
|
self.set_params(scale)
|
||||||
|
|
||||||
return super().__call__()
|
return super().__call__()
|
||||||
@@ -199,8 +200,8 @@ def collect(csv_paths, renames=[], defines=[]):
|
|||||||
with openio(path) as f:
|
with openio(path) as f:
|
||||||
reader = csv.DictReader(f, restval='')
|
reader = csv.DictReader(f, restval='')
|
||||||
fields.extend(
|
fields.extend(
|
||||||
k for k in reader.fieldnames
|
k for k in reader.fieldnames
|
||||||
if k not in fields)
|
if k not in fields)
|
||||||
for r in reader:
|
for r in reader:
|
||||||
# apply any renames
|
# apply any renames
|
||||||
if renames:
|
if renames:
|
||||||
@@ -249,7 +250,7 @@ def fold(results, by=None, x=None, y=None, defines=[], labels=None):
|
|||||||
# filter by 'by'
|
# filter by 'by'
|
||||||
if by and not all(
|
if by and not all(
|
||||||
k in r and r[k] == v
|
k in r and r[k] == v
|
||||||
for k, v in zip(by, key)):
|
for k, v in zip(by, key)):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# find xs
|
# find xs
|
||||||
@@ -353,9 +354,9 @@ class Grid:
|
|||||||
self_i = 0
|
self_i = 0
|
||||||
other_i = 0
|
other_i = 0
|
||||||
self_xweight = (self_xweights[self_i]
|
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]
|
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):
|
while self_i < len(self_xweights) and other_i < len(other_xweights):
|
||||||
if other_xweight - self_xweight > 0.0000001:
|
if other_xweight - self_xweight > 0.0000001:
|
||||||
new_xweights.append(self_xweight)
|
new_xweights.append(self_xweight)
|
||||||
@@ -374,7 +375,7 @@ class Grid:
|
|||||||
|
|
||||||
self_i += 1
|
self_i += 1
|
||||||
self_xweight = (self_xweights[self_i]
|
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:
|
elif self_xweight - other_xweight > 0.0000001:
|
||||||
new_xweights.append(other_xweight)
|
new_xweights.append(other_xweight)
|
||||||
self_xweight -= other_xweight
|
self_xweight -= other_xweight
|
||||||
@@ -392,7 +393,7 @@ class Grid:
|
|||||||
|
|
||||||
other_i += 1
|
other_i += 1
|
||||||
other_xweight = (other_xweights[other_i]
|
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:
|
else:
|
||||||
new_xweights.append(self_xweight)
|
new_xweights.append(self_xweight)
|
||||||
|
|
||||||
@@ -404,10 +405,10 @@ class Grid:
|
|||||||
|
|
||||||
self_i += 1
|
self_i += 1
|
||||||
self_xweight = (self_xweights[self_i]
|
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_i += 1
|
||||||
other_xweight = (other_xweights[other_i]
|
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
|
# squish so ratios are preserved
|
||||||
self_h = sum(self.yweights)
|
self_h = sum(self.yweights)
|
||||||
@@ -423,8 +424,9 @@ class Grid:
|
|||||||
|
|
||||||
self.xweights = new_xweights
|
self.xweights = new_xweights
|
||||||
self.yweights = self_yweights + other.yweights
|
self.yweights = self_yweights + other.yweights
|
||||||
self.map = self_map | {(x, y+len(self_yweights)): s
|
self.map = self_map | {
|
||||||
for (x, y), s in other_map.items()}
|
(x, y+len(self_yweights)): s
|
||||||
|
for (x, y), s in other_map.items()}
|
||||||
else:
|
else:
|
||||||
for s in self.subplots:
|
for s in self.subplots:
|
||||||
s.y += len(other.yweights)
|
s.y += len(other.yweights)
|
||||||
@@ -432,8 +434,9 @@ class Grid:
|
|||||||
|
|
||||||
self.xweights = new_xweights
|
self.xweights = new_xweights
|
||||||
self.yweights = other.yweights + self_yweights
|
self.yweights = other.yweights + self_yweights
|
||||||
self.map = other_map | {(x, y+len(other.yweights)): s
|
self.map = other_map | {
|
||||||
for (x, y), s in self_map.items()}
|
(x, y+len(other.yweights)): s
|
||||||
|
for (x, y), s in self_map.items()}
|
||||||
|
|
||||||
if dir in ['right', 'left']:
|
if dir in ['right', 'left']:
|
||||||
# first scale the two grids so they line up
|
# first scale the two grids so they line up
|
||||||
@@ -451,9 +454,9 @@ class Grid:
|
|||||||
self_i = 0
|
self_i = 0
|
||||||
other_i = 0
|
other_i = 0
|
||||||
self_yweight = (self_yweights[self_i]
|
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]
|
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):
|
while self_i < len(self_yweights) and other_i < len(other_yweights):
|
||||||
if other_yweight - self_yweight > 0.0000001:
|
if other_yweight - self_yweight > 0.0000001:
|
||||||
new_yweights.append(self_yweight)
|
new_yweights.append(self_yweight)
|
||||||
@@ -472,7 +475,7 @@ class Grid:
|
|||||||
|
|
||||||
self_i += 1
|
self_i += 1
|
||||||
self_yweight = (self_yweights[self_i]
|
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:
|
elif self_yweight - other_yweight > 0.0000001:
|
||||||
new_yweights.append(other_yweight)
|
new_yweights.append(other_yweight)
|
||||||
self_yweight -= other_yweight
|
self_yweight -= other_yweight
|
||||||
@@ -490,7 +493,7 @@ class Grid:
|
|||||||
|
|
||||||
other_i += 1
|
other_i += 1
|
||||||
other_yweight = (other_yweights[other_i]
|
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:
|
else:
|
||||||
new_yweights.append(self_yweight)
|
new_yweights.append(self_yweight)
|
||||||
|
|
||||||
@@ -502,10 +505,10 @@ class Grid:
|
|||||||
|
|
||||||
self_i += 1
|
self_i += 1
|
||||||
self_yweight = (self_yweights[self_i]
|
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_i += 1
|
||||||
other_yweight = (other_yweights[other_i]
|
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
|
# squish so ratios are preserved
|
||||||
self_w = sum(self.xweights)
|
self_w = sum(self.xweights)
|
||||||
@@ -521,8 +524,9 @@ class Grid:
|
|||||||
|
|
||||||
self.xweights = self_xweights + other.xweights
|
self.xweights = self_xweights + other.xweights
|
||||||
self.yweights = new_yweights
|
self.yweights = new_yweights
|
||||||
self.map = self_map | {(x+len(self_xweights), y): s
|
self.map = self_map | {
|
||||||
for (x, y), s in other_map.items()}
|
(x+len(self_xweights), y): s
|
||||||
|
for (x, y), s in other_map.items()}
|
||||||
else:
|
else:
|
||||||
for s in self.subplots:
|
for s in self.subplots:
|
||||||
s.x += len(other.xweights)
|
s.x += len(other.xweights)
|
||||||
@@ -530,8 +534,9 @@ class Grid:
|
|||||||
|
|
||||||
self.xweights = other.xweights + self_xweights
|
self.xweights = other.xweights + self_xweights
|
||||||
self.yweights = new_yweights
|
self.yweights = new_yweights
|
||||||
self.map = other_map | {(x+len(other.xweights), y): s
|
self.map = other_map | {
|
||||||
for (x, y), s in self_map.items()}
|
(x+len(other.xweights), y): s
|
||||||
|
for (x, y), s in self_map.items()}
|
||||||
|
|
||||||
|
|
||||||
def scale(self, width, height):
|
def scale(self, width, height):
|
||||||
@@ -546,11 +551,11 @@ class Grid:
|
|||||||
|
|
||||||
for dir, subargs in subplots:
|
for dir, subargs in subplots:
|
||||||
subgrid = cls.fromargs(
|
subgrid = cls.fromargs(
|
||||||
width=subargs.pop('width',
|
width=subargs.pop('width',
|
||||||
0.5 if dir in ['right', 'left'] else width),
|
0.5 if dir in ['right', 'left'] else width),
|
||||||
height=subargs.pop('height',
|
height=subargs.pop('height',
|
||||||
0.5 if dir in ['above', 'below'] else height),
|
0.5 if dir in ['above', 'below'] else height),
|
||||||
**subargs)
|
**subargs)
|
||||||
grid.merge(subgrid, dir)
|
grid.merge(subgrid, dir)
|
||||||
|
|
||||||
grid.scale(width, height)
|
grid.scale(width, height)
|
||||||
@@ -668,8 +673,8 @@ def main(csv_paths, output, *,
|
|||||||
# fix ggplot when dark
|
# fix ggplot when dark
|
||||||
if ggplot:
|
if ggplot:
|
||||||
plt.rc('axes',
|
plt.rc('axes',
|
||||||
facecolor=foreground_,
|
facecolor=foreground_,
|
||||||
edgecolor=background_)
|
edgecolor=background_)
|
||||||
plt.rc('grid', color=background_)
|
plt.rc('grid', color=background_)
|
||||||
|
|
||||||
if font is not None:
|
if font is not None:
|
||||||
@@ -677,22 +682,22 @@ def main(csv_paths, output, *,
|
|||||||
plt.rc('font', size=font_size)
|
plt.rc('font', size=font_size)
|
||||||
plt.rc('text', color=font_color_)
|
plt.rc('text', color=font_color_)
|
||||||
plt.rc('figure',
|
plt.rc('figure',
|
||||||
titlesize='medium',
|
titlesize='medium',
|
||||||
labelsize='small')
|
labelsize='small')
|
||||||
plt.rc('axes',
|
plt.rc('axes',
|
||||||
titlesize='small',
|
titlesize='small',
|
||||||
labelsize='small',
|
labelsize='small',
|
||||||
labelcolor=font_color_)
|
labelcolor=font_color_)
|
||||||
if not ggplot:
|
if not ggplot:
|
||||||
plt.rc('axes', edgecolor=font_color_)
|
plt.rc('axes', edgecolor=font_color_)
|
||||||
plt.rc('xtick', labelsize='small', color=font_color_)
|
plt.rc('xtick', labelsize='small', color=font_color_)
|
||||||
plt.rc('ytick', labelsize='small', color=font_color_)
|
plt.rc('ytick', labelsize='small', color=font_color_)
|
||||||
plt.rc('legend',
|
plt.rc('legend',
|
||||||
fontsize='small',
|
fontsize='small',
|
||||||
fancybox=False,
|
fancybox=False,
|
||||||
framealpha=None,
|
framealpha=None,
|
||||||
edgecolor=foreground_,
|
edgecolor=foreground_,
|
||||||
borderaxespad=0)
|
borderaxespad=0)
|
||||||
plt.rc('axes.spines', top=False, right=False)
|
plt.rc('axes.spines', top=False, right=False)
|
||||||
|
|
||||||
plt.rc('figure', facecolor=background_, edgecolor=background_)
|
plt.rc('figure', facecolor=background_, edgecolor=background_)
|
||||||
@@ -723,19 +728,19 @@ def main(csv_paths, output, *,
|
|||||||
all_defines[k] |= vs
|
all_defines[k] |= vs
|
||||||
all_defines = sorted(all_defines.items())
|
all_defines = sorted(all_defines.items())
|
||||||
all_labels = ((label or [])
|
all_labels = ((label or [])
|
||||||
+ subplots_get('label', **subplot, subplots=subplots))
|
+ subplots_get('label', **subplot, subplots=subplots))
|
||||||
|
|
||||||
# separate out renames
|
# separate out renames
|
||||||
all_renames = list(it.chain.from_iterable(
|
all_renames = list(it.chain.from_iterable(
|
||||||
((k, v) for v in vs)
|
((k, v) for v in vs)
|
||||||
for k, vs in it.chain(all_by, all_x, all_y)))
|
for k, vs in it.chain(all_by, all_x, all_y)))
|
||||||
all_by = [k for k, _ in all_by]
|
all_by = [k for k, _ in all_by]
|
||||||
all_x = [k for k, _ in all_x]
|
all_x = [k for k, _ in all_x]
|
||||||
all_y = [k for k, _ in all_y]
|
all_y = [k for k, _ in all_y]
|
||||||
|
|
||||||
if not all_by and not all_y:
|
if not all_by and not all_y:
|
||||||
print("error: needs --by or -y to figure out fields",
|
print("error: needs --by or -y to figure out fields",
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# first collect results from CSV files
|
# 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 y not specified, guess it's anything not in by/defines/x/renames
|
||||||
if not all_y:
|
if not all_y:
|
||||||
all_y = [
|
all_y = [k for k in fields_
|
||||||
k for k in fields_
|
|
||||||
if k not in all_by
|
if k not in all_by
|
||||||
and not any(k == k_ for k_, _ in all_defines)
|
and not any(k == k_ for k_, _ in all_defines)
|
||||||
and not any(k == old_k for _, old_k in all_renames)]
|
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
|
# figure out formats/colors here so that subplot defines don't change
|
||||||
# them later, that'd be bad
|
# them later, that'd be bad
|
||||||
dataformats_ = {
|
dataformats_ = {
|
||||||
name: formats_[i % len(formats_)]
|
name: formats_[i % len(formats_)]
|
||||||
for i, name in enumerate(datasets_.keys())}
|
for i, name in enumerate(datasets_.keys())}
|
||||||
datacolors_ = {
|
datacolors_ = {
|
||||||
name: colors_[i % len(colors_)]
|
name: colors_[i % len(colors_)]
|
||||||
for i, name in enumerate(datasets_.keys())}
|
for i, name in enumerate(datasets_.keys())}
|
||||||
|
|
||||||
# create a grid of subplots
|
# create a grid of subplots
|
||||||
grid = Grid.fromargs(**subplot, subplots=subplots)
|
grid = Grid.fromargs(**subplot, subplots=subplots)
|
||||||
|
|
||||||
# create a matplotlib plot
|
# create a matplotlib plot
|
||||||
fig = plt.figure(figsize=(
|
fig = plt.figure(
|
||||||
width/plt.rcParams['figure.dpi'],
|
figsize=(
|
||||||
height/plt.rcParams['figure.dpi']),
|
width/plt.rcParams['figure.dpi'],
|
||||||
layout='constrained',
|
height/plt.rcParams['figure.dpi']),
|
||||||
# we need a linewidth to keep xkcd mode happy
|
layout='constrained',
|
||||||
linewidth=8 if xkcd else 0)
|
# we need a linewidth to keep xkcd mode happy
|
||||||
|
linewidth=8 if xkcd else 0)
|
||||||
|
|
||||||
gs = fig.add_gridspec(
|
gs = fig.add_gridspec(
|
||||||
grid.height
|
grid.height
|
||||||
+ (1 if legend_above else 0)
|
+ (1 if legend_above else 0)
|
||||||
+ (1 if legend_below else 0),
|
+ (1 if legend_below else 0),
|
||||||
grid.width
|
grid.width
|
||||||
+ (1 if legend_right else 0),
|
+ (1 if legend_right else 0),
|
||||||
height_ratios=([0.001] if legend_above else [])
|
height_ratios=([0.001] if legend_above else [])
|
||||||
+ [max(s, 0.01) for s in reversed(grid.yweights)]
|
+ [max(s, 0.01) for s in reversed(grid.yweights)]
|
||||||
+ ([0.001] if legend_below else []),
|
+ ([0.001] if legend_below else []),
|
||||||
width_ratios=[max(s, 0.01) for s in grid.xweights]
|
width_ratios=[max(s, 0.01) for s in grid.xweights]
|
||||||
+ ([0.001] if legend_right else []))
|
+ ([0.001] if legend_right else []))
|
||||||
|
|
||||||
# first create axes so that plots can interact with each other
|
# first create axes so that plots can interact with each other
|
||||||
for s in grid:
|
for s in grid:
|
||||||
s.ax = fig.add_subplot(gs[
|
s.ax = fig.add_subplot(gs[
|
||||||
grid.height-(s.y+s.yspan) + (1 if legend_above else 0)
|
grid.height-(s.y+s.yspan) + (1 if legend_above else 0)
|
||||||
: grid.height-s.y + (1 if legend_above else 0),
|
: grid.height-s.y + (1 if legend_above else 0),
|
||||||
s.x
|
s.x
|
||||||
: s.x+s.xspan])
|
: s.x+s.xspan])
|
||||||
|
|
||||||
# now plot each subplot
|
# now plot each subplot
|
||||||
for s in grid:
|
for s in grid:
|
||||||
@@ -830,18 +835,18 @@ def main(csv_paths, output, *,
|
|||||||
|
|
||||||
# filter by subplot x/y
|
# filter by subplot x/y
|
||||||
subdatasets = co.OrderedDict([(name, dataset)
|
subdatasets = co.OrderedDict([(name, dataset)
|
||||||
for name, dataset in subdatasets.items()
|
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_x) <= 1 or name[-(1 if len(all_y) <= 1 else 2)] in x_
|
||||||
if len(all_y) <= 1 or name[-1] in y_])
|
if len(all_y) <= 1 or name[-1] in y_])
|
||||||
|
|
||||||
# plot!
|
# plot!
|
||||||
ax = s.ax
|
ax = s.ax
|
||||||
for name, dataset in subdatasets.items():
|
for name, dataset in subdatasets.items():
|
||||||
dats = sorted((x,y) for x,y in dataset)
|
dats = sorted((x,y) for x,y in dataset)
|
||||||
ax.plot([x for x,_ in dats], [y for _,y in dats],
|
ax.plot([x for x,_ in dats], [y for _,y in dats],
|
||||||
dataformats_[name],
|
dataformats_[name],
|
||||||
color=datacolors_[name],
|
color=datacolors_[name],
|
||||||
label=','.join(name))
|
label=','.join(name))
|
||||||
|
|
||||||
# axes scaling
|
# axes scaling
|
||||||
if xlog_:
|
if xlog_:
|
||||||
@@ -852,31 +857,31 @@ def main(csv_paths, output, *,
|
|||||||
ax.yaxis.set_minor_locator(mpl.ticker.NullLocator())
|
ax.yaxis.set_minor_locator(mpl.ticker.NullLocator())
|
||||||
# axes limits
|
# axes limits
|
||||||
ax.set_xlim(
|
ax.set_xlim(
|
||||||
xlim_[0] if xlim_[0] is not None
|
xlim_[0] if xlim_[0] is not None
|
||||||
else min(it.chain([0], (x
|
else min(it.chain([0], (x
|
||||||
for dataset in subdatasets.values()
|
for dataset in subdatasets.values()
|
||||||
for x, y in dataset
|
for x, y in dataset
|
||||||
if y is not None))),
|
if y is not None))),
|
||||||
xlim_[1] if xlim_[1] is not None
|
xlim_[1] if xlim_[1] is not None
|
||||||
else max(it.chain([0], (x
|
else max(it.chain([0], (x
|
||||||
for r in subdatasets.values()
|
for r in subdatasets.values()
|
||||||
for x, y in dataset
|
for x, y in dataset
|
||||||
if y is not None))))
|
if y is not None))))
|
||||||
ax.set_ylim(
|
ax.set_ylim(
|
||||||
ylim_[0] if ylim_[0] is not None
|
ylim_[0] if ylim_[0] is not None
|
||||||
else min(it.chain([0], (y
|
else min(it.chain([0], (y
|
||||||
for dataset in subdatasets.values()
|
for dataset in subdatasets.values()
|
||||||
for _, y in dataset
|
for _, y in dataset
|
||||||
if y is not None))),
|
if y is not None))),
|
||||||
ylim_[1] if ylim_[1] is not None
|
ylim_[1] if ylim_[1] is not None
|
||||||
else max(it.chain([0], (y
|
else max(it.chain([0], (y
|
||||||
for dataset in subdatasets.values()
|
for dataset in subdatasets.values()
|
||||||
for _, y in dataset
|
for _, y in dataset
|
||||||
if y is not None))))
|
if y is not None))))
|
||||||
# axes ticks
|
# axes ticks
|
||||||
if x2_:
|
if x2_:
|
||||||
ax.xaxis.set_major_formatter(lambda x, pos:
|
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:
|
if xticklabels_ is not None:
|
||||||
ax.xaxis.set_ticklabels(xticklabels_)
|
ax.xaxis.set_ticklabels(xticklabels_)
|
||||||
if xticks_ is None:
|
if xticks_ is None:
|
||||||
@@ -889,7 +894,7 @@ def main(csv_paths, output, *,
|
|||||||
ax.xaxis.set_major_locator(mpl.ticker.NullLocator())
|
ax.xaxis.set_major_locator(mpl.ticker.NullLocator())
|
||||||
else:
|
else:
|
||||||
ax.xaxis.set_major_formatter(lambda x, pos:
|
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:
|
if xticklabels_ is not None:
|
||||||
ax.xaxis.set_ticklabels(xticklabels_)
|
ax.xaxis.set_ticklabels(xticklabels_)
|
||||||
if xticks_ is None:
|
if xticks_ is None:
|
||||||
@@ -902,7 +907,7 @@ def main(csv_paths, output, *,
|
|||||||
ax.xaxis.set_major_locator(mpl.ticker.NullLocator())
|
ax.xaxis.set_major_locator(mpl.ticker.NullLocator())
|
||||||
if y2_:
|
if y2_:
|
||||||
ax.yaxis.set_major_formatter(lambda x, pos:
|
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:
|
if yticklabels_ is not None:
|
||||||
ax.yaxis.set_ticklabels(yticklabels_)
|
ax.yaxis.set_ticklabels(yticklabels_)
|
||||||
if yticks_ is None:
|
if yticks_ is None:
|
||||||
@@ -915,7 +920,7 @@ def main(csv_paths, output, *,
|
|||||||
ax.yaxis.set_major_locator(mpl.ticker.NullLocator())
|
ax.yaxis.set_major_locator(mpl.ticker.NullLocator())
|
||||||
else:
|
else:
|
||||||
ax.yaxis.set_major_formatter(lambda x, pos:
|
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:
|
if yticklabels_ is not None:
|
||||||
ax.yaxis.set_ticklabels(yticklabels_)
|
ax.yaxis.set_ticklabels(yticklabels_)
|
||||||
if yticks_ is None:
|
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 = fig.add_subplot(gs[(1 if legend_above else 0):,-1])
|
||||||
ax.set_axis_off()
|
ax.set_axis_off()
|
||||||
ax.legend(
|
ax.legend(
|
||||||
[h for _,h in legend],
|
[h for _,h in legend],
|
||||||
[l for l,_ in legend],
|
[l for l,_ in legend],
|
||||||
loc='upper left',
|
loc='upper left',
|
||||||
fancybox=False,
|
fancybox=False,
|
||||||
borderaxespad=0)
|
borderaxespad=0)
|
||||||
|
|
||||||
if legend_above:
|
if legend_above:
|
||||||
ax = fig.add_subplot(gs[0, :grid.width])
|
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_ = [l for l in legend_ if l is not None]
|
||||||
|
|
||||||
legend_ = ax.legend(
|
legend_ = ax.legend(
|
||||||
[h for _,h in legend_],
|
[h for _,h in legend_],
|
||||||
[l for l,_ in legend_],
|
[l for l,_ in legend_],
|
||||||
loc='upper center',
|
loc='upper center',
|
||||||
ncol=ncol,
|
ncol=ncol,
|
||||||
fancybox=False,
|
fancybox=False,
|
||||||
borderaxespad=0)
|
borderaxespad=0)
|
||||||
|
|
||||||
if (legend_.get_window_extent().width
|
if (legend_.get_window_extent().width
|
||||||
<= ax.get_window_extent().width):
|
<= ax.get_window_extent().width):
|
||||||
@@ -1007,8 +1012,8 @@ def main(csv_paths, output, *,
|
|||||||
# works really well actually
|
# works really well actually
|
||||||
if xlabel:
|
if xlabel:
|
||||||
ax.set_title(escape(xlabel),
|
ax.set_title(escape(xlabel),
|
||||||
size=plt.rcParams['axes.labelsize'],
|
size=plt.rcParams['axes.labelsize'],
|
||||||
weight=plt.rcParams['axes.labelweight'])
|
weight=plt.rcParams['axes.labelweight'])
|
||||||
|
|
||||||
# try different column counts until we fit in the axes
|
# try different column counts until we fit in the axes
|
||||||
for ncol in reversed(range(1, len(legend)+1)):
|
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_ = [l for l in legend_ if l is not None]
|
||||||
|
|
||||||
legend_ = ax.legend(
|
legend_ = ax.legend(
|
||||||
[h for _,h in legend_],
|
[h for _,h in legend_],
|
||||||
[l for l,_ in legend_],
|
[l for l,_ in legend_],
|
||||||
loc='upper center',
|
loc='upper center',
|
||||||
ncol=ncol,
|
ncol=ncol,
|
||||||
fancybox=False,
|
fancybox=False,
|
||||||
borderaxespad=0)
|
borderaxespad=0)
|
||||||
|
|
||||||
if (legend_.get_window_extent().width
|
if (legend_.get_window_extent().width
|
||||||
<= ax.get_window_extent().width):
|
<= ax.get_window_extent().width):
|
||||||
@@ -1062,9 +1067,9 @@ def main(csv_paths, output, *,
|
|||||||
# some stats
|
# some stats
|
||||||
if not quiet:
|
if not quiet:
|
||||||
print('updated %s, %s datasets, %s points' % (
|
print('updated %s, %s datasets, %s points' % (
|
||||||
output,
|
output,
|
||||||
len(datasets_),
|
len(datasets_),
|
||||||
sum(len(dataset) for dataset in datasets_.values())))
|
sum(len(dataset) for dataset in datasets_.values())))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -1072,265 +1077,267 @@ if __name__ == "__main__":
|
|||||||
import argparse
|
import argparse
|
||||||
import re
|
import re
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Plot CSV files with matplotlib.",
|
description="Plot CSV files with matplotlib.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'csv_paths',
|
'csv_paths',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
help="Input *.csv files.")
|
help="Input *.csv files.")
|
||||||
output_rule = parser.add_argument(
|
output_rule = parser.add_argument(
|
||||||
'-o', '--output',
|
'-o', '--output',
|
||||||
required=True,
|
required=True,
|
||||||
help="Output *.svg/*.png file.")
|
help="Output *.svg/*.png file.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--svg',
|
'--svg',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Output an svg file. By default this is infered.")
|
help="Output an svg file. By default this is infered.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--png',
|
'--png',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Output a png file. By default this is infered.")
|
help="Output a png file. By default this is infered.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-q', '--quiet',
|
'-q', '--quiet',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Don't print info.")
|
help="Don't print info.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--by',
|
'-b', '--by',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs=None: (
|
lambda k, vs=None: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
tuple(v.strip() for v in vs.split(','))
|
tuple(v.strip() for v in vs.split(','))
|
||||||
if vs is not None else ())
|
if vs is not None else ())
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Group by this field. Can rename fields with new_name=old_name.")
|
help="Group by this field. Can rename fields with "
|
||||||
|
"new_name=old_name.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-x',
|
'-x',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs=None: (
|
lambda k, vs=None: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
tuple(v.strip() for v in vs.split(','))
|
tuple(v.strip() for v in vs.split(','))
|
||||||
if vs is not None else ())
|
if vs is not None else ())
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Field to use for the x-axis. Can rename fields with "
|
help="Field to use for the x-axis. Can rename fields with "
|
||||||
"new_name=old_name.")
|
"new_name=old_name.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-y',
|
'-y',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs=None: (
|
lambda k, vs=None: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
tuple(v.strip() for v in vs.split(','))
|
tuple(v.strip() for v in vs.split(','))
|
||||||
if vs is not None else ())
|
if vs is not None else ())
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Field to use for the y-axis. Can rename fields with "
|
help="Field to use for the y-axis. Can rename fields with "
|
||||||
"new_name=old_name.")
|
"new_name=old_name.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-D', '--define',
|
'-D', '--define',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs: (
|
lambda k, vs: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
{v.strip() for v in vs.split(',')})
|
{v.strip() for v in vs.split(',')})
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
action='append',
|
action='append',
|
||||||
help="Only include results where this field is this value. May include "
|
help="Only include results where this field is this value. May "
|
||||||
"comma-separated options.")
|
"include comma-separated options.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-L', '--label',
|
'-L', '--label',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs: (
|
lambda k, vs: (
|
||||||
re.sub(r'\\([=\\])', r'\1', k.strip()),
|
re.sub(r'\\([=\\])', r'\1', k.strip()),
|
||||||
tuple(v.strip() for v in vs.split(',')))
|
tuple(v.strip() for v in vs.split(',')))
|
||||||
)(*re.split(r'(?<!\\)=', x, 1)),
|
)(*re.split(r'(?<!\\)=', x, 1)),
|
||||||
help="Use this label for a given group, where a group is roughly the "
|
help="Use this label for a given group, where a group is roughly "
|
||||||
"comma-separated values in the -b/--by, -x, and -y fields. Also "
|
"the comma-separated values in the -b/--by, -x, and -y "
|
||||||
"provides an ordering. Accepts escaped equals.")
|
"fields. Also provides an ordering. Accepts escaped equals.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-.', '--points',
|
'-.', '--points',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Only draw data points.")
|
help="Only draw data points.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-!', '--points-and-lines',
|
'-!', '--points-and-lines',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Draw data points and lines.")
|
help="Draw data points and lines.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--colors',
|
'--colors',
|
||||||
type=lambda x: [x.strip() for x in x.split(',')],
|
type=lambda x: [x.strip() for x in x.split(',')],
|
||||||
help="Comma-separated hex colors to use.")
|
help="Comma-separated hex colors to use.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--formats',
|
'--formats',
|
||||||
type=lambda x: [re.sub(r'\\([,\\])', r'\1', x.strip())
|
type=lambda x: [re.sub(r'\\([,\\])', r'\1', x.strip())
|
||||||
for x in re.split(r'(?<!\\),', x)],
|
for x in re.split(r'(?<!\\),', x)],
|
||||||
help="Comma-separated matplotlib formats to use. Accepts escaped "
|
help="Comma-separated matplotlib formats to use. Accepts escaped "
|
||||||
"commas.")
|
"commas.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-W', '--width',
|
'-W', '--width',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Width in pixels. Defaults to %r." % WIDTH)
|
help="Width in pixels. Defaults to %r." % WIDTH)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-H', '--height',
|
'-H', '--height',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Height in pixels. Defaults to %r." % HEIGHT)
|
help="Height in pixels. Defaults to %r." % HEIGHT)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-X', '--xlim',
|
'-X', '--xlim',
|
||||||
type=lambda x: tuple(
|
type=lambda x: tuple(
|
||||||
dat(x) if x.strip() else None
|
dat(x) if x.strip() else None
|
||||||
for x in x.split(',')),
|
for x in x.split(',')),
|
||||||
help="Range for the x-axis.")
|
help="Range for the x-axis.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-Y', '--ylim',
|
'-Y', '--ylim',
|
||||||
type=lambda x: tuple(
|
type=lambda x: tuple(
|
||||||
dat(x) if x.strip() else None
|
dat(x) if x.strip() else None
|
||||||
for x in x.split(',')),
|
for x in x.split(',')),
|
||||||
help="Range for the y-axis.")
|
help="Range for the y-axis.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--xlog',
|
'--xlog',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Use a logarithmic x-axis.")
|
help="Use a logarithmic x-axis.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--ylog',
|
'--ylog',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Use a logarithmic y-axis.")
|
help="Use a logarithmic y-axis.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--x2',
|
'--x2',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Use base-2 prefixes for the x-axis.")
|
help="Use base-2 prefixes for the x-axis.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--y2',
|
'--y2',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Use base-2 prefixes for the y-axis.")
|
help="Use base-2 prefixes for the y-axis.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--xticks',
|
'--xticks',
|
||||||
type=lambda x: int(x, 0) if ',' not in x
|
type=lambda x: int(x, 0) if ',' not in x
|
||||||
else [dat(x) for x in x.split(',')],
|
else [dat(x) for x in x.split(',')],
|
||||||
help="Ticks for the x-axis. This can be explicit comma-separated "
|
help="Ticks for the x-axis. This can be explicit comma-separated "
|
||||||
"ticks, the number of ticks, or 0 to disable.")
|
"ticks, the number of ticks, or 0 to disable.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--yticks',
|
'--yticks',
|
||||||
type=lambda x: int(x, 0) if ',' not in x
|
type=lambda x: int(x, 0) if ',' not in x
|
||||||
else [dat(x) for x in x.split(',')],
|
else [dat(x) for x in x.split(',')],
|
||||||
help="Ticks for the y-axis. This can be explicit comma-separated "
|
help="Ticks for the y-axis. This can be explicit comma-separated "
|
||||||
"ticks, the number of ticks, or 0 to disable.")
|
"ticks, the number of ticks, or 0 to disable.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--xunits',
|
'--xunits',
|
||||||
help="Units for the x-axis.")
|
help="Units for the x-axis.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--yunits',
|
'--yunits',
|
||||||
help="Units for the y-axis.")
|
help="Units for the y-axis.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--xlabel',
|
'--xlabel',
|
||||||
help="Add a label to the x-axis.")
|
help="Add a label to the x-axis.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--ylabel',
|
'--ylabel',
|
||||||
help="Add a label to the y-axis.")
|
help="Add a label to the y-axis.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--xticklabels',
|
'--xticklabels',
|
||||||
type=lambda x: [re.sub(r'\\([,\\])', r'\1', x.strip())
|
type=lambda x: [re.sub(r'\\([,\\])', r'\1', x.strip())
|
||||||
for x in re.split(r'(?<!\\),', x)]
|
for x in re.split(r'(?<!\\),', x)]
|
||||||
if x.strip() else [],
|
if x.strip() else [],
|
||||||
help="Comma separated xticklabels. Accepts escaped commas.")
|
help="Comma separated xticklabels. Accepts escaped commas.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--yticklabels',
|
'--yticklabels',
|
||||||
type=lambda x: [re.sub(r'\\([,\\])', r'\1', x.strip())
|
type=lambda x: [re.sub(r'\\([,\\])', r'\1', x.strip())
|
||||||
for x in re.split(r'(?<!\\),', x)]
|
for x in re.split(r'(?<!\\),', x)]
|
||||||
if x.strip() else [],
|
if x.strip() else [],
|
||||||
help="Comma separated yticklabels. Accepts escaped commas.")
|
help="Comma separated yticklabels. Accepts escaped commas.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-t', '--title',
|
'-t', '--title',
|
||||||
help="Add a title.")
|
help="Add a title.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-l', '--legend', '--legend-right',
|
'-l', '--legend', '--legend-right',
|
||||||
dest='legend_right',
|
dest='legend_right',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Place a legend to the right.")
|
help="Place a legend to the right.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--legend-above',
|
'--legend-above',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Place a legend above.")
|
help="Place a legend above.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--legend-below',
|
'--legend-below',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Place a legend below.")
|
help="Place a legend below.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--dark',
|
'--dark',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Use the dark style.")
|
help="Use the dark style.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--ggplot',
|
'--ggplot',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Use the ggplot style.")
|
help="Use the ggplot style.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--xkcd',
|
'--xkcd',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Use the xkcd style.")
|
help="Use the xkcd style.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--font',
|
'--font',
|
||||||
type=lambda x: [x.strip() for x in x.split(',')],
|
type=lambda x: [x.strip() for x in x.split(',')],
|
||||||
help="Font family for matplotlib.")
|
help="Font family for matplotlib.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--font-size',
|
'--font-size',
|
||||||
help="Font size for matplotlib. Defaults to %r." % FONT_SIZE)
|
help="Font size for matplotlib. Defaults to %r." % FONT_SIZE)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--font-color',
|
'--font-color',
|
||||||
help="Color for the font and other line elements.")
|
help="Color for the font and other line elements.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--foreground',
|
'--foreground',
|
||||||
help="Foreground color to use.")
|
help="Foreground color to use.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--background',
|
'--background',
|
||||||
help="Background color to use.")
|
help="Background color to use.")
|
||||||
class AppendSubplot(argparse.Action):
|
class AppendSubplot(argparse.Action):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse(value):
|
def parse(value):
|
||||||
import copy
|
import copy
|
||||||
subparser = copy.deepcopy(parser)
|
subparser = copy.deepcopy(parser)
|
||||||
next(a for a in subparser._actions
|
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
|
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
|
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 ""))
|
return subparser.parse_intermixed_args(shlex.split(value or ""))
|
||||||
def __call__(self, parser, namespace, value, option):
|
def __call__(self, parser, namespace, value, option):
|
||||||
if not hasattr(namespace, 'subplots'):
|
if not hasattr(namespace, 'subplots'):
|
||||||
namespace.subplots = []
|
namespace.subplots = []
|
||||||
namespace.subplots.append((
|
namespace.subplots.append((
|
||||||
option.split('-')[-1],
|
option.split('-')[-1],
|
||||||
self.__class__.parse(value)))
|
self.__class__.parse(value)))
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--subplot-above',
|
'--subplot-above',
|
||||||
action=AppendSubplot,
|
action=AppendSubplot,
|
||||||
help="Add subplot above with the same dataset. Takes an arg string to "
|
help="Add subplot above with the same dataset. Takes an arg "
|
||||||
"control the subplot which supports most (but not all) of the "
|
"string to control the subplot which supports most (but "
|
||||||
"parameters listed here. The relative dimensions of the subplot "
|
"not all) of the parameters listed here. The relative "
|
||||||
"can be controlled with -W/-H which now take a percentage.")
|
"dimensions of the subplot can be controlled with -W/-H "
|
||||||
|
"which now take a percentage.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--subplot-below',
|
'--subplot-below',
|
||||||
action=AppendSubplot,
|
action=AppendSubplot,
|
||||||
help="Add subplot below with the same dataset.")
|
help="Add subplot below with the same dataset.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--subplot-left',
|
'--subplot-left',
|
||||||
action=AppendSubplot,
|
action=AppendSubplot,
|
||||||
help="Add subplot left with the same dataset.")
|
help="Add subplot left with the same dataset.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--subplot-right',
|
'--subplot-right',
|
||||||
action=AppendSubplot,
|
action=AppendSubplot,
|
||||||
help="Add subplot right with the same dataset.")
|
help="Add subplot right with the same dataset.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--subplot',
|
'--subplot',
|
||||||
type=AppendSubplot.parse,
|
type=AppendSubplot.parse,
|
||||||
help="Add subplot-specific arguments to the main plot.")
|
help="Add subplot-specific arguments to the main plot.")
|
||||||
|
|
||||||
def dictify(ns):
|
def dictify(ns):
|
||||||
if hasattr(ns, 'subplots'):
|
if hasattr(ns, 'subplots'):
|
||||||
ns.subplots = [(dir, dictify(subplot_ns))
|
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:
|
if ns.subplot is not None:
|
||||||
ns.subplot = dictify(ns.subplot)
|
ns.subplot = dictify(ns.subplot)
|
||||||
return {k: v
|
return {k: v
|
||||||
for k, v in vars(ns).items()
|
for k, v in vars(ns).items()
|
||||||
if v is not None}
|
if v is not None}
|
||||||
|
|
||||||
sys.exit(main(**dictify(parser.parse_intermixed_args())))
|
sys.exit(main(**dictify(parser.parse_intermixed_args())))
|
||||||
|
|||||||
+58
-61
@@ -13,6 +13,7 @@
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
LIMIT = 16
|
LIMIT = 16
|
||||||
|
|
||||||
CMP = {
|
CMP = {
|
||||||
@@ -133,15 +134,14 @@ def write_header(f, limit=LIMIT):
|
|||||||
|
|
||||||
# write assert macros
|
# write assert macros
|
||||||
for op, cmp in sorted(CMP.items()):
|
for op, cmp in sorted(CMP.items()):
|
||||||
f.writeln("#define __PRETTY_ASSERT_BOOL_%s(lh, rh) do { \\"
|
f.writeln("#define __PRETTY_ASSERT_BOOL_%s(lh, rh) do { \\" % (
|
||||||
% cmp.upper())
|
cmp.upper()))
|
||||||
f.writeln(" bool _lh = !!(lh); \\")
|
f.writeln(" bool _lh = !!(lh); \\")
|
||||||
f.writeln(" bool _rh = !!(rh); \\")
|
f.writeln(" bool _rh = !!(rh); \\")
|
||||||
f.writeln(" if (!(_lh %s _rh)) { \\" % op)
|
f.writeln(" if (!(_lh %s _rh)) { \\" % op)
|
||||||
f.writeln(" __pretty_assert_print( \\")
|
f.writeln(" __pretty_assert_print( \\")
|
||||||
f.writeln(" __FILE__, __LINE__, \\")
|
f.writeln(" __FILE__, __LINE__, \\")
|
||||||
f.writeln(" __pretty_assert_bool, \"%s\", \\"
|
f.writeln(" __pretty_assert_bool, \"%s\", \\" % cmp)
|
||||||
% cmp)
|
|
||||||
f.writeln(" &_lh, 0, \\")
|
f.writeln(" &_lh, 0, \\")
|
||||||
f.writeln(" &_rh, 0); \\")
|
f.writeln(" &_rh, 0); \\")
|
||||||
f.writeln(" __builtin_trap(); \\")
|
f.writeln(" __builtin_trap(); \\")
|
||||||
@@ -149,15 +149,14 @@ def write_header(f, limit=LIMIT):
|
|||||||
f.writeln("} while (0)")
|
f.writeln("} while (0)")
|
||||||
f.writeln()
|
f.writeln()
|
||||||
for op, cmp in sorted(CMP.items()):
|
for op, cmp in sorted(CMP.items()):
|
||||||
f.writeln("#define __PRETTY_ASSERT_INT_%s(lh, rh) do { \\"
|
f.writeln("#define __PRETTY_ASSERT_INT_%s(lh, rh) do { \\" % (
|
||||||
% cmp.upper())
|
cmp.upper()))
|
||||||
f.writeln(" __typeof__(rh) _lh = lh; \\")
|
f.writeln(" __typeof__(rh) _lh = lh; \\")
|
||||||
f.writeln(" __typeof__(rh) _rh = rh; \\")
|
f.writeln(" __typeof__(rh) _rh = rh; \\")
|
||||||
f.writeln(" if (!(_lh %s _rh)) { \\" % op)
|
f.writeln(" if (!(_lh %s _rh)) { \\" % op)
|
||||||
f.writeln(" __pretty_assert_print( \\")
|
f.writeln(" __pretty_assert_print( \\")
|
||||||
f.writeln(" __FILE__, __LINE__, \\")
|
f.writeln(" __FILE__, __LINE__, \\")
|
||||||
f.writeln(" __pretty_assert_int, \"%s\", \\"
|
f.writeln(" __pretty_assert_int, \"%s\", \\" % cmp)
|
||||||
% cmp)
|
|
||||||
f.writeln(" &(intmax_t){(intmax_t)_lh}, 0, \\")
|
f.writeln(" &(intmax_t){(intmax_t)_lh}, 0, \\")
|
||||||
f.writeln(" &(intmax_t){(intmax_t)_rh}, 0); \\")
|
f.writeln(" &(intmax_t){(intmax_t)_rh}, 0); \\")
|
||||||
f.writeln(" __builtin_trap(); \\")
|
f.writeln(" __builtin_trap(); \\")
|
||||||
@@ -165,15 +164,14 @@ def write_header(f, limit=LIMIT):
|
|||||||
f.writeln("} while (0)")
|
f.writeln("} while (0)")
|
||||||
f.writeln()
|
f.writeln()
|
||||||
for op, cmp in sorted(CMP.items()):
|
for op, cmp in sorted(CMP.items()):
|
||||||
f.writeln("#define __PRETTY_ASSERT_MEM_%s(lh, rh, size) do { \\"
|
f.writeln("#define __PRETTY_ASSERT_MEM_%s(lh, rh, size) do { \\" % (
|
||||||
% cmp.upper())
|
cmp.upper()))
|
||||||
f.writeln(" const void *_lh = lh; \\")
|
f.writeln(" const void *_lh = lh; \\")
|
||||||
f.writeln(" const void *_rh = rh; \\")
|
f.writeln(" const void *_rh = rh; \\")
|
||||||
f.writeln(" if (!(memcmp(_lh, _rh, size) %s 0)) { \\" % op)
|
f.writeln(" if (!(memcmp(_lh, _rh, size) %s 0)) { \\" % op)
|
||||||
f.writeln(" __pretty_assert_print( \\")
|
f.writeln(" __pretty_assert_print( \\")
|
||||||
f.writeln(" __FILE__, __LINE__, \\")
|
f.writeln(" __FILE__, __LINE__, \\")
|
||||||
f.writeln(" __pretty_assert_mem, \"%s\", \\"
|
f.writeln(" __pretty_assert_mem, \"%s\", \\" % cmp)
|
||||||
% cmp)
|
|
||||||
f.writeln(" _lh, size, \\")
|
f.writeln(" _lh, size, \\")
|
||||||
f.writeln(" _rh, size); \\")
|
f.writeln(" _rh, size); \\")
|
||||||
f.writeln(" __builtin_trap(); \\")
|
f.writeln(" __builtin_trap(); \\")
|
||||||
@@ -181,15 +179,14 @@ def write_header(f, limit=LIMIT):
|
|||||||
f.writeln("} while (0)")
|
f.writeln("} while (0)")
|
||||||
f.writeln()
|
f.writeln()
|
||||||
for op, cmp in sorted(CMP.items()):
|
for op, cmp in sorted(CMP.items()):
|
||||||
f.writeln("#define __PRETTY_ASSERT_STR_%s(lh, rh) do { \\"
|
f.writeln("#define __PRETTY_ASSERT_STR_%s(lh, rh) do { \\" % (
|
||||||
% cmp.upper())
|
cmp.upper()))
|
||||||
f.writeln(" const char *_lh = lh; \\")
|
f.writeln(" const char *_lh = lh; \\")
|
||||||
f.writeln(" const char *_rh = rh; \\")
|
f.writeln(" const char *_rh = rh; \\")
|
||||||
f.writeln(" if (!(strcmp(_lh, _rh) %s 0)) { \\" % op)
|
f.writeln(" if (!(strcmp(_lh, _rh) %s 0)) { \\" % op)
|
||||||
f.writeln(" __pretty_assert_print( \\")
|
f.writeln(" __pretty_assert_print( \\")
|
||||||
f.writeln(" __FILE__, __LINE__, \\")
|
f.writeln(" __FILE__, __LINE__, \\")
|
||||||
f.writeln(" __pretty_assert_str, \"%s\", \\"
|
f.writeln(" __pretty_assert_str, \"%s\", \\" % cmp)
|
||||||
% cmp)
|
|
||||||
f.writeln(" _lh, strlen(_lh), \\")
|
f.writeln(" _lh, strlen(_lh), \\")
|
||||||
f.writeln(" _rh, strlen(_rh)); \\")
|
f.writeln(" _rh, strlen(_rh)); \\")
|
||||||
f.writeln(" __builtin_trap(); \\")
|
f.writeln(" __builtin_trap(); \\")
|
||||||
@@ -206,11 +203,11 @@ def write_header(f, limit=LIMIT):
|
|||||||
|
|
||||||
def mkassert(type, cmp, lh, rh, size=None):
|
def mkassert(type, cmp, lh, rh, size=None):
|
||||||
if size is not None:
|
if size is not None:
|
||||||
return ("__PRETTY_ASSERT_%s_%s(%s, %s, %s)"
|
return ("__PRETTY_ASSERT_%s_%s(%s, %s, %s)" % (
|
||||||
% (type.upper(), cmp.upper(), lh, rh, size))
|
type.upper(), cmp.upper(), lh, rh, size))
|
||||||
else:
|
else:
|
||||||
return ("__PRETTY_ASSERT_%s_%s(%s, %s)"
|
return ("__PRETTY_ASSERT_%s_%s(%s, %s)" % (
|
||||||
% (type.upper(), cmp.upper(), lh, rh))
|
type.upper(), cmp.upper(), lh, rh))
|
||||||
|
|
||||||
def mkunreachable():
|
def mkunreachable():
|
||||||
return "__PRETTY_ASSERT_UNREACHABLE()"
|
return "__PRETTY_ASSERT_UNREACHABLE()"
|
||||||
@@ -224,12 +221,12 @@ class ParseFailure(Exception):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "expected %r, found %s..." % (
|
return "expected %r, found %s..." % (
|
||||||
self.expected, repr(self.found)[:70])
|
self.expected, repr(self.found)[:70])
|
||||||
|
|
||||||
class Parser:
|
class Parser:
|
||||||
def __init__(self, in_f, lexemes=LEXEMES):
|
def __init__(self, in_f, lexemes=LEXEMES):
|
||||||
p = '|'.join('(?P<%s>%s)' % (n, '|'.join(l))
|
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)
|
p = re.compile(p, re.DOTALL)
|
||||||
data = in_f.read()
|
data = in_f.read()
|
||||||
tokens = []
|
tokens = []
|
||||||
@@ -496,54 +493,54 @@ if __name__ == "__main__":
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Preprocessor that makes asserts easier to debug.",
|
description="Preprocessor that makes asserts easier to debug.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'input',
|
'input',
|
||||||
help="Input C file.")
|
help="Input C file.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-o', '--output',
|
'-o', '--output',
|
||||||
required=True,
|
required=True,
|
||||||
help="Output C file.")
|
help="Output C file.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-p', '--prefix',
|
'-p', '--prefix',
|
||||||
action='append',
|
action='append',
|
||||||
help="Additional prefixes for symbols.")
|
help="Additional prefixes for symbols.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-P', '--prefix-insensitive',
|
'-P', '--prefix-insensitive',
|
||||||
action='append',
|
action='append',
|
||||||
help="Additional prefixes for lower/upper case symbol variants.")
|
help="Additional prefixes for lower/upper case symbol variants.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--assert',
|
'--assert',
|
||||||
dest='assert_',
|
dest='assert_',
|
||||||
action='append',
|
action='append',
|
||||||
help="Additional symbols for assert statements.")
|
help="Additional symbols for assert statements.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--unreachable',
|
'--unreachable',
|
||||||
action='append',
|
action='append',
|
||||||
help="Additional symbols for unreachable statements.")
|
help="Additional symbols for unreachable statements.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--memcmp',
|
'--memcmp',
|
||||||
action='append',
|
action='append',
|
||||||
help="Additional symbols for memcmp expressions.")
|
help="Additional symbols for memcmp expressions.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--strcmp',
|
'--strcmp',
|
||||||
action='append',
|
action='append',
|
||||||
help="Additional symbols for strcmp expressions.")
|
help="Additional symbols for strcmp expressions.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-n', '--no-defaults',
|
'-n', '--no-defaults',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Disable default symbols.")
|
help="Disable default symbols.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--no-arrows',
|
'--no-arrows',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Disable arrow (=>) expressions.")
|
help="Disable arrow (=>) expressions.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-l', '--limit',
|
'-l', '--limit',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
default=LIMIT,
|
default=LIMIT,
|
||||||
help="Maximum number of characters to display in strcmp and memcmp. "
|
help="Maximum number of characters to display in strcmp and "
|
||||||
"Defaults to %r." % LIMIT)
|
"memcmp. Defaults to %r." % LIMIT)
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
@@ -2,25 +2,27 @@
|
|||||||
|
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
|
|
||||||
|
|
||||||
def main(args):
|
def main(args):
|
||||||
with open(args.disk, 'rb') as f:
|
with open(args.disk, 'rb') as f:
|
||||||
f.seek(args.block * args.block_size)
|
f.seek(args.block * args.block_size)
|
||||||
block = (f.read(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?
|
# what did you expect?
|
||||||
print("%-8s %-s" % ('off', 'data'))
|
print("%-8s %-s" % ('off', 'data'))
|
||||||
return sp.run(['xxd', '-g1', '-'], input=block).returncode
|
return sp.run(['xxd', '-g1', '-'], input=block).returncode
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
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',
|
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),
|
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),
|
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()))
|
sys.exit(main(parser.parse_args()))
|
||||||
|
|||||||
+44
-41
@@ -5,6 +5,7 @@ import binascii
|
|||||||
import sys
|
import sys
|
||||||
import itertools as it
|
import itertools as it
|
||||||
|
|
||||||
|
|
||||||
TAG_TYPES = {
|
TAG_TYPES = {
|
||||||
'splice': (0x700, 0x400),
|
'splice': (0x700, 0x400),
|
||||||
'create': (0x7ff, 0x401),
|
'create': (0x7ff, 0x401),
|
||||||
@@ -104,8 +105,9 @@ class Tag:
|
|||||||
try:
|
try:
|
||||||
if ' ' in type:
|
if ' ' in type:
|
||||||
type1, type3 = type.split()
|
type1, type3 = type.split()
|
||||||
return (self.is_(type1) and
|
return (self.is_(type1)
|
||||||
(self.type & ~TAG_TYPES[type1][0]) == int(type3, 0))
|
and (self.type & ~TAG_TYPES[type1][0])
|
||||||
|
== int(type3, 0))
|
||||||
|
|
||||||
return self.type == int(type, 0)
|
return self.type == int(type, 0)
|
||||||
|
|
||||||
@@ -114,9 +116,9 @@ class Tag:
|
|||||||
|
|
||||||
def mkmask(self):
|
def mkmask(self):
|
||||||
return Tag(
|
return Tag(
|
||||||
0x700 if self.isunique else 0x7ff,
|
0x700 if self.isunique else 0x7ff,
|
||||||
0x3ff if self.isattr else 0,
|
0x3ff if self.isattr else 0,
|
||||||
0)
|
0)
|
||||||
|
|
||||||
def chid(self, nid):
|
def chid(self, nid):
|
||||||
ntag = Tag(self.type, nid, self.size)
|
ntag = Tag(self.type, nid, self.size)
|
||||||
@@ -142,7 +144,7 @@ class Tag:
|
|||||||
type = reverse_types[mask, self.type & mask]
|
type = reverse_types[mask, self.type & mask]
|
||||||
if prefix > 0:
|
if prefix > 0:
|
||||||
return '%s %#x%s' % (
|
return '%s %#x%s' % (
|
||||||
type, self.type & ((1 << prefix)-1), crc_status)
|
type, self.type & ((1 << prefix)-1), crc_status)
|
||||||
else:
|
else:
|
||||||
return '%s%s' % (type, crc_status)
|
return '%s%s' % (type, crc_status)
|
||||||
else:
|
else:
|
||||||
@@ -226,7 +228,7 @@ class MetadataPair:
|
|||||||
if fcrcdata:
|
if fcrcdata:
|
||||||
fcrcsize, fcrc = fcrcdata
|
fcrcsize, fcrc = fcrcdata
|
||||||
fcrc_ = 0xffffffff ^ binascii.crc32(
|
fcrc_ = 0xffffffff ^ binascii.crc32(
|
||||||
block[off:off+fcrcsize])
|
block[off:off+fcrcsize])
|
||||||
if fcrc_ == fcrc:
|
if fcrc_ == fcrc:
|
||||||
fcrctag.erased = True
|
fcrctag.erased = True
|
||||||
corrupt = True
|
corrupt = True
|
||||||
@@ -239,8 +241,8 @@ class MetadataPair:
|
|||||||
|
|
||||||
# find active ids
|
# find active ids
|
||||||
self.ids = list(it.takewhile(
|
self.ids = list(it.takewhile(
|
||||||
lambda id: Tag('name', id, 0) in self,
|
lambda id: Tag('name', id, 0) in self,
|
||||||
it.count()))
|
it.count()))
|
||||||
|
|
||||||
# find most recent tags
|
# find most recent tags
|
||||||
self.tags = []
|
self.tags = []
|
||||||
@@ -286,16 +288,16 @@ class MetadataPair:
|
|||||||
|
|
||||||
gdiff = 0
|
gdiff = 0
|
||||||
for tag in reversed(self.log):
|
for tag in reversed(self.log):
|
||||||
if (gmask.id != 0 and tag.is_('splice') and
|
if (gmask.id != 0 and tag.is_('splice')
|
||||||
tag.id <= gtag.id - gdiff):
|
and tag.id <= gtag.id - gdiff):
|
||||||
if tag.is_('create') and tag.id == gtag.id - gdiff:
|
if tag.is_('create') and tag.id == gtag.id - gdiff:
|
||||||
# creation point
|
# creation point
|
||||||
break
|
break
|
||||||
|
|
||||||
gdiff += tag.schunk
|
gdiff += tag.schunk
|
||||||
|
|
||||||
if ((int(gmask) & int(tag)) ==
|
if ((int(gmask) & int(tag))
|
||||||
(int(gmask) & int(gtag.chid(gtag.id - gdiff)))):
|
== (int(gmask) & int(gtag.chid(gtag.id - gdiff)))):
|
||||||
if tag.size == 0x3ff:
|
if tag.size == 0x3ff:
|
||||||
# deleted
|
# deleted
|
||||||
break
|
break
|
||||||
@@ -306,28 +308,28 @@ class MetadataPair:
|
|||||||
|
|
||||||
def _dump_tags(self, tags, f=sys.stdout, truncate=True):
|
def _dump_tags(self, tags, f=sys.stdout, truncate=True):
|
||||||
f.write("%-8s %-8s %-13s %4s %4s" % (
|
f.write("%-8s %-8s %-13s %4s %4s" % (
|
||||||
'off', 'tag', 'type', 'id', 'len'))
|
'off', 'tag', 'type', 'id', 'len'))
|
||||||
if truncate:
|
if truncate:
|
||||||
f.write(' data (truncated)')
|
f.write(' data (truncated)')
|
||||||
f.write('\n')
|
f.write('\n')
|
||||||
|
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
f.write("%08x: %08x %-14s %3s %4s" % (
|
f.write("%08x: %08x %-14s %3s %4s" % (
|
||||||
tag.off, tag,
|
tag.off, tag,
|
||||||
tag.typerepr(), tag.idrepr(), tag.sizerepr()))
|
tag.typerepr(), tag.idrepr(), tag.sizerepr()))
|
||||||
if truncate:
|
if truncate:
|
||||||
f.write(" %-23s %-8s\n" % (
|
f.write(" %-23s %-8s\n" % (
|
||||||
' '.join('%02x' % c for c in tag.data[:8]),
|
' '.join('%02x' % c for c in tag.data[:8]),
|
||||||
''.join(c if c >= ' ' and c <= '~' else '.'
|
''.join(c if c >= ' ' and c <= '~' else '.'
|
||||||
for c in map(chr, tag.data[:8]))))
|
for c in map(chr, tag.data[:8]))))
|
||||||
else:
|
else:
|
||||||
f.write("\n")
|
f.write("\n")
|
||||||
for i in range(0, len(tag.data), 16):
|
for i in range(0, len(tag.data), 16):
|
||||||
f.write(" %08x: %-47s %-16s\n" % (
|
f.write(" %08x: %-47s %-16s\n" % (
|
||||||
tag.off+i,
|
tag.off+i,
|
||||||
' '.join('%02x' % c for c in tag.data[i:i+16]),
|
' '.join('%02x' % c for c in tag.data[i:i+16]),
|
||||||
''.join(c if c >= ' ' and c <= '~' else '.'
|
''.join(c if c >= ' ' and c <= '~' else '.'
|
||||||
for c in map(chr, tag.data[i:i+16]))))
|
for c in map(chr, tag.data[i:i+16]))))
|
||||||
|
|
||||||
def dump_tags(self, f=sys.stdout, truncate=True):
|
def dump_tags(self, f=sys.stdout, truncate=True):
|
||||||
self._dump_tags(self.tags, f=f, truncate=truncate)
|
self._dump_tags(self.tags, f=f, truncate=truncate)
|
||||||
@@ -346,7 +348,7 @@ def main(args):
|
|||||||
continue
|
continue
|
||||||
f.seek(block * args.block_size)
|
f.seek(block * args.block_size)
|
||||||
blocks.append(f.read(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
|
# find most recent pair
|
||||||
mdir = MetadataPair(blocks)
|
mdir = MetadataPair(blocks)
|
||||||
@@ -359,15 +361,15 @@ def main(args):
|
|||||||
mdir.tail = None
|
mdir.tail = None
|
||||||
|
|
||||||
print("mdir {%s} rev %d%s%s%s" % (
|
print("mdir {%s} rev %d%s%s%s" % (
|
||||||
', '.join('%#x' % b
|
', '.join('%#x' % b
|
||||||
for b in [args.block1, args.block2]
|
for b in [args.block1, args.block2]
|
||||||
if b is not None),
|
if b is not None),
|
||||||
mdir.rev,
|
mdir.rev,
|
||||||
' (was %s)' % ', '.join('%d' % m.rev for m in mdir.pair[1:])
|
' (was %s)' % ', '.join('%d' % m.rev for m in mdir.pair[1:])
|
||||||
if len(mdir.pair) > 1 else '',
|
if len(mdir.pair) > 1 else '',
|
||||||
' (corrupted!)' if not mdir else '',
|
' (corrupted!)' if not mdir else '',
|
||||||
' -> {%#x, %#x}' % struct.unpack('<II', mdir.tail.data)
|
' -> {%#x, %#x}' % struct.unpack('<II', mdir.tail.data)
|
||||||
if mdir.tail else ''))
|
if mdir.tail else ''))
|
||||||
if args.all:
|
if args.all:
|
||||||
mdir.dump_all(truncate=not args.no_truncate)
|
mdir.dump_all(truncate=not args.no_truncate)
|
||||||
elif args.log:
|
elif args.log:
|
||||||
@@ -377,23 +379,24 @@ def main(args):
|
|||||||
|
|
||||||
return 0 if mdir else 1
|
return 0 if mdir else 1
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
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',
|
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),
|
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),
|
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),
|
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',
|
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',
|
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',
|
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()))
|
sys.exit(main(parser.parse_args()))
|
||||||
|
|||||||
+35
-29
@@ -7,6 +7,7 @@ import io
|
|||||||
import itertools as it
|
import itertools as it
|
||||||
from readmdir import Tag, MetadataPair
|
from readmdir import Tag, MetadataPair
|
||||||
|
|
||||||
|
|
||||||
def main(args):
|
def main(args):
|
||||||
superblock = None
|
superblock = None
|
||||||
gstate = b'\0\0\0\0\0\0\0\0\0\0\0\0'
|
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:
|
for block in tail:
|
||||||
f.seek(block * args.block_size)
|
f.seek(block * args.block_size)
|
||||||
data.append(f.read(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
|
blocks[id(data[-1])] = block
|
||||||
|
|
||||||
mdir = MetadataPair(data)
|
mdir = MetadataPair(data)
|
||||||
@@ -48,7 +49,7 @@ def main(args):
|
|||||||
# have superblock?
|
# have superblock?
|
||||||
try:
|
try:
|
||||||
nsuperblock = mdir[
|
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)]
|
superblock = nsuperblock, mdir[Tag('inlinestruct', 0, 0)]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
@@ -57,7 +58,7 @@ def main(args):
|
|||||||
try:
|
try:
|
||||||
ngstate = mdir[Tag('movestate', 0, 0)]
|
ngstate = mdir[Tag('movestate', 0, 0)]
|
||||||
gstate = bytes((a or 0) ^ (b or 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:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -103,10 +104,13 @@ def main(args):
|
|||||||
version = ('?', '?')
|
version = ('?', '?')
|
||||||
if superblock:
|
if superblock:
|
||||||
version = tuple(reversed(
|
version = tuple(reversed(
|
||||||
struct.unpack('<HH', superblock[1].data[0:4].ljust(4, b'\xff'))))
|
struct.unpack('<HH',
|
||||||
print("%-47s%s" % ("littlefs v%s.%s" % version,
|
superblock[1].data[0:4].ljust(4, b'\xff'))))
|
||||||
"data (truncated, if it fits)"
|
print("%-47s%s" % (
|
||||||
if not any([args.no_truncate, args.log, args.all]) else ""))
|
"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
|
||||||
print("gstate 0x%s" % ''.join('%02x' % c for c in 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))
|
print(" orphans >=%d" % max(tag.size, 1))
|
||||||
if tag.type:
|
if tag.type:
|
||||||
print(" move dir {%#x, %#x} id %d" % (
|
print(" move dir {%#x, %#x} id %d" % (
|
||||||
blocks[0], blocks[1], tag.id))
|
blocks[0], blocks[1], tag.id))
|
||||||
|
|
||||||
# print mdir info
|
# print mdir info
|
||||||
for i, dir in enumerate(dirs):
|
for i, dir in enumerate(dirs):
|
||||||
print("dir %s" % (json.dumps(dir[0].path)
|
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):
|
for j, mdir in enumerate(dir):
|
||||||
print("mdir {%#x, %#x} rev %d (was %d)%s%s" % (
|
print("mdir {%#x, %#x} rev %d (was %d)%s%s" % (
|
||||||
mdir.blocks[0], mdir.blocks[1], mdir.rev, mdir.pair[1].rev,
|
mdir.blocks[0], mdir.blocks[1], mdir.rev, mdir.pair[1].rev,
|
||||||
' (corrupted!)' if not mdir else '',
|
' (corrupted!)' if not mdir else '',
|
||||||
' -> {%#x, %#x}' % struct.unpack('<II', mdir.tail.data)
|
' -> {%#x, %#x}' % struct.unpack('<II', mdir.tail.data)
|
||||||
if mdir.tail else ''))
|
if mdir.tail else ''))
|
||||||
|
|
||||||
f = io.StringIO()
|
f = io.StringIO()
|
||||||
if args.log:
|
if args.log:
|
||||||
@@ -141,43 +145,45 @@ def main(args):
|
|||||||
lines = list(filter(None, f.getvalue().split('\n')))
|
lines = list(filter(None, f.getvalue().split('\n')))
|
||||||
for k, line in enumerate(lines):
|
for k, line in enumerate(lines):
|
||||||
print("%s %s" % (
|
print("%s %s" % (
|
||||||
' ' if j == len(dir)-1 else
|
' ' if j == len(dir)-1 else
|
||||||
'v' if k == len(lines)-1 else
|
'v' if k == len(lines)-1 else
|
||||||
'|',
|
'|',
|
||||||
line))
|
line))
|
||||||
|
|
||||||
errcode = 0
|
errcode = 0
|
||||||
for mdir in corrupted:
|
for mdir in corrupted:
|
||||||
errcode = errcode or 1
|
errcode = errcode or 1
|
||||||
print("*** corrupted mdir {%#x, %#x}! ***" % (
|
print("*** corrupted mdir {%#x, %#x}! ***" % (
|
||||||
mdir.blocks[0], mdir.blocks[1]))
|
mdir.blocks[0], mdir.blocks[1]))
|
||||||
|
|
||||||
if cycle:
|
if cycle:
|
||||||
errcode = errcode or 2
|
errcode = errcode or 2
|
||||||
print("*** cycle detected {%#x, %#x}! ***" % (
|
print("*** cycle detected {%#x, %#x}! ***" % (
|
||||||
cycle[0], cycle[1]))
|
cycle[0], cycle[1]))
|
||||||
|
|
||||||
return errcode
|
return errcode
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
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',
|
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),
|
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,
|
parser.add_argument('block1', nargs='?', default=0,
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Optional first block address for finding the superblock.")
|
help="Optional first block address for finding the superblock.")
|
||||||
parser.add_argument('block2', nargs='?', default=1,
|
parser.add_argument('block2', nargs='?', default=1,
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Optional second block address for finding the superblock.")
|
help="Optional second block address for finding the superblock.")
|
||||||
parser.add_argument('-l', '--log', action='store_true',
|
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',
|
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',
|
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()))
|
sys.exit(main(parser.parse_args()))
|
||||||
|
|||||||
+221
-216
@@ -18,7 +18,6 @@ import os
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# integer fields
|
# integer fields
|
||||||
class RInt(co.namedtuple('RInt', 'x')):
|
class RInt(co.namedtuple('RInt', 'x')):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
@@ -107,14 +106,14 @@ class StackResult(co.namedtuple('StackResult', [
|
|||||||
frame=0, limit=0,
|
frame=0, limit=0,
|
||||||
children=[]):
|
children=[]):
|
||||||
return super().__new__(cls, file, function,
|
return super().__new__(cls, file, function,
|
||||||
RInt(frame), RInt(limit),
|
RInt(frame), RInt(limit),
|
||||||
children)
|
children)
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
return StackResult(self.file, self.function,
|
return StackResult(self.file, self.function,
|
||||||
self.frame + other.frame,
|
self.frame + other.frame,
|
||||||
max(self.limit, other.limit),
|
max(self.limit, other.limit),
|
||||||
self.children + other.children)
|
self.children + other.children)
|
||||||
|
|
||||||
|
|
||||||
def openio(path, mode='r', buffering=-1):
|
def openio(path, mode='r', buffering=-1):
|
||||||
@@ -163,7 +162,7 @@ def collect(ci_paths, *,
|
|||||||
# collect into functions
|
# collect into functions
|
||||||
callgraph = co.defaultdict(lambda: (None, None, 0, set()))
|
callgraph = co.defaultdict(lambda: (None, None, 0, set()))
|
||||||
f_pattern = re.compile(
|
f_pattern = re.compile(
|
||||||
r'([^\\]*)\\n([^:]*)[^\\]*\\n([0-9]+) bytes \((.*)\)')
|
r'([^\\]*)\\n([^:]*)[^\\]*\\n([0-9]+) bytes \((.*)\)')
|
||||||
for path in ci_paths:
|
for path in ci_paths:
|
||||||
with open(path) as f:
|
with open(path) as f:
|
||||||
vcg = parse_vcg(f.read())
|
vcg = parse_vcg(f.read())
|
||||||
@@ -179,12 +178,12 @@ def collect(ci_paths, *,
|
|||||||
if (not args.get('quiet')
|
if (not args.get('quiet')
|
||||||
and 'static' not in type
|
and 'static' not in type
|
||||||
and 'bounded' not in type):
|
and 'bounded' not in type):
|
||||||
print("warning: "
|
print("warning: found non-static stack "
|
||||||
"found non-static stack for %s (%s, %s)" % (
|
"for %s (%s, %s)" % (
|
||||||
function, type, size))
|
function, type, size))
|
||||||
_, _, _, targets = callgraph[info['title']]
|
_, _, _, targets = callgraph[info['title']]
|
||||||
callgraph[info['title']] = (
|
callgraph[info['title']] = (
|
||||||
file, function, int(size), targets)
|
file, function, int(size), targets)
|
||||||
elif k == 'edge':
|
elif k == 'edge':
|
||||||
info = dict(info)
|
info = dict(info)
|
||||||
_, _, _, targets = callgraph[info['sourcename']]
|
_, _, _, targets = callgraph[info['sourcename']]
|
||||||
@@ -199,8 +198,7 @@ def collect(ci_paths, *,
|
|||||||
continue
|
continue
|
||||||
# ignore filtered sources
|
# ignore filtered sources
|
||||||
if sources is not None:
|
if sources is not None:
|
||||||
if not any(
|
if not any(os.path.abspath(s_file) == os.path.abspath(s)
|
||||||
os.path.abspath(s_file) == os.path.abspath(s)
|
|
||||||
for s in sources):
|
for s in sources):
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
@@ -268,9 +266,9 @@ def collect(ci_paths, *,
|
|||||||
# in the case of recursion
|
# in the case of recursion
|
||||||
for source, (_, _, _, targets) in callgraph.items():
|
for source, (_, _, _, targets) in callgraph.items():
|
||||||
results[source].children.extend(
|
results[source].children.extend(
|
||||||
results[target]
|
results[target]
|
||||||
for target in targets
|
for target in targets
|
||||||
if target in results)
|
if target in results)
|
||||||
|
|
||||||
return list(results.values())
|
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)):
|
for k in it.chain(by or [], (k for k, _ in defines)):
|
||||||
if k not in Result._by and k not in Result._fields:
|
if k not in Result._by and k not in Result._fields:
|
||||||
print("error: could not find field %r?" % k,
|
print("error: could not find field %r?" % k,
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# filter by matching defines
|
# filter by matching defines
|
||||||
@@ -338,74 +336,78 @@ def table(Result, results, diff_results=None, *,
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
r = max(results_,
|
r = max(results_,
|
||||||
key=lambda r: tuple(
|
key=lambda r: tuple(
|
||||||
tuple(
|
tuple((getattr(r, k),)
|
||||||
(getattr(r, k),)
|
if getattr(r, k, None) is not None
|
||||||
if getattr(r, k, None) is not None
|
else ()
|
||||||
else ()
|
for k in (
|
||||||
for k in ([k] if k else [
|
[k] if k else [
|
||||||
k for k in Result._sort if k in fields])
|
k for k in Result._sort
|
||||||
if k in fields)
|
if k in fields])
|
||||||
for k in it.chain(hot, [None])))
|
if k in fields)
|
||||||
|
for k in it.chain(hot, [None])))
|
||||||
|
|
||||||
# found a cycle?
|
# found a cycle?
|
||||||
if tuple(getattr(r, k) for k in Result._by) in seen:
|
if tuple(getattr(r, k) for k in Result._by) in seen:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
return [r._replace(children=[])] + rec_hot(
|
return [r._replace(children=[])] + rec_hot(
|
||||||
r.children,
|
r.children,
|
||||||
seen | {tuple(getattr(r, k) for k in Result._by)})
|
seen | {tuple(getattr(r, k) for k in Result._by)})
|
||||||
|
|
||||||
results = [r._replace(children=rec_hot(r.children)) for r in results]
|
results = [r._replace(children=rec_hot(r.children)) for r in results]
|
||||||
|
|
||||||
# organize by name
|
# organize by name
|
||||||
table = {
|
table = {
|
||||||
','.join(str(getattr(r, k) or '') for k in by): r
|
','.join(str(getattr(r, k) or '') for k in by): r
|
||||||
for r in results}
|
for r in results}
|
||||||
diff_table = {
|
diff_table = {
|
||||||
','.join(str(getattr(r, k) or '') for k in by): r
|
','.join(str(getattr(r, k) or '') for k in by): r
|
||||||
for r in diff_results or []}
|
for r in diff_results or []}
|
||||||
names = [name
|
names = [name
|
||||||
for name in table.keys() | diff_table.keys()
|
for name in table.keys() | diff_table.keys()
|
||||||
if diff_results is None
|
if diff_results is None
|
||||||
or all_
|
or all_
|
||||||
or any(
|
or any(
|
||||||
types[k].ratio(
|
types[k].ratio(
|
||||||
getattr(table.get(name), k, None),
|
getattr(table.get(name), k, None),
|
||||||
getattr(diff_table.get(name), k, None))
|
getattr(diff_table.get(name), k, None))
|
||||||
for k in fields)]
|
for k in fields)]
|
||||||
|
|
||||||
# sort again, now with diff info, note that python's sort is stable
|
# sort again, now with diff info, note that python's sort is stable
|
||||||
names.sort()
|
names.sort()
|
||||||
if diff_results is not None:
|
if diff_results is not None:
|
||||||
names.sort(key=lambda n: tuple(
|
names.sort(
|
||||||
types[k].ratio(
|
key=lambda n: tuple(
|
||||||
getattr(table.get(n), k, None),
|
types[k].ratio(
|
||||||
getattr(diff_table.get(n), k, None))
|
getattr(table.get(n), k, None),
|
||||||
for k in fields),
|
getattr(diff_table.get(n), k, None))
|
||||||
reverse=True)
|
for k in fields),
|
||||||
|
reverse=True)
|
||||||
if sort:
|
if sort:
|
||||||
for k, reverse in reversed(sort):
|
for k, reverse in reversed(sort):
|
||||||
names.sort(
|
names.sort(
|
||||||
key=lambda n: tuple(
|
key=lambda n: tuple(
|
||||||
(getattr(table[n], k),)
|
(getattr(table[n], k),)
|
||||||
if getattr(table.get(n), k, None) is not None else ()
|
if getattr(table.get(n), k, None) is not None
|
||||||
for k in ([k] if k else [
|
else ()
|
||||||
k for k in Result._sort if k in fields])),
|
for k in (
|
||||||
reverse=reverse ^ (not k or k in Result._fields))
|
[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
|
# build up our lines
|
||||||
lines = []
|
lines = []
|
||||||
|
|
||||||
# header
|
# header
|
||||||
header = [
|
header = ['%s%s' % (
|
||||||
'%s%s' % (
|
','.join(by),
|
||||||
','.join(by),
|
' (%d added, %d removed)' % (
|
||||||
' (%d added, %d removed)' % (
|
sum(1 for n in table if n not in diff_table),
|
||||||
sum(1 for n in table if n not in diff_table),
|
sum(1 for n in diff_table if n not in table))
|
||||||
sum(1 for n in diff_table if n not in table))
|
if diff_results is not None and not percent else '')
|
||||||
if diff_results is not None and not percent else '')
|
|
||||||
if not summary else '']
|
if not summary else '']
|
||||||
if diff_results is None:
|
if diff_results is None:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
@@ -428,43 +430,43 @@ def table(Result, results, diff_results=None, *,
|
|||||||
if diff_results is None:
|
if diff_results is None:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(getattr(r, k).table(),
|
(getattr(r, k).table(),
|
||||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
elif percent:
|
elif percent:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(getattr(r, k).table()
|
(getattr(r, k).table()
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none,
|
else types[k].none,
|
||||||
(lambda t: ['+∞%'] if t == +mt.inf
|
(lambda t: ['+∞%'] if t == +mt.inf
|
||||||
else ['-∞%'] if t == -mt.inf
|
else ['-∞%'] if t == -mt.inf
|
||||||
else ['%+.1f%%' % (100*t)])(
|
else ['%+.1f%%' % (100*t)])(
|
||||||
types[k].ratio(
|
types[k].ratio(
|
||||||
getattr(r, k, None),
|
getattr(r, k, None),
|
||||||
getattr(diff_r, k, None)))))
|
getattr(diff_r, k, None)))))
|
||||||
else:
|
else:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(getattr(diff_r, k).table()
|
entry.append(getattr(diff_r, k).table()
|
||||||
if getattr(diff_r, k, None) is not None
|
if getattr(diff_r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(getattr(r, k).table()
|
entry.append(getattr(r, k).table()
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(types[k].diff(
|
(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(
|
|
||||||
getattr(r, k, None),
|
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
|
return entry
|
||||||
|
|
||||||
# recursive entry helper
|
# recursive entry helper
|
||||||
@@ -473,8 +475,8 @@ def table(Result, results, diff_results=None, *,
|
|||||||
# build the children table at each layer
|
# build the children table at each layer
|
||||||
results_ = fold(Result, results_, by=by)
|
results_ = fold(Result, results_, by=by)
|
||||||
table_ = {
|
table_ = {
|
||||||
','.join(str(getattr(r, k) or '') for k in by): r
|
','.join(str(getattr(r, k) or '') for k in by): r
|
||||||
for r in results_}
|
for r in results_}
|
||||||
names_ = list(table_.keys())
|
names_ = list(table_.keys())
|
||||||
|
|
||||||
# sort the children layer
|
# sort the children layer
|
||||||
@@ -482,13 +484,16 @@ def table(Result, results, diff_results=None, *,
|
|||||||
if sort:
|
if sort:
|
||||||
for k, reverse in reversed(sort):
|
for k, reverse in reversed(sort):
|
||||||
names_.sort(
|
names_.sort(
|
||||||
key=lambda n: tuple(
|
key=lambda n: tuple(
|
||||||
(getattr(table_[n], k),)
|
(getattr(table_[n], k),)
|
||||||
if getattr(table_.get(n), k, None) is not None
|
if getattr(table_.get(n), k, None)
|
||||||
else ()
|
is not None
|
||||||
for k in ([k] if k else [
|
else ()
|
||||||
k for k in Result._sort if k in fields])),
|
for k in (
|
||||||
reverse=reverse ^ (not k or k in Result._fields))
|
[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_):
|
for i, name in enumerate(names_):
|
||||||
r = table_[name]
|
r = table_[name]
|
||||||
@@ -509,14 +514,13 @@ def table(Result, results, diff_results=None, *,
|
|||||||
|
|
||||||
# recurse?
|
# recurse?
|
||||||
if depth_ > 1:
|
if depth_ > 1:
|
||||||
recurse(
|
recurse(r.children,
|
||||||
r.children,
|
depth_-1,
|
||||||
depth_-1,
|
seen | {name},
|
||||||
seen | {name},
|
(prefixes[2+is_last] + "|-> ",
|
||||||
(prefixes[2+is_last] + "|-> ",
|
prefixes[2+is_last] + "'-> ",
|
||||||
prefixes[2+is_last] + "'-> ",
|
prefixes[2+is_last] + "| ",
|
||||||
prefixes[2+is_last] + "| ",
|
prefixes[2+is_last] + " "))
|
||||||
prefixes[2+is_last] + " "))
|
|
||||||
|
|
||||||
# entries
|
# entries
|
||||||
if not summary:
|
if not summary:
|
||||||
@@ -530,14 +534,13 @@ def table(Result, results, diff_results=None, *,
|
|||||||
|
|
||||||
# recursive entries
|
# recursive entries
|
||||||
if name in table and depth > 1:
|
if name in table and depth > 1:
|
||||||
recurse(
|
recurse(table[name].children,
|
||||||
table[name].children,
|
depth-1,
|
||||||
depth-1,
|
{name},
|
||||||
{name},
|
("|-> ",
|
||||||
("|-> ",
|
"'-> ",
|
||||||
"'-> ",
|
"| ",
|
||||||
"| ",
|
" "))
|
||||||
" "))
|
|
||||||
|
|
||||||
# total
|
# total
|
||||||
r = next(iter(fold(Result, results, by=[])), None)
|
r = next(iter(fold(Result, results, by=[])), None)
|
||||||
@@ -549,8 +552,8 @@ def table(Result, results, diff_results=None, *,
|
|||||||
|
|
||||||
# homogenize
|
# homogenize
|
||||||
lines = [
|
lines = [
|
||||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||||
for line in lines]
|
for line in lines]
|
||||||
|
|
||||||
# find the best widths, note that column 0 contains the names and is
|
# find the best widths, note that column 0 contains the names and is
|
||||||
# handled a bit differently
|
# handled a bit differently
|
||||||
@@ -564,11 +567,11 @@ def table(Result, results, diff_results=None, *,
|
|||||||
# print our table
|
# print our table
|
||||||
for line in lines:
|
for line in lines:
|
||||||
print('%-*s %s' % (
|
print('%-*s %s' % (
|
||||||
widths[0], line[0][0],
|
widths[0], line[0][0],
|
||||||
' '.join('%*s%-*s' % (
|
' '.join('%*s%-*s' % (
|
||||||
widths[i], x[0],
|
widths[i], x[0],
|
||||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||||
for i, x in enumerate(line[1:], 1))))
|
for i, x in enumerate(line[1:], 1))))
|
||||||
|
|
||||||
|
|
||||||
def main(ci_paths,
|
def main(ci_paths,
|
||||||
@@ -600,10 +603,10 @@ def main(ci_paths,
|
|||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
results.append(StackResult(
|
results.append(StackResult(
|
||||||
**{k: r[k] for k in StackResult._by
|
**{k: r[k] for k in StackResult._by
|
||||||
if k in r and r[k].strip()},
|
if k in r and r[k].strip()},
|
||||||
**{k: r[k] for k in StackResult._fields
|
**{k: r[k] for k in StackResult._fields
|
||||||
if k in r and r[k].strip()}))
|
if k in r and r[k].strip()}))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -615,25 +618,27 @@ def main(ci_paths,
|
|||||||
if sort:
|
if sort:
|
||||||
for k, reverse in reversed(sort):
|
for k, reverse in reversed(sort):
|
||||||
results.sort(
|
results.sort(
|
||||||
key=lambda r: tuple(
|
key=lambda r: tuple(
|
||||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||||
for k in ([k] if k else StackResult._sort)),
|
for k in ([k] if k else StackResult._sort)),
|
||||||
reverse=reverse ^ (not k or k in StackResult._fields))
|
reverse=reverse ^ (not k or k in StackResult._fields))
|
||||||
|
|
||||||
# write results to CSV
|
# write results to CSV
|
||||||
if args.get('output'):
|
if args.get('output'):
|
||||||
with openio(args['output'], 'w') as f:
|
with openio(args['output'], 'w') as f:
|
||||||
writer = csv.DictWriter(f,
|
writer = csv.DictWriter(f,
|
||||||
(by if by is not None else StackResult._by)
|
(by if by is not None else StackResult._by)
|
||||||
+ [k for k in (
|
+ [k for k in (
|
||||||
fields if fields is not None else StackResult._fields)])
|
fields if fields is not None
|
||||||
|
else StackResult._fields)])
|
||||||
writer.writeheader()
|
writer.writeheader()
|
||||||
for r in results:
|
for r in results:
|
||||||
writer.writerow(
|
writer.writerow(
|
||||||
{k: getattr(r, k) for k in (
|
{k: getattr(r, k) for k in (
|
||||||
by if by is not None else StackResult._by)}
|
by if by is not None else StackResult._by)}
|
||||||
| {k: getattr(r, k) for k in (
|
| {k: getattr(r, k) for k in (
|
||||||
fields if fields is not None else StackResult._fields)})
|
fields if fields is not None
|
||||||
|
else StackResult._fields)})
|
||||||
|
|
||||||
# find previous results?
|
# find previous results?
|
||||||
if args.get('diff'):
|
if args.get('diff'):
|
||||||
@@ -651,10 +656,10 @@ def main(ci_paths,
|
|||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
diff_results.append(StackResult(
|
diff_results.append(StackResult(
|
||||||
**{k: r[k] for k in StackResult._by
|
**{k: r[k] for k in StackResult._by
|
||||||
if k in r and r[k].strip()},
|
if k in r and r[k].strip()},
|
||||||
**{k: r[k] for k in StackResult._fields
|
**{k: r[k] for k in StackResult._fields
|
||||||
if k in r and r[k].strip()}))
|
if k in r and r[k].strip()}))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
raise
|
raise
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
@@ -666,11 +671,11 @@ def main(ci_paths,
|
|||||||
# print table
|
# print table
|
||||||
if not args.get('quiet'):
|
if not args.get('quiet'):
|
||||||
table(StackResult, results,
|
table(StackResult, results,
|
||||||
diff_results if args.get('diff') else None,
|
diff_results if args.get('diff') else None,
|
||||||
by=by if by is not None else ['function'],
|
by=by if by is not None else ['function'],
|
||||||
fields=fields,
|
fields=fields,
|
||||||
sort=sort,
|
sort=sort,
|
||||||
**args)
|
**args)
|
||||||
|
|
||||||
# error on recursion
|
# error on recursion
|
||||||
if args.get('error_on_recursion') and any(
|
if args.get('error_on_recursion') and any(
|
||||||
@@ -682,103 +687,103 @@ if __name__ == "__main__":
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Find stack usage at the function level.",
|
description="Find stack usage at the function level.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'ci_paths',
|
'ci_paths',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
help="Input *.ci files.")
|
help="Input *.ci files.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-v', '--verbose',
|
'-v', '--verbose',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Output commands that run behind the scenes.")
|
help="Output commands that run behind the scenes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-q', '--quiet',
|
'-q', '--quiet',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Don't show anything, useful with -o.")
|
help="Don't show anything, useful with -o.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-o', '--output',
|
'-o', '--output',
|
||||||
help="Specify CSV file to store results.")
|
help="Specify CSV file to store results.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-u', '--use',
|
'-u', '--use',
|
||||||
help="Don't parse anything, use this CSV file.")
|
help="Don't parse anything, use this CSV file.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-d', '--diff',
|
'-d', '--diff',
|
||||||
help="Specify CSV file to diff against.")
|
help="Specify CSV file to diff against.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-a', '--all',
|
'-a', '--all',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show all, not just the ones that changed.")
|
help="Show all, not just the ones that changed.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-p', '--percent',
|
'-p', '--percent',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Only show percentage change, not a full diff.")
|
help="Only show percentage change, not a full diff.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--by',
|
'-b', '--by',
|
||||||
action='append',
|
action='append',
|
||||||
choices=StackResult._by,
|
choices=StackResult._by,
|
||||||
help="Group by this field.")
|
help="Group by this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-f', '--field',
|
'-f', '--field',
|
||||||
dest='fields',
|
dest='fields',
|
||||||
action='append',
|
action='append',
|
||||||
choices=StackResult._fields,
|
choices=StackResult._fields,
|
||||||
help="Show this field.")
|
help="Show this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-D', '--define',
|
'-D', '--define',
|
||||||
dest='defines',
|
dest='defines',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs: (
|
lambda k, vs: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
{v.strip() for v in vs.split(',')})
|
{v.strip() for v in vs.split(',')})
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Only include results where this field is this value.")
|
help="Only include results where this field is this value.")
|
||||||
class AppendSort(argparse.Action):
|
class AppendSort(argparse.Action):
|
||||||
def __call__(self, parser, namespace, value, option):
|
def __call__(self, parser, namespace, value, option):
|
||||||
if namespace.sort is None:
|
if namespace.sort is None:
|
||||||
namespace.sort = []
|
namespace.sort = []
|
||||||
namespace.sort.append((value, True if option == '-S' else False))
|
namespace.sort.append((value, True if option == '-S' else False))
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-s', '--sort',
|
'-s', '--sort',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
action=AppendSort,
|
action=AppendSort,
|
||||||
help="Sort by this field.")
|
help="Sort by this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-S', '--reverse-sort',
|
'-S', '--reverse-sort',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
action=AppendSort,
|
action=AppendSort,
|
||||||
help="Sort by this field, but backwards.")
|
help="Sort by this field, but backwards.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-Y', '--summary',
|
'-Y', '--summary',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Only show the total.")
|
help="Only show the total.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-F', '--source',
|
'-F', '--source',
|
||||||
dest='sources',
|
dest='sources',
|
||||||
action='append',
|
action='append',
|
||||||
help="Only consider definitions in this file. Defaults to anything "
|
help="Only consider definitions in this file. Defaults to "
|
||||||
"in the current directory.")
|
"anything in the current directory.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--everything',
|
'--everything',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Include builtin and libc specific symbols.")
|
help="Include builtin and libc specific symbols.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-z', '--depth',
|
'-z', '--depth',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
const=0,
|
const=0,
|
||||||
help="Depth of function calls to show. 0 shows all calls unless we "
|
help="Depth of function calls to show. 0 shows all calls unless "
|
||||||
"find a cycle. Defaults to 0.")
|
"we find a cycle. Defaults to 0.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-t', '--hot',
|
'-t', '--hot',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
action='append',
|
action='append',
|
||||||
help="Show only the hot path for each function call.")
|
help="Show only the hot path for each function call.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-e', '--error-on-recursion',
|
'-e', '--error-on-recursion',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Error if any functions are recursive.")
|
help="Error if any functions are recursive.")
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+197
-192
@@ -23,7 +23,6 @@ import subprocess as sp
|
|||||||
OBJDUMP_PATH = ['objdump']
|
OBJDUMP_PATH = ['objdump']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# integer fields
|
# integer fields
|
||||||
class RInt(co.namedtuple('RInt', 'x')):
|
class RInt(co.namedtuple('RInt', 'x')):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
@@ -100,7 +99,9 @@ class RInt(co.namedtuple('RInt', 'x')):
|
|||||||
return self.__class__(self.x * other.x)
|
return self.__class__(self.x * other.x)
|
||||||
|
|
||||||
# struct size results
|
# struct size results
|
||||||
class StructResult(co.namedtuple('StructResult', ['file', 'struct', 'size'])):
|
class StructResult(co.namedtuple('StructResult', [
|
||||||
|
'file', 'struct',
|
||||||
|
'size'])):
|
||||||
_by = ['file', 'struct']
|
_by = ['file', 'struct']
|
||||||
_fields = ['size']
|
_fields = ['size']
|
||||||
_sort = ['size']
|
_sort = ['size']
|
||||||
@@ -109,11 +110,11 @@ class StructResult(co.namedtuple('StructResult', ['file', 'struct', 'size'])):
|
|||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
def __new__(cls, file='', struct='', size=0):
|
def __new__(cls, file='', struct='', size=0):
|
||||||
return super().__new__(cls, file, struct,
|
return super().__new__(cls, file, struct,
|
||||||
RInt(size))
|
RInt(size))
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
return StructResult(self.file, self.struct,
|
return StructResult(self.file, self.struct,
|
||||||
self.size + other.size)
|
self.size + other.size)
|
||||||
|
|
||||||
|
|
||||||
def openio(path, mode='r', buffering=-1):
|
def openio(path, mode='r', buffering=-1):
|
||||||
@@ -133,15 +134,15 @@ def collect(obj_paths, *,
|
|||||||
internal=False,
|
internal=False,
|
||||||
**args):
|
**args):
|
||||||
line_pattern = re.compile(
|
line_pattern = re.compile(
|
||||||
'^\s+(?P<no>[0-9]+)'
|
'^\s+(?P<no>[0-9]+)'
|
||||||
'(?:\s+(?P<dir>[0-9]+))?'
|
'(?:\s+(?P<dir>[0-9]+))?'
|
||||||
'\s+.*'
|
'\s+.*'
|
||||||
'\s+(?P<path>[^\s]+)$')
|
'\s+(?P<path>[^\s]+)$')
|
||||||
info_pattern = re.compile(
|
info_pattern = re.compile(
|
||||||
'^(?:.*(?P<tag>DW_TAG_[a-z_]+).*'
|
'^(?:.*(?P<tag>DW_TAG_[a-z_]+).*'
|
||||||
'|.*DW_AT_name.*:\s*(?P<name>[^:\s]+)\s*'
|
'|.*DW_AT_name.*:\s*(?P<name>[^:\s]+)\s*'
|
||||||
'|.*DW_AT_decl_file.*:\s*(?P<file>[0-9]+)\s*'
|
'|.*DW_AT_decl_file.*:\s*(?P<file>[0-9]+)\s*'
|
||||||
'|.*DW_AT_byte_size.*:\s*(?P<size>[0-9]+)\s*)$')
|
'|.*DW_AT_byte_size.*:\s*(?P<size>[0-9]+)\s*)$')
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
for path in obj_paths:
|
for path in obj_paths:
|
||||||
@@ -153,11 +154,11 @@ def collect(obj_paths, *,
|
|||||||
if args.get('verbose'):
|
if args.get('verbose'):
|
||||||
print(' '.join(shlex.quote(c) for c in cmd))
|
print(' '.join(shlex.quote(c) for c in cmd))
|
||||||
proc = sp.Popen(cmd,
|
proc = sp.Popen(cmd,
|
||||||
stdout=sp.PIPE,
|
stdout=sp.PIPE,
|
||||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||||
universal_newlines=True,
|
universal_newlines=True,
|
||||||
errors='replace',
|
errors='replace',
|
||||||
close_fds=False)
|
close_fds=False)
|
||||||
for line in proc.stdout:
|
for line in proc.stdout:
|
||||||
# note that files contain references to dirs, which we
|
# note that files contain references to dirs, which we
|
||||||
# dereference as soon as we see them as each file table follows a
|
# 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'))
|
dir = int(m.group('dir'))
|
||||||
if dir in dirs:
|
if dir in dirs:
|
||||||
files[int(m.group('no'))] = os.path.join(
|
files[int(m.group('no'))] = os.path.join(
|
||||||
dirs[dir],
|
dirs[dir],
|
||||||
m.group('path'))
|
m.group('path'))
|
||||||
else:
|
else:
|
||||||
files[int(m.group('no'))] = m.group('path')
|
files[int(m.group('no'))] = m.group('path')
|
||||||
proc.wait()
|
proc.wait()
|
||||||
@@ -199,11 +200,11 @@ def collect(obj_paths, *,
|
|||||||
if args.get('verbose'):
|
if args.get('verbose'):
|
||||||
print(' '.join(shlex.quote(c) for c in cmd))
|
print(' '.join(shlex.quote(c) for c in cmd))
|
||||||
proc = sp.Popen(cmd,
|
proc = sp.Popen(cmd,
|
||||||
stdout=sp.PIPE,
|
stdout=sp.PIPE,
|
||||||
stderr=None if args.get('verbose') else sp.DEVNULL,
|
stderr=None if args.get('verbose') else sp.DEVNULL,
|
||||||
universal_newlines=True,
|
universal_newlines=True,
|
||||||
errors='replace',
|
errors='replace',
|
||||||
close_fds=False)
|
close_fds=False)
|
||||||
for i, line in enumerate(proc.stdout):
|
for i, line in enumerate(proc.stdout):
|
||||||
# state machine here to find structs
|
# state machine here to find structs
|
||||||
m = info_pattern.match(line)
|
m = info_pattern.match(line)
|
||||||
@@ -211,7 +212,7 @@ def collect(obj_paths, *,
|
|||||||
if m.group('tag'):
|
if m.group('tag'):
|
||||||
append()
|
append()
|
||||||
is_struct = (m.group('tag') == 'DW_TAG_structure_type'
|
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_name = None
|
||||||
s_file = None
|
s_file = None
|
||||||
s_size = None
|
s_size = None
|
||||||
@@ -233,15 +234,14 @@ def collect(obj_paths, *,
|
|||||||
for r in results_:
|
for r in results_:
|
||||||
# ignore filtered sources
|
# ignore filtered sources
|
||||||
if sources is not None:
|
if sources is not None:
|
||||||
if not any(
|
if not any(os.path.abspath(r.file) == os.path.abspath(s)
|
||||||
os.path.abspath(r.file) == os.path.abspath(s)
|
|
||||||
for s in sources):
|
for s in sources):
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
# default to only cwd
|
# default to only cwd
|
||||||
if not everything and not os.path.commonpath([
|
if not everything and not os.path.commonpath([
|
||||||
os.getcwd(),
|
os.getcwd(),
|
||||||
os.path.abspath(r.file)]) == os.getcwd():
|
os.path.abspath(r.file)]) == os.getcwd():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# limit to .h files unless --internal
|
# limit to .h files unless --internal
|
||||||
@@ -250,8 +250,8 @@ def collect(obj_paths, *,
|
|||||||
|
|
||||||
# simplify path
|
# simplify path
|
||||||
if os.path.commonpath([
|
if os.path.commonpath([
|
||||||
os.getcwd(),
|
os.getcwd(),
|
||||||
os.path.abspath(r.file)]) == os.getcwd():
|
os.path.abspath(r.file)]) == os.getcwd():
|
||||||
file = os.path.relpath(r.file)
|
file = os.path.relpath(r.file)
|
||||||
else:
|
else:
|
||||||
file = os.path.abspath(r.file)
|
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)):
|
for k in it.chain(by or [], (k for k, _ in defines)):
|
||||||
if k not in Result._by and k not in Result._fields:
|
if k not in Result._by and k not in Result._fields:
|
||||||
print("error: could not find field %r?" % k,
|
print("error: could not find field %r?" % k,
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# filter by matching defines
|
# filter by matching defines
|
||||||
@@ -317,52 +317,55 @@ def table(Result, results, diff_results=None, *,
|
|||||||
|
|
||||||
# organize by name
|
# organize by name
|
||||||
table = {
|
table = {
|
||||||
','.join(str(getattr(r, k) or '') for k in by): r
|
','.join(str(getattr(r, k) or '') for k in by): r
|
||||||
for r in results}
|
for r in results}
|
||||||
diff_table = {
|
diff_table = {
|
||||||
','.join(str(getattr(r, k) or '') for k in by): r
|
','.join(str(getattr(r, k) or '') for k in by): r
|
||||||
for r in diff_results or []}
|
for r in diff_results or []}
|
||||||
names = [name
|
names = [name
|
||||||
for name in table.keys() | diff_table.keys()
|
for name in table.keys() | diff_table.keys()
|
||||||
if diff_results is None
|
if diff_results is None
|
||||||
or all_
|
or all_
|
||||||
or any(
|
or any(
|
||||||
types[k].ratio(
|
types[k].ratio(
|
||||||
getattr(table.get(name), k, None),
|
getattr(table.get(name), k, None),
|
||||||
getattr(diff_table.get(name), k, None))
|
getattr(diff_table.get(name), k, None))
|
||||||
for k in fields)]
|
for k in fields)]
|
||||||
|
|
||||||
# sort again, now with diff info, note that python's sort is stable
|
# sort again, now with diff info, note that python's sort is stable
|
||||||
names.sort()
|
names.sort()
|
||||||
if diff_results is not None:
|
if diff_results is not None:
|
||||||
names.sort(key=lambda n: tuple(
|
names.sort(
|
||||||
types[k].ratio(
|
key=lambda n: tuple(
|
||||||
getattr(table.get(n), k, None),
|
types[k].ratio(
|
||||||
getattr(diff_table.get(n), k, None))
|
getattr(table.get(n), k, None),
|
||||||
for k in fields),
|
getattr(diff_table.get(n), k, None))
|
||||||
reverse=True)
|
for k in fields),
|
||||||
|
reverse=True)
|
||||||
if sort:
|
if sort:
|
||||||
for k, reverse in reversed(sort):
|
for k, reverse in reversed(sort):
|
||||||
names.sort(
|
names.sort(
|
||||||
key=lambda n: tuple(
|
key=lambda n: tuple(
|
||||||
(getattr(table[n], k),)
|
(getattr(table[n], k),)
|
||||||
if getattr(table.get(n), k, None) is not None else ()
|
if getattr(table.get(n), k, None) is not None
|
||||||
for k in ([k] if k else [
|
else ()
|
||||||
k for k in Result._sort if k in fields])),
|
for k in (
|
||||||
reverse=reverse ^ (not k or k in Result._fields))
|
[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
|
# build up our lines
|
||||||
lines = []
|
lines = []
|
||||||
|
|
||||||
# header
|
# header
|
||||||
header = [
|
header = ['%s%s' % (
|
||||||
'%s%s' % (
|
','.join(by),
|
||||||
','.join(by),
|
' (%d added, %d removed)' % (
|
||||||
' (%d added, %d removed)' % (
|
sum(1 for n in table if n not in diff_table),
|
||||||
sum(1 for n in table if n not in diff_table),
|
sum(1 for n in diff_table if n not in table))
|
||||||
sum(1 for n in diff_table if n not in table))
|
if diff_results is not None and not percent else '')
|
||||||
if diff_results is not None and not percent else '')
|
|
||||||
if not summary else '']
|
if not summary else '']
|
||||||
if diff_results is None:
|
if diff_results is None:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
@@ -385,43 +388,43 @@ def table(Result, results, diff_results=None, *,
|
|||||||
if diff_results is None:
|
if diff_results is None:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(getattr(r, k).table(),
|
(getattr(r, k).table(),
|
||||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
elif percent:
|
elif percent:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(getattr(r, k).table()
|
(getattr(r, k).table()
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none,
|
else types[k].none,
|
||||||
(lambda t: ['+∞%'] if t == +mt.inf
|
(lambda t: ['+∞%'] if t == +mt.inf
|
||||||
else ['-∞%'] if t == -mt.inf
|
else ['-∞%'] if t == -mt.inf
|
||||||
else ['%+.1f%%' % (100*t)])(
|
else ['%+.1f%%' % (100*t)])(
|
||||||
types[k].ratio(
|
types[k].ratio(
|
||||||
getattr(r, k, None),
|
getattr(r, k, None),
|
||||||
getattr(diff_r, k, None)))))
|
getattr(diff_r, k, None)))))
|
||||||
else:
|
else:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(getattr(diff_r, k).table()
|
entry.append(getattr(diff_r, k).table()
|
||||||
if getattr(diff_r, k, None) is not None
|
if getattr(diff_r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(getattr(r, k).table()
|
entry.append(getattr(r, k).table()
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(types[k].diff(
|
(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(
|
|
||||||
getattr(r, k, None),
|
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
|
return entry
|
||||||
|
|
||||||
# entries
|
# entries
|
||||||
@@ -444,8 +447,8 @@ def table(Result, results, diff_results=None, *,
|
|||||||
|
|
||||||
# homogenize
|
# homogenize
|
||||||
lines = [
|
lines = [
|
||||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||||
for line in lines]
|
for line in lines]
|
||||||
|
|
||||||
# find the best widths, note that column 0 contains the names and is
|
# find the best widths, note that column 0 contains the names and is
|
||||||
# handled a bit differently
|
# handled a bit differently
|
||||||
@@ -459,11 +462,11 @@ def table(Result, results, diff_results=None, *,
|
|||||||
# print our table
|
# print our table
|
||||||
for line in lines:
|
for line in lines:
|
||||||
print('%-*s %s' % (
|
print('%-*s %s' % (
|
||||||
widths[0], line[0][0],
|
widths[0], line[0][0],
|
||||||
' '.join('%*s%-*s' % (
|
' '.join('%*s%-*s' % (
|
||||||
widths[i], x[0],
|
widths[i], x[0],
|
||||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||||
for i, x in enumerate(line[1:], 1))))
|
for i, x in enumerate(line[1:], 1))))
|
||||||
|
|
||||||
|
|
||||||
def main(obj_paths, *,
|
def main(obj_paths, *,
|
||||||
@@ -489,11 +492,11 @@ def main(obj_paths, *,
|
|||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
results.append(StructResult(
|
results.append(StructResult(
|
||||||
**{k: r[k] for k in StructResult._by
|
**{k: r[k] for k in StructResult._by
|
||||||
if k in r and r[k].strip()},
|
if k in r and r[k].strip()},
|
||||||
**{k: r[k]
|
**{k: r[k]
|
||||||
for k in StructResult._fields
|
for k in StructResult._fields
|
||||||
if k in r and r[k].strip()}))
|
if k in r and r[k].strip()}))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -505,25 +508,27 @@ def main(obj_paths, *,
|
|||||||
if sort:
|
if sort:
|
||||||
for k, reverse in reversed(sort):
|
for k, reverse in reversed(sort):
|
||||||
results.sort(
|
results.sort(
|
||||||
key=lambda r: tuple(
|
key=lambda r: tuple(
|
||||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||||
for k in ([k] if k else StructResult._sort)),
|
for k in ([k] if k else StructResult._sort)),
|
||||||
reverse=reverse ^ (not k or k in StructResult._fields))
|
reverse=reverse ^ (not k or k in StructResult._fields))
|
||||||
|
|
||||||
# write results to CSV
|
# write results to CSV
|
||||||
if args.get('output'):
|
if args.get('output'):
|
||||||
with openio(args['output'], 'w') as f:
|
with openio(args['output'], 'w') as f:
|
||||||
writer = csv.DictWriter(f,
|
writer = csv.DictWriter(f,
|
||||||
(by if by is not None else StructResult._by)
|
(by if by is not None else StructResult._by)
|
||||||
+ [k for k in (
|
+ [k for k in (
|
||||||
fields if fields is not None else StructResult._fields)])
|
fields if fields is not None
|
||||||
|
else StructResult._fields)])
|
||||||
writer.writeheader()
|
writer.writeheader()
|
||||||
for r in results:
|
for r in results:
|
||||||
writer.writerow(
|
writer.writerow(
|
||||||
{k: getattr(r, k) for k in (
|
{k: getattr(r, k) for k in (
|
||||||
by if by is not None else StructResult._by)}
|
by if by is not None else StructResult._by)}
|
||||||
| {k: getattr(r, k) for k in (
|
| {k: getattr(r, k) for k in (
|
||||||
fields if fields is not None else StructResult._fields)})
|
fields if fields is not None
|
||||||
|
else StructResult._fields)})
|
||||||
|
|
||||||
# find previous results?
|
# find previous results?
|
||||||
if args.get('diff'):
|
if args.get('diff'):
|
||||||
@@ -541,11 +546,11 @@ def main(obj_paths, *,
|
|||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
diff_results.append(StructResult(
|
diff_results.append(StructResult(
|
||||||
**{k: r[k] for k in StructResult._by
|
**{k: r[k] for k in StructResult._by
|
||||||
if k in r and r[k].strip()},
|
if k in r and r[k].strip()},
|
||||||
**{k: r[k]
|
**{k: r[k]
|
||||||
for k in StructResult._fields
|
for k in StructResult._fields
|
||||||
if k in r and r[k].strip()}))
|
if k in r and r[k].strip()}))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
@@ -557,108 +562,108 @@ def main(obj_paths, *,
|
|||||||
# print table
|
# print table
|
||||||
if not args.get('quiet'):
|
if not args.get('quiet'):
|
||||||
table(StructResult, results,
|
table(StructResult, results,
|
||||||
diff_results if args.get('diff') else None,
|
diff_results if args.get('diff') else None,
|
||||||
by=by if by is not None else ['struct'],
|
by=by if by is not None else ['struct'],
|
||||||
fields=fields,
|
fields=fields,
|
||||||
sort=sort,
|
sort=sort,
|
||||||
**args)
|
**args)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Find struct sizes.",
|
description="Find struct sizes.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'obj_paths',
|
'obj_paths',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
help="Input *.o files.")
|
help="Input *.o files.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-v', '--verbose',
|
'-v', '--verbose',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Output commands that run behind the scenes.")
|
help="Output commands that run behind the scenes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-q', '--quiet',
|
'-q', '--quiet',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Don't show anything, useful with -o.")
|
help="Don't show anything, useful with -o.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-o', '--output',
|
'-o', '--output',
|
||||||
help="Specify CSV file to store results.")
|
help="Specify CSV file to store results.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-u', '--use',
|
'-u', '--use',
|
||||||
help="Don't parse anything, use this CSV file.")
|
help="Don't parse anything, use this CSV file.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-d', '--diff',
|
'-d', '--diff',
|
||||||
help="Specify CSV file to diff against.")
|
help="Specify CSV file to diff against.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-a', '--all',
|
'-a', '--all',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show all, not just the ones that changed.")
|
help="Show all, not just the ones that changed.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-p', '--percent',
|
'-p', '--percent',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Only show percentage change, not a full diff.")
|
help="Only show percentage change, not a full diff.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--by',
|
'-b', '--by',
|
||||||
action='append',
|
action='append',
|
||||||
choices=StructResult._by,
|
choices=StructResult._by,
|
||||||
help="Group by this field.")
|
help="Group by this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-f', '--field',
|
'-f', '--field',
|
||||||
dest='fields',
|
dest='fields',
|
||||||
action='append',
|
action='append',
|
||||||
choices=StructResult._fields,
|
choices=StructResult._fields,
|
||||||
help="Show this field.")
|
help="Show this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-D', '--define',
|
'-D', '--define',
|
||||||
dest='defines',
|
dest='defines',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs: (
|
lambda k, vs: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
{v.strip() for v in vs.split(',')})
|
{v.strip() for v in vs.split(',')})
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Only include results where this field is this value.")
|
help="Only include results where this field is this value.")
|
||||||
class AppendSort(argparse.Action):
|
class AppendSort(argparse.Action):
|
||||||
def __call__(self, parser, namespace, value, option):
|
def __call__(self, parser, namespace, value, option):
|
||||||
if namespace.sort is None:
|
if namespace.sort is None:
|
||||||
namespace.sort = []
|
namespace.sort = []
|
||||||
namespace.sort.append((value, True if option == '-S' else False))
|
namespace.sort.append((value, True if option == '-S' else False))
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-s', '--sort',
|
'-s', '--sort',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
action=AppendSort,
|
action=AppendSort,
|
||||||
help="Sort by this field.")
|
help="Sort by this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-S', '--reverse-sort',
|
'-S', '--reverse-sort',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
action=AppendSort,
|
action=AppendSort,
|
||||||
help="Sort by this field, but backwards.")
|
help="Sort by this field, but backwards.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-Y', '--summary',
|
'-Y', '--summary',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Only show the total.")
|
help="Only show the total.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-F', '--source',
|
'-F', '--source',
|
||||||
dest='sources',
|
dest='sources',
|
||||||
action='append',
|
action='append',
|
||||||
help="Only consider definitions in this file. Defaults to anything "
|
help="Only consider definitions in this file. Defaults to "
|
||||||
"in the current directory.")
|
"anything in the current directory.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--everything',
|
'--everything',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Include builtin and libc specific symbols.")
|
help="Include builtin and libc specific symbols.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--internal',
|
'--internal',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Also show structs in .c files.")
|
help="Also show structs in .c files.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--objdump-path',
|
'--objdump-path',
|
||||||
type=lambda x: x.split(),
|
type=lambda x: x.split(),
|
||||||
default=OBJDUMP_PATH,
|
default=OBJDUMP_PATH,
|
||||||
help="Path to the objdump executable, may include flags. "
|
help="Path to the objdump executable, may include flags. "
|
||||||
"Defaults to %r." % OBJDUMP_PATH)
|
"Defaults to %r." % OBJDUMP_PATH)
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+221
-217
@@ -32,16 +32,16 @@ OPS = {
|
|||||||
'max': max,
|
'max': max,
|
||||||
'avg': lambda xs: RFloat(sum(float(x) for x in xs) / len(xs)),
|
'avg': lambda xs: RFloat(sum(float(x) for x in xs) / len(xs)),
|
||||||
'stddev': lambda xs: (
|
'stddev': lambda xs: (
|
||||||
lambda avg: RFloat(
|
lambda avg: RFloat(
|
||||||
mt.sqrt(sum((float(x) - avg)**2 for x in xs) / len(xs)))
|
mt.sqrt(sum((float(x) - avg)**2 for x in xs) / len(xs)))
|
||||||
)(sum(float(x) 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))),
|
'gmean': lambda xs: RFloat(mt.prod(float(x) for x in xs)**(1/len(xs))),
|
||||||
'gstddev': lambda xs: (
|
'gstddev': lambda xs: (
|
||||||
lambda gmean: RFloat(
|
lambda gmean: RFloat(
|
||||||
mt.exp(mt.sqrt(
|
mt.exp(mt.sqrt(
|
||||||
sum(mt.log(float(x)/gmean)**2 for x in xs)
|
sum(mt.log(float(x)/gmean)**2 for x in xs)
|
||||||
/ len(xs)))
|
/ len(xs)))
|
||||||
if gmean else mt.inf)
|
if gmean else mt.inf)
|
||||||
)(mt.prod(float(x) for x in xs)**(1/len(xs))),
|
)(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):
|
def notes(self):
|
||||||
t = self.a.x/self.b.x if self.b.x else 1.0
|
t = self.a.x/self.b.x if self.b.x else 1.0
|
||||||
return ['∞%' if t == +mt.inf
|
return ['∞%' if t == +mt.inf
|
||||||
else '-∞%' if t == -mt.inf
|
else '-∞%' if t == -mt.inf
|
||||||
else '%.1f%%' % (100*t)]
|
else '%.1f%%' % (100*t)]
|
||||||
|
|
||||||
def diff(self, other):
|
def diff(self, other):
|
||||||
new_a, new_b = self if self else (RInt(0), RInt(0))
|
new_a, new_b = self if self else (RInt(0), RInt(0))
|
||||||
old_a, old_b = other if other else (RInt(0), RInt(0))
|
old_a, old_b = other if other else (RInt(0), RInt(0))
|
||||||
return '%11s' % ('%s/%s' % (
|
return '%11s' % ('%s/%s' % (
|
||||||
new_a.diff(old_a).strip(),
|
new_a.diff(old_a).strip(),
|
||||||
new_b.diff(old_b).strip()))
|
new_b.diff(old_b).strip()))
|
||||||
|
|
||||||
def ratio(self, other):
|
def ratio(self, other):
|
||||||
new_a, new_b = self if self else (RInt(0), RInt(0))
|
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:
|
with openio(path) as f:
|
||||||
reader = csv.DictReader(f, restval='')
|
reader = csv.DictReader(f, restval='')
|
||||||
fields.extend(
|
fields.extend(
|
||||||
k for k in reader.fieldnames
|
k for k in reader.fieldnames
|
||||||
if k not in fields)
|
if k not in fields)
|
||||||
for r in reader:
|
for r in reader:
|
||||||
# apply any renames
|
# apply any renames
|
||||||
if renames:
|
if renames:
|
||||||
@@ -302,19 +302,17 @@ def infer(fields_, results,
|
|||||||
defines=[]):
|
defines=[]):
|
||||||
# if by not specified, guess it's anything not in fields/renames/defines
|
# if by not specified, guess it's anything not in fields/renames/defines
|
||||||
if by is None:
|
if by is None:
|
||||||
by = [
|
by = [k for k in fields_
|
||||||
k for k in fields_
|
if k not in (fields or [])
|
||||||
if k not in (fields or [])
|
and not any(k == old_k for _, old_k in renames)
|
||||||
and not any(k == old_k for _, old_k in renames)
|
and not any(k == k_ for k_, _ in defines)]
|
||||||
and not any(k == k_ for k_, _ in defines)]
|
|
||||||
|
|
||||||
# if fields not specified, guess it's anything not in by/renames/defines
|
# if fields not specified, guess it's anything not in by/renames/defines
|
||||||
if fields is None:
|
if fields is None:
|
||||||
fields = [
|
fields = [k for k in fields_
|
||||||
k for k in fields_
|
if k not in (by or [])
|
||||||
if k not in (by or [])
|
and not any(k == old_k for _, old_k in renames)
|
||||||
and not any(k == old_k for _, old_k in renames)
|
and not any(k == k_ for k_, _ in defines)]
|
||||||
and not any(k == k_ for k_, _ in defines)]
|
|
||||||
|
|
||||||
# deduplicate by/fields
|
# deduplicate by/fields
|
||||||
by = list(co.OrderedDict.fromkeys(by).keys())
|
by = list(co.OrderedDict.fromkeys(by).keys())
|
||||||
@@ -338,7 +336,7 @@ def infer(fields_, results,
|
|||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
print("error: no type matches field %r?" % k,
|
print("error: no type matches field %r?" % k,
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
types = types_
|
types = types_
|
||||||
|
|
||||||
@@ -351,11 +349,11 @@ def infer(fields_, results,
|
|||||||
# create result class
|
# create result class
|
||||||
def __new__(cls, **r):
|
def __new__(cls, **r):
|
||||||
return cls.__mro__[1].__new__(cls,
|
return cls.__mro__[1].__new__(cls,
|
||||||
**{k: r.get(k, '') for k in by},
|
**{k: r.get(k, '') for k in by},
|
||||||
**{k: r[k] if k in r and isinstance(r[k], tuple)
|
**{k: r[k] if k in r and isinstance(r[k], tuple)
|
||||||
else ([types[k](r[k])], 1) if k in r
|
else ([types[k](r[k])], 1) if k in r
|
||||||
else ([], 0)
|
else ([], 0)
|
||||||
for k in fields})
|
for k in fields})
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
# reuse lists if possible
|
# 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 (a[0][:a[1]] + b[0][:b[1]], a[1] + b[1])
|
||||||
|
|
||||||
return self.__class__(
|
return self.__class__(
|
||||||
**{k: getattr(self, k) for k in by},
|
**{k: getattr(self, k) for k in by},
|
||||||
**{k: extend(
|
**{k: extend(
|
||||||
object.__getattribute__(self, k),
|
object.__getattribute__(self, k),
|
||||||
object.__getattribute__(other, k))
|
object.__getattribute__(other, k))
|
||||||
for k in fields})
|
for k in fields})
|
||||||
|
|
||||||
def __getattribute__(self, k):
|
def __getattribute__(self, k):
|
||||||
if k in fields:
|
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)):
|
for k in it.chain(by or [], (k for k, _ in defines)):
|
||||||
if k not in Result._by and k not in Result._fields:
|
if k not in Result._by and k not in Result._fields:
|
||||||
print("error: could not find field %r?" % k,
|
print("error: could not find field %r?" % k,
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# filter by matching defines
|
# filter by matching defines
|
||||||
@@ -450,52 +448,55 @@ def table(Result, results, diff_results=None, *,
|
|||||||
|
|
||||||
# organize by name
|
# organize by name
|
||||||
table = {
|
table = {
|
||||||
','.join(str(getattr(r, k) or '') for k in by): r
|
','.join(str(getattr(r, k) or '') for k in by): r
|
||||||
for r in results}
|
for r in results}
|
||||||
diff_table = {
|
diff_table = {
|
||||||
','.join(str(getattr(r, k) or '') for k in by): r
|
','.join(str(getattr(r, k) or '') for k in by): r
|
||||||
for r in diff_results or []}
|
for r in diff_results or []}
|
||||||
names = [name
|
names = [name
|
||||||
for name in table.keys() | diff_table.keys()
|
for name in table.keys() | diff_table.keys()
|
||||||
if diff_results is None
|
if diff_results is None
|
||||||
or all_
|
or all_
|
||||||
or any(
|
or any(
|
||||||
types[k].ratio(
|
types[k].ratio(
|
||||||
getattr(table.get(name), k, None),
|
getattr(table.get(name), k, None),
|
||||||
getattr(diff_table.get(name), k, None))
|
getattr(diff_table.get(name), k, None))
|
||||||
for k in fields)]
|
for k in fields)]
|
||||||
|
|
||||||
# sort again, now with diff info, note that python's sort is stable
|
# sort again, now with diff info, note that python's sort is stable
|
||||||
names.sort()
|
names.sort()
|
||||||
if diff_results is not None:
|
if diff_results is not None:
|
||||||
names.sort(key=lambda n: tuple(
|
names.sort(
|
||||||
types[k].ratio(
|
key=lambda n: tuple(
|
||||||
getattr(table.get(n), k, None),
|
types[k].ratio(
|
||||||
getattr(diff_table.get(n), k, None))
|
getattr(table.get(n), k, None),
|
||||||
for k in fields),
|
getattr(diff_table.get(n), k, None))
|
||||||
reverse=True)
|
for k in fields),
|
||||||
|
reverse=True)
|
||||||
if sort:
|
if sort:
|
||||||
for k, reverse in reversed(sort):
|
for k, reverse in reversed(sort):
|
||||||
names.sort(
|
names.sort(
|
||||||
key=lambda n: tuple(
|
key=lambda n: tuple(
|
||||||
(getattr(table[n], k),)
|
(getattr(table[n], k),)
|
||||||
if getattr(table.get(n), k, None) is not None else ()
|
if getattr(table.get(n), k, None) is not None
|
||||||
for k in ([k] if k else [
|
else ()
|
||||||
k for k in Result._sort if k in fields])),
|
for k in (
|
||||||
reverse=reverse ^ (not k or k in Result._fields))
|
[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
|
# build up our lines
|
||||||
lines = []
|
lines = []
|
||||||
|
|
||||||
# header
|
# header
|
||||||
header = [
|
header = ['%s%s' % (
|
||||||
'%s%s' % (
|
','.join(by),
|
||||||
','.join(by),
|
' (%d added, %d removed)' % (
|
||||||
' (%d added, %d removed)' % (
|
sum(1 for n in table if n not in diff_table),
|
||||||
sum(1 for n in table if n not in diff_table),
|
sum(1 for n in diff_table if n not in table))
|
||||||
sum(1 for n in diff_table if n not in table))
|
if diff_results is not None and not percent else '')
|
||||||
if diff_results is not None and not percent else '')
|
|
||||||
if not summary else '']
|
if not summary else '']
|
||||||
if diff_results is None:
|
if diff_results is None:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
@@ -518,43 +519,43 @@ def table(Result, results, diff_results=None, *,
|
|||||||
if diff_results is None:
|
if diff_results is None:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(getattr(r, k).table(),
|
(getattr(r, k).table(),
|
||||||
getattr(getattr(r, k), 'notes', lambda: [])())
|
getattr(getattr(r, k), 'notes', lambda: [])())
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
elif percent:
|
elif percent:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(getattr(r, k).table()
|
(getattr(r, k).table()
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none,
|
else types[k].none,
|
||||||
(lambda t: ['+∞%'] if t == +mt.inf
|
(lambda t: ['+∞%'] if t == +mt.inf
|
||||||
else ['-∞%'] if t == -mt.inf
|
else ['-∞%'] if t == -mt.inf
|
||||||
else ['%+.1f%%' % (100*t)])(
|
else ['%+.1f%%' % (100*t)])(
|
||||||
types[k].ratio(
|
types[k].ratio(
|
||||||
getattr(r, k, None),
|
getattr(r, k, None),
|
||||||
getattr(diff_r, k, None)))))
|
getattr(diff_r, k, None)))))
|
||||||
else:
|
else:
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(getattr(diff_r, k).table()
|
entry.append(getattr(diff_r, k).table()
|
||||||
if getattr(diff_r, k, None) is not None
|
if getattr(diff_r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(getattr(r, k).table()
|
entry.append(getattr(r, k).table()
|
||||||
if getattr(r, k, None) is not None
|
if getattr(r, k, None) is not None
|
||||||
else types[k].none)
|
else types[k].none)
|
||||||
for k in fields:
|
for k in fields:
|
||||||
entry.append(
|
entry.append(
|
||||||
(types[k].diff(
|
(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(
|
|
||||||
getattr(r, k, None),
|
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
|
return entry
|
||||||
|
|
||||||
# entries
|
# entries
|
||||||
@@ -577,8 +578,8 @@ def table(Result, results, diff_results=None, *,
|
|||||||
|
|
||||||
# homogenize
|
# homogenize
|
||||||
lines = [
|
lines = [
|
||||||
[x if isinstance(x, tuple) else (x, []) for x in line]
|
[x if isinstance(x, tuple) else (x, []) for x in line]
|
||||||
for line in lines]
|
for line in lines]
|
||||||
|
|
||||||
# find the best widths, note that column 0 contains the names and is
|
# find the best widths, note that column 0 contains the names and is
|
||||||
# handled a bit differently
|
# handled a bit differently
|
||||||
@@ -592,11 +593,11 @@ def table(Result, results, diff_results=None, *,
|
|||||||
# print our table
|
# print our table
|
||||||
for line in lines:
|
for line in lines:
|
||||||
print('%-*s %s' % (
|
print('%-*s %s' % (
|
||||||
widths[0], line[0][0],
|
widths[0], line[0][0],
|
||||||
' '.join('%*s%-*s' % (
|
' '.join('%*s%-*s' % (
|
||||||
widths[i], x[0],
|
widths[i], x[0],
|
||||||
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
|
||||||
for i, x in enumerate(line[1:], 1))))
|
for i, x in enumerate(line[1:], 1))))
|
||||||
|
|
||||||
|
|
||||||
def main(csv_paths, *,
|
def main(csv_paths, *,
|
||||||
@@ -607,8 +608,8 @@ def main(csv_paths, *,
|
|||||||
**args):
|
**args):
|
||||||
# separate out renames
|
# separate out renames
|
||||||
renames = list(it.chain.from_iterable(
|
renames = list(it.chain.from_iterable(
|
||||||
((k, v) for v in vs)
|
((k, v) for v in vs)
|
||||||
for k, vs in it.chain(by or [], fields or [])))
|
for k, vs in it.chain(by or [], fields or [])))
|
||||||
if by is not None:
|
if by is not None:
|
||||||
by = [k for k, _ in by]
|
by = [k for k, _ in by]
|
||||||
if fields is not None:
|
if fields is not None:
|
||||||
@@ -620,7 +621,7 @@ def main(csv_paths, *,
|
|||||||
for k in args.get(t, []):
|
for k in args.get(t, []):
|
||||||
if k in types:
|
if k in types:
|
||||||
print("error: conflicting type for field %r?" % k,
|
print("error: conflicting type for field %r?" % k,
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
types[k] = TYPES[t]
|
types[k] = TYPES[t]
|
||||||
# rename types?
|
# rename types?
|
||||||
@@ -637,7 +638,7 @@ def main(csv_paths, *,
|
|||||||
for k in args.get(o, []):
|
for k in args.get(o, []):
|
||||||
if k in ops:
|
if k in ops:
|
||||||
print("error: conflicting op for field %r?" % k,
|
print("error: conflicting op for field %r?" % k,
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
ops[k] = OPS[o]
|
ops[k] = OPS[o]
|
||||||
# rename ops?
|
# rename ops?
|
||||||
@@ -650,7 +651,7 @@ def main(csv_paths, *,
|
|||||||
|
|
||||||
if by is None and fields is None:
|
if by is None and fields is None:
|
||||||
print("error: needs --by or --fields to figure out fields",
|
print("error: needs --by or --fields to figure out fields",
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# use is just an alias
|
# use is just an alias
|
||||||
@@ -662,12 +663,12 @@ def main(csv_paths, *,
|
|||||||
|
|
||||||
# homogenize
|
# homogenize
|
||||||
Result = infer(fields_, results,
|
Result = infer(fields_, results,
|
||||||
by=by,
|
by=by,
|
||||||
fields=fields,
|
fields=fields,
|
||||||
types=types,
|
types=types,
|
||||||
ops=ops,
|
ops=ops,
|
||||||
renames=renames,
|
renames=renames,
|
||||||
defines=defines)
|
defines=defines)
|
||||||
results_ = []
|
results_ = []
|
||||||
for r in results:
|
for r in results:
|
||||||
if not any(k in r and r[k].strip()
|
if not any(k in r and r[k].strip()
|
||||||
@@ -675,8 +676,8 @@ def main(csv_paths, *,
|
|||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
results_.append(Result(**{
|
results_.append(Result(**{
|
||||||
k: r[k] for k in Result._by + Result._fields
|
k: r[k] for k in Result._by + Result._fields
|
||||||
if k in r and r[k].strip()}))
|
if k in r and r[k].strip()}))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
results = results_
|
results = results_
|
||||||
@@ -689,10 +690,10 @@ def main(csv_paths, *,
|
|||||||
if sort:
|
if sort:
|
||||||
for k, reverse in reversed(sort):
|
for k, reverse in reversed(sort):
|
||||||
results.sort(
|
results.sort(
|
||||||
key=lambda r: tuple(
|
key=lambda r: tuple(
|
||||||
(getattr(r, k),) if getattr(r, k) is not None else ()
|
(getattr(r, k),) if getattr(r, k) is not None else ()
|
||||||
for k in ([k] if k else Result._sort)),
|
for k in ([k] if k else Result._sort)),
|
||||||
reverse=reverse ^ (not k or k in Result._fields))
|
reverse=reverse ^ (not k or k in Result._fields))
|
||||||
|
|
||||||
# write results to CSV
|
# write results to CSV
|
||||||
if args.get('output'):
|
if args.get('output'):
|
||||||
@@ -702,7 +703,8 @@ def main(csv_paths, *,
|
|||||||
for r in results:
|
for r in results:
|
||||||
# note we need to go through getattr to resolve lazy fields
|
# note we need to go through getattr to resolve lazy fields
|
||||||
writer.writerow({
|
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?
|
# find previous results?
|
||||||
if args.get('diff'):
|
if args.get('diff'):
|
||||||
@@ -714,8 +716,8 @@ def main(csv_paths, *,
|
|||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
diff_results_.append(Result(**{
|
diff_results_.append(Result(**{
|
||||||
k: r[k] for k in Result._by + Result._fields
|
k: r[k] for k in Result._by + Result._fields
|
||||||
if k in r and r[k].strip()}))
|
if k in r and r[k].strip()}))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
diff_results = diff_results_
|
diff_results = diff_results_
|
||||||
@@ -726,139 +728,141 @@ def main(csv_paths, *,
|
|||||||
# print table
|
# print table
|
||||||
if not args.get('quiet'):
|
if not args.get('quiet'):
|
||||||
table(Result, results,
|
table(Result, results,
|
||||||
diff_results if args.get('diff') else None,
|
diff_results if args.get('diff') else None,
|
||||||
by=by,
|
by=by,
|
||||||
fields=fields,
|
fields=fields,
|
||||||
sort=sort,
|
sort=sort,
|
||||||
**args)
|
**args)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Summarize measurements in CSV files.",
|
description="Summarize measurements in CSV files.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'csv_paths',
|
'csv_paths',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
help="Input *.csv files.")
|
help="Input *.csv files.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-q', '--quiet',
|
'-q', '--quiet',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Don't show anything, useful with -o.")
|
help="Don't show anything, useful with -o.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-o', '--output',
|
'-o', '--output',
|
||||||
help="Specify CSV file to store results.")
|
help="Specify CSV file to store results.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-u', '--use',
|
'-u', '--use',
|
||||||
help="Don't parse anything, use this CSV file.")
|
help="Don't parse anything, use this CSV file.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-d', '--diff',
|
'-d', '--diff',
|
||||||
help="Specify CSV file to diff against.")
|
help="Specify CSV file to diff against.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-a', '--all',
|
'-a', '--all',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show all, not just the ones that changed.")
|
help="Show all, not just the ones that changed.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-p', '--percent',
|
'-p', '--percent',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Only show percentage change, not a full diff.")
|
help="Only show percentage change, not a full diff.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--by',
|
'-b', '--by',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs=None: (
|
lambda k, vs=None: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
tuple(v.strip() for v in vs.split(','))
|
tuple(v.strip() for v in vs.split(','))
|
||||||
if vs is not None else ())
|
if vs is not None else ())
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Group by this field. Can rename fields with new_name=old_name.")
|
help="Group by this field. Can rename fields with "
|
||||||
|
"new_name=old_name.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-f', '--field',
|
'-f', '--field',
|
||||||
dest='fields',
|
dest='fields',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs=None: (
|
lambda k, vs=None: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
tuple(v.strip() for v in vs.split(','))
|
tuple(v.strip() for v in vs.split(','))
|
||||||
if vs is not None else ())
|
if vs is not None else ())
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Show this field. Can rename fields with new_name=old_name.")
|
help="Show this field. Can rename fields with "
|
||||||
|
"new_name=old_name.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-D', '--define',
|
'-D', '--define',
|
||||||
dest='defines',
|
dest='defines',
|
||||||
action='append',
|
action='append',
|
||||||
type=lambda x: (
|
type=lambda x: (
|
||||||
lambda k, vs: (
|
lambda k, vs: (
|
||||||
k.strip(),
|
k.strip(),
|
||||||
{v.strip() for v in vs.split(',')})
|
{v.strip() for v in vs.split(',')})
|
||||||
)(*x.split('=', 1)),
|
)(*x.split('=', 1)),
|
||||||
help="Only include results where this field is this value. May include "
|
help="Only include results where this field is this value. May "
|
||||||
"comma-separated options.")
|
"include comma-separated options.")
|
||||||
class AppendSort(argparse.Action):
|
class AppendSort(argparse.Action):
|
||||||
def __call__(self, parser, namespace, value, option):
|
def __call__(self, parser, namespace, value, option):
|
||||||
if namespace.sort is None:
|
if namespace.sort is None:
|
||||||
namespace.sort = []
|
namespace.sort = []
|
||||||
namespace.sort.append((value, True if option == '-S' else False))
|
namespace.sort.append((value, True if option == '-S' else False))
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-s', '--sort',
|
'-s', '--sort',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
action=AppendSort,
|
action=AppendSort,
|
||||||
help="Sort by this field.")
|
help="Sort by this field.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-S', '--reverse-sort',
|
'-S', '--reverse-sort',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
action=AppendSort,
|
action=AppendSort,
|
||||||
help="Sort by this field, but backwards.")
|
help="Sort by this field, but backwards.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-Y', '--summary',
|
'-Y', '--summary',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Only show the total.")
|
help="Only show the total.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--int',
|
'--int',
|
||||||
action='append',
|
action='append',
|
||||||
help="Treat these fields as ints.")
|
help="Treat these fields as ints.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--float',
|
'--float',
|
||||||
action='append',
|
action='append',
|
||||||
help="Treat these fields as floats.")
|
help="Treat these fields as floats.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--frac',
|
'--frac',
|
||||||
action='append',
|
action='append',
|
||||||
help="Treat these fields as fractions.")
|
help="Treat these fields as fractions.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--sum',
|
'--sum',
|
||||||
action='append',
|
action='append',
|
||||||
help="Add these fields (the default).")
|
help="Add these fields (the default).")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--prod',
|
'--prod',
|
||||||
action='append',
|
action='append',
|
||||||
help="Multiply these fields.")
|
help="Multiply these fields.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--min',
|
'--min',
|
||||||
action='append',
|
action='append',
|
||||||
help="Take the minimum of these fields.")
|
help="Take the minimum of these fields.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--max',
|
'--max',
|
||||||
action='append',
|
action='append',
|
||||||
help="Take the maximum of these fields.")
|
help="Take the maximum of these fields.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--avg', '--mean',
|
'--avg', '--mean',
|
||||||
action='append',
|
action='append',
|
||||||
help="Average these fields.")
|
help="Average these fields.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--stddev',
|
'--stddev',
|
||||||
action='append',
|
action='append',
|
||||||
help="Find the standard deviation of these fields.")
|
help="Find the standard deviation of these fields.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--gmean',
|
'--gmean',
|
||||||
action='append',
|
action='append',
|
||||||
help="Find the geometric mean of these fields.")
|
help="Find the geometric mean of these fields.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--gstddev',
|
'--gstddev',
|
||||||
action='append',
|
action='append',
|
||||||
help="Find the geometric standard deviation of these fields.")
|
help="Find the geometric standard deviation of these fields.")
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+27
-26
@@ -75,8 +75,8 @@ class RingIO:
|
|||||||
# pad to fill any existing canvas, but truncate to terminal size
|
# pad to fill any existing canvas, but truncate to terminal size
|
||||||
h = shutil.get_terminal_size((80, 5))[1]
|
h = shutil.get_terminal_size((80, 5))[1]
|
||||||
lines.extend('' for _ in range(
|
lines.extend('' for _ in range(
|
||||||
len(lines),
|
len(lines),
|
||||||
min(RingIO.canvas_lines, h)))
|
min(RingIO.canvas_lines, h)))
|
||||||
while len(lines) > h:
|
while len(lines) > h:
|
||||||
if self.head:
|
if self.head:
|
||||||
lines.pop()
|
lines.pop()
|
||||||
@@ -142,7 +142,7 @@ def main(path='-', *,
|
|||||||
time.sleep(sleep or 0.1)
|
time.sleep(sleep or 0.1)
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
print("error: file not found %r" % path,
|
print("error: file not found %r" % path,
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
@@ -157,32 +157,33 @@ if __name__ == "__main__":
|
|||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Efficiently displays the last n lines of a file/pipe.",
|
description="Efficiently displays the last n lines of a "
|
||||||
allow_abbrev=False)
|
"file/pipe.",
|
||||||
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'path',
|
'path',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
help="Path to read from.")
|
help="Path to read from.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-n', '--lines',
|
'-n', '--lines',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
const=0,
|
const=0,
|
||||||
help="Show this many lines of history. 0 uses the terminal height. "
|
help="Show this many lines of history. 0 uses the terminal "
|
||||||
"Defaults to 5.")
|
"height. Defaults to 5.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-z', '--cat',
|
'-z', '--cat',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Pipe directly to stdout.")
|
help="Pipe directly to stdout.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-s', '--sleep',
|
'-s', '--sleep',
|
||||||
type=float,
|
type=float,
|
||||||
help="Seconds to sleep between reads. Defaults to 0.01.")
|
help="Seconds to sleep between reads. Defaults to 0.01.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-k', '--keep-open',
|
'-k', '--keep-open',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Reopen the pipe on EOF, useful when multiple "
|
help="Reopen the pipe on EOF, useful when multiple "
|
||||||
"processes are writing.")
|
"processes are writing.")
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+14
-14
@@ -45,7 +45,7 @@ def main(in_path, out_paths, *, keep_open=False):
|
|||||||
pass
|
pass
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
print("error: file not found %r" % in_path,
|
print("error: file not found %r" % in_path,
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
@@ -55,20 +55,20 @@ if __name__ == "__main__":
|
|||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="tee, but for pipes.",
|
description="tee, but for pipes.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'in_path',
|
'in_path',
|
||||||
help="Path to read from.")
|
help="Path to read from.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'out_paths',
|
'out_paths',
|
||||||
nargs='+',
|
nargs='+',
|
||||||
help="Path to write to.")
|
help="Path to write to.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-k', '--keep-open',
|
'-k', '--keep-open',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Reopen the pipe on EOF, useful when multiple "
|
help="Reopen the pipe on EOF, useful when multiple "
|
||||||
"processes are writing.")
|
"processes are writing.")
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+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_DOTS = " .':"
|
||||||
CHARS_BRAILLE = (
|
CHARS_BRAILLE = (
|
||||||
'⠀⢀⡀⣀⠠⢠⡠⣠⠄⢄⡄⣄⠤⢤⡤⣤' '⠐⢐⡐⣐⠰⢰⡰⣰⠔⢔⡔⣔⠴⢴⡴⣴'
|
'⠀⢀⡀⣀⠠⢠⡠⣠⠄⢄⡄⣄⠤⢤⡤⣤' '⠐⢐⡐⣐⠰⢰⡰⣰⠔⢔⡔⣔⠴⢴⡴⣴'
|
||||||
'⠂⢂⡂⣂⠢⢢⡢⣢⠆⢆⡆⣆⠦⢦⡦⣦' '⠒⢒⡒⣒⠲⢲⡲⣲⠖⢖⡖⣖⠶⢶⡶⣶'
|
'⠂⢂⡂⣂⠢⢢⡢⣢⠆⢆⡆⣆⠦⢦⡦⣦' '⠒⢒⡒⣒⠲⢲⡲⣲⠖⢖⡖⣖⠶⢶⡶⣶'
|
||||||
'⠈⢈⡈⣈⠨⢨⡨⣨⠌⢌⡌⣌⠬⢬⡬⣬' '⠘⢘⡘⣘⠸⢸⡸⣸⠜⢜⡜⣜⠼⢼⡼⣼'
|
'⠈⢈⡈⣈⠨⢨⡨⣨⠌⢌⡌⣌⠬⢬⡬⣬' '⠘⢘⡘⣘⠸⢸⡸⣸⠜⢜⡜⣜⠼⢼⡼⣼'
|
||||||
'⠊⢊⡊⣊⠪⢪⡪⣪⠎⢎⡎⣎⠮⢮⡮⣮' '⠚⢚⡚⣚⠺⢺⡺⣺⠞⢞⡞⣞⠾⢾⡾⣾'
|
'⠊⢊⡊⣊⠪⢪⡪⣪⠎⢎⡎⣎⠮⢮⡮⣮' '⠚⢚⡚⣚⠺⢺⡺⣺⠞⢞⡞⣞⠾⢾⡾⣾'
|
||||||
'⠁⢁⡁⣁⠡⢡⡡⣡⠅⢅⡅⣅⠥⢥⡥⣥' '⠑⢑⡑⣑⠱⢱⡱⣱⠕⢕⡕⣕⠵⢵⡵⣵'
|
'⠁⢁⡁⣁⠡⢡⡡⣡⠅⢅⡅⣅⠥⢥⡥⣥' '⠑⢑⡑⣑⠱⢱⡱⣱⠕⢕⡕⣕⠵⢵⡵⣵'
|
||||||
'⠃⢃⡃⣃⠣⢣⡣⣣⠇⢇⡇⣇⠧⢧⡧⣧' '⠓⢓⡓⣓⠳⢳⡳⣳⠗⢗⡗⣗⠷⢷⡷⣷'
|
'⠃⢃⡃⣃⠣⢣⡣⣣⠇⢇⡇⣇⠧⢧⡧⣧' '⠓⢓⡓⣓⠳⢳⡳⣳⠗⢗⡗⣗⠷⢷⡷⣷'
|
||||||
'⠉⢉⡉⣉⠩⢩⡩⣩⠍⢍⡍⣍⠭⢭⡭⣭' '⠙⢙⡙⣙⠹⢹⡹⣹⠝⢝⡝⣝⠽⢽⡽⣽'
|
'⠉⢉⡉⣉⠩⢩⡩⣩⠍⢍⡍⣍⠭⢭⡭⣭' '⠙⢙⡙⣙⠹⢹⡹⣹⠝⢝⡝⣝⠽⢽⡽⣽'
|
||||||
'⠋⢋⡋⣋⠫⢫⡫⣫⠏⢏⡏⣏⠯⢯⡯⣯' '⠛⢛⡛⣛⠻⢻⡻⣻⠟⢟⡟⣟⠿⢿⡿⣿')
|
'⠋⢋⡋⣋⠫⢫⡫⣫⠏⢏⡏⣏⠯⢯⡯⣯' '⠛⢛⡛⣛⠻⢻⡻⣻⠟⢟⡟⣟⠿⢿⡿⣿')
|
||||||
|
|
||||||
|
|
||||||
def openio(path, mode='r', buffering=-1):
|
def openio(path, mode='r', buffering=-1):
|
||||||
@@ -156,8 +156,8 @@ class RingIO:
|
|||||||
# pad to fill any existing canvas, but truncate to terminal size
|
# pad to fill any existing canvas, but truncate to terminal size
|
||||||
h = shutil.get_terminal_size((80, 5))[1]
|
h = shutil.get_terminal_size((80, 5))[1]
|
||||||
lines.extend('' for _ in range(
|
lines.extend('' for _ in range(
|
||||||
len(lines),
|
len(lines),
|
||||||
min(RingIO.canvas_lines, h)))
|
min(RingIO.canvas_lines, h)))
|
||||||
while len(lines) > h:
|
while len(lines) > h:
|
||||||
if self.head:
|
if self.head:
|
||||||
lines.pop()
|
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, 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+b_x_, y+b_y_, a_x, a_y, b_x-b_x_, b_y-b_y_)
|
||||||
yield from hilbert_(
|
yield from hilbert_(
|
||||||
x+(a_x-a_dx)+(b_x_-b_dx), y+(a_y-a_dy)+(b_y_-b_dy),
|
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_))
|
-b_x_, -b_y_, -(a_x-a_x_), -(a_y-a_y_))
|
||||||
|
|
||||||
if width >= height:
|
if width >= height:
|
||||||
curve = hilbert_(0, 0, +width, 0, 0, +height)
|
curve = hilbert_(0, 0, +width, 0, 0, +height)
|
||||||
@@ -276,11 +276,11 @@ class Pixel(int):
|
|||||||
proged=False,
|
proged=False,
|
||||||
erased=False):
|
erased=False):
|
||||||
return super().__new__(cls,
|
return super().__new__(cls,
|
||||||
state
|
state
|
||||||
| (wear << 3)
|
| (wear << 3)
|
||||||
| (1 if readed else 0)
|
| (1 if readed else 0)
|
||||||
| (2 if proged else 0)
|
| (2 if proged else 0)
|
||||||
| (4 if erased else 0))
|
| (4 if erased else 0))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def wear(self):
|
def wear(self):
|
||||||
@@ -312,8 +312,8 @@ class Pixel(int):
|
|||||||
|
|
||||||
def __or__(self, other):
|
def __or__(self, other):
|
||||||
return Pixel(
|
return Pixel(
|
||||||
(int(self) | int(other)) & 7,
|
(int(self) | int(other)) & 7,
|
||||||
wear=max(self.wear, other.wear))
|
wear=max(self.wear, other.wear))
|
||||||
|
|
||||||
def worn(self, max_wear, *,
|
def worn(self, max_wear, *,
|
||||||
block_cycles=None,
|
block_cycles=None,
|
||||||
@@ -363,12 +363,11 @@ class Pixel(int):
|
|||||||
f = [colors[3]]
|
f = [colors[3]]
|
||||||
|
|
||||||
if wear:
|
if wear:
|
||||||
w = min(
|
w = min(self.worn(
|
||||||
self.worn(
|
max_wear,
|
||||||
max_wear,
|
block_cycles=block_cycles,
|
||||||
block_cycles=block_cycles,
|
wear_chars=wear_chars),
|
||||||
wear_chars=wear_chars),
|
1)
|
||||||
1)
|
|
||||||
|
|
||||||
c = wear_chars[int(w * (len(wear_chars)-1))]
|
c = wear_chars[int(w * (len(wear_chars)-1))]
|
||||||
f.append(wear_colors[int(w * (len(wear_colors)-1))])
|
f.append(wear_colors[int(w * (len(wear_colors)-1))])
|
||||||
@@ -390,8 +389,8 @@ class Pixel(int):
|
|||||||
# apply colors
|
# apply colors
|
||||||
if f and color:
|
if f and color:
|
||||||
c = '%s%s\x1b[m' % (
|
c = '%s%s\x1b[m' % (
|
||||||
''.join('\x1b[%sm' % f_ for f_ in f),
|
''.join('\x1b[%sm' % f_ for f_ in f),
|
||||||
c)
|
c)
|
||||||
|
|
||||||
return c
|
return c
|
||||||
|
|
||||||
@@ -457,25 +456,25 @@ class Bmap:
|
|||||||
block -= self._block_window.start
|
block -= self._block_window.start
|
||||||
|
|
||||||
size = (max(self._off_window.start,
|
size = (max(self._off_window.start,
|
||||||
min(self._off_window.stop, off+size))
|
min(self._off_window.stop, off+size))
|
||||||
- max(self._off_window.start,
|
- max(self._off_window.start,
|
||||||
min(self._off_window.stop, off)))
|
min(self._off_window.stop, off)))
|
||||||
off = (max(self._off_window.start,
|
off = (max(self._off_window.start,
|
||||||
min(self._off_window.stop, off))
|
min(self._off_window.stop, off))
|
||||||
- self._off_window.start)
|
- self._off_window.start)
|
||||||
if size == 0:
|
if size == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
# map to our block space
|
# map to our block space
|
||||||
range_ = range(
|
range_ = range(
|
||||||
block*len(self._off_window) + off,
|
block*len(self._off_window) + off,
|
||||||
block*len(self._off_window) + off+size)
|
block*len(self._off_window) + off+size)
|
||||||
range_ = range(
|
range_ = range(
|
||||||
(range_.start*len(self.pixels)) // self._window,
|
(range_.start*len(self.pixels)) // self._window,
|
||||||
(range_.stop*len(self.pixels)) // self._window)
|
(range_.stop*len(self.pixels)) // self._window)
|
||||||
range_ = range(
|
range_ = range(
|
||||||
range_.start,
|
range_.start,
|
||||||
max(range_.stop, range_.start+1))
|
max(range_.stop, range_.start+1))
|
||||||
|
|
||||||
# apply the op
|
# apply the op
|
||||||
for i in range_:
|
for i in range_:
|
||||||
@@ -499,9 +498,9 @@ class Bmap:
|
|||||||
width=None,
|
width=None,
|
||||||
height=None):
|
height=None):
|
||||||
block_size = (block_size if block_size is not 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
|
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
|
width = width if width is not None else self.width
|
||||||
height = height if height is not None else self.height
|
height = height if height is not None else self.height
|
||||||
|
|
||||||
@@ -519,17 +518,17 @@ class Bmap:
|
|||||||
for x in range(width*height):
|
for x in range(width*height):
|
||||||
# map into our old bd space
|
# map into our old bd space
|
||||||
range_ = range(
|
range_ = range(
|
||||||
(x*self._window) // (width*height),
|
(x*self._window) // (width*height),
|
||||||
((x+1)*self._window) // (width*height))
|
((x+1)*self._window) // (width*height))
|
||||||
range_ = range(
|
range_ = range(
|
||||||
range_.start,
|
range_.start,
|
||||||
max(range_.stop, range_.start+1))
|
max(range_.stop, range_.start+1))
|
||||||
|
|
||||||
# aggregate state
|
# aggregate state
|
||||||
pixels.append(ft.reduce(
|
pixels.append(ft.reduce(
|
||||||
Pixel.__or__,
|
Pixel.__or__,
|
||||||
self.pixels[range_.start:range_.stop],
|
self.pixels[range_.start:range_.stop],
|
||||||
Pixel()))
|
Pixel()))
|
||||||
|
|
||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
@@ -580,10 +579,10 @@ class Bmap:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
grid = list(it.chain.from_iterable(
|
grid = list(it.chain.from_iterable(
|
||||||
# did we resize?
|
# did we resize?
|
||||||
it.islice(it.chain(h, it.repeat(Pixel())),
|
it.islice(it.chain(h, it.repeat(Pixel())),
|
||||||
self.width*self.height)
|
self.width*self.height)
|
||||||
for h in self.history))
|
for h in self.history))
|
||||||
self.history = []
|
self.history = []
|
||||||
|
|
||||||
line = []
|
line = []
|
||||||
@@ -603,14 +602,14 @@ class Bmap:
|
|||||||
byte_p |= 1 << i
|
byte_p |= 1 << i
|
||||||
|
|
||||||
line.append(best_p.draw(
|
line.append(best_p.draw(
|
||||||
max_wear,
|
max_wear,
|
||||||
CHARS_BRAILLE[byte_p],
|
CHARS_BRAILLE[byte_p],
|
||||||
braille=True,
|
braille=True,
|
||||||
read=read,
|
read=read,
|
||||||
prog=prog,
|
prog=prog,
|
||||||
erase=erase,
|
erase=erase,
|
||||||
wear=wear,
|
wear=wear,
|
||||||
**args))
|
**args))
|
||||||
elif dots:
|
elif dots:
|
||||||
# encode into a byte
|
# encode into a byte
|
||||||
for x in range(self.width):
|
for x in range(self.width):
|
||||||
@@ -627,23 +626,23 @@ class Bmap:
|
|||||||
byte_p |= 1 << i
|
byte_p |= 1 << i
|
||||||
|
|
||||||
line.append(best_p.draw(
|
line.append(best_p.draw(
|
||||||
max_wear,
|
max_wear,
|
||||||
CHARS_DOTS[byte_p],
|
CHARS_DOTS[byte_p],
|
||||||
dots=True,
|
dots=True,
|
||||||
read=read,
|
read=read,
|
||||||
prog=prog,
|
prog=prog,
|
||||||
erase=erase,
|
erase=erase,
|
||||||
wear=wear,
|
wear=wear,
|
||||||
**args))
|
**args))
|
||||||
else:
|
else:
|
||||||
for x in range(self.width):
|
for x in range(self.width):
|
||||||
line.append(grid[x + row*self.width].draw(
|
line.append(grid[x + row*self.width].draw(
|
||||||
max_wear,
|
max_wear,
|
||||||
read=read,
|
read=read,
|
||||||
prog=prog,
|
prog=prog,
|
||||||
erase=erase,
|
erase=erase,
|
||||||
wear=wear,
|
wear=wear,
|
||||||
**args))
|
**args))
|
||||||
|
|
||||||
return ''.join(line)
|
return ''.join(line)
|
||||||
|
|
||||||
@@ -725,7 +724,7 @@ def main(path='-', *,
|
|||||||
|
|
||||||
if any(isinstance(b, list) and len(b) > 1 for b in block):
|
if any(isinstance(b, list) and len(b) > 1 for b in block):
|
||||||
print("error: more than one block address?",
|
print("error: more than one block address?",
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
if isinstance(block[0], list):
|
if isinstance(block[0], list):
|
||||||
block = (block[0][0], *block[1:])
|
block = (block[0][0], *block[1:])
|
||||||
@@ -765,10 +764,10 @@ def main(path='-', *,
|
|||||||
|
|
||||||
# create our block device representation
|
# create our block device representation
|
||||||
bmap = Bmap(
|
bmap = Bmap(
|
||||||
block_size=block_size if block_size is not None else 1,
|
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_count=block_count if block_count is not None else 1,
|
||||||
block_window=block_window,
|
block_window=block_window,
|
||||||
off_window=off_window)
|
off_window=off_window)
|
||||||
|
|
||||||
def resize():
|
def resize():
|
||||||
nonlocal bmap
|
nonlocal bmap
|
||||||
@@ -791,12 +790,13 @@ def main(path='-', *,
|
|||||||
# terminal size changed?
|
# terminal size changed?
|
||||||
if width_ != bmap.width or height_ != bmap.height:
|
if width_ != bmap.width or height_ != bmap.height:
|
||||||
bmap.resize(
|
bmap.resize(
|
||||||
# scale if we're printing with dots or braille
|
# scale if we're printing with dots or braille
|
||||||
width=2*width_ if braille else width_,
|
width=2*width_ if braille else width_,
|
||||||
height=max(1,
|
height=max(
|
||||||
4*height_ if braille
|
1,
|
||||||
else 2*height_ if dots
|
4*height_ if braille
|
||||||
else height_))
|
else 2*height_ if dots
|
||||||
|
else height_))
|
||||||
resize()
|
resize()
|
||||||
|
|
||||||
# keep track of some extra info
|
# keep track of some extra info
|
||||||
@@ -806,30 +806,32 @@ def main(path='-', *,
|
|||||||
|
|
||||||
# parse a line of trace output
|
# parse a line of trace output
|
||||||
pattern = re.compile(
|
pattern = re.compile(
|
||||||
'^(?P<file>[^:]*):(?P<line>[0-9]+):trace:.*?bd_(?:'
|
'^(?P<file>[^:]*):(?P<line>[0-9]+):trace:.*?bd_(?:'
|
||||||
'(?P<create>create\w*)\('
|
'(?P<create>create\w*)\('
|
||||||
'(?:'
|
'(?:'
|
||||||
'block_size=(?P<block_size>\w+)'
|
'block_size=(?P<block_size>\w+)'
|
||||||
'|' 'block_count=(?P<block_count>\w+)'
|
'|' 'block_count=(?P<block_count>\w+)'
|
||||||
'|' '.*?' ')*' '\)'
|
'|' '.*?' ')*'
|
||||||
'|' '(?P<read>read)\('
|
'\)'
|
||||||
'\s*(?P<read_ctx>\w+)' '\s*,'
|
'|' '(?P<read>read)\('
|
||||||
'\s*(?P<read_block>\w+)' '\s*,'
|
'\s*(?P<read_ctx>\w+)' '\s*,'
|
||||||
'\s*(?P<read_off>\w+)' '\s*,'
|
'\s*(?P<read_block>\w+)' '\s*,'
|
||||||
'\s*(?P<read_buffer>\w+)' '\s*,'
|
'\s*(?P<read_off>\w+)' '\s*,'
|
||||||
'\s*(?P<read_size>\w+)' '\s*\)'
|
'\s*(?P<read_buffer>\w+)' '\s*,'
|
||||||
'|' '(?P<prog>prog)\('
|
'\s*(?P<read_size>\w+)' '\s*\)'
|
||||||
'\s*(?P<prog_ctx>\w+)' '\s*,'
|
'|' '(?P<prog>prog)\('
|
||||||
'\s*(?P<prog_block>\w+)' '\s*,'
|
'\s*(?P<prog_ctx>\w+)' '\s*,'
|
||||||
'\s*(?P<prog_off>\w+)' '\s*,'
|
'\s*(?P<prog_block>\w+)' '\s*,'
|
||||||
'\s*(?P<prog_buffer>\w+)' '\s*,'
|
'\s*(?P<prog_off>\w+)' '\s*,'
|
||||||
'\s*(?P<prog_size>\w+)' '\s*\)'
|
'\s*(?P<prog_buffer>\w+)' '\s*,'
|
||||||
'|' '(?P<erase>erase)\('
|
'\s*(?P<prog_size>\w+)' '\s*\)'
|
||||||
'\s*(?P<erase_ctx>\w+)' '\s*,'
|
'|' '(?P<erase>erase)\('
|
||||||
'\s*(?P<erase_block>\w+)'
|
'\s*(?P<erase_ctx>\w+)' '\s*,'
|
||||||
'\s*\(\s*(?P<erase_size>\w+)\s*\)' '\s*\)'
|
'\s*(?P<erase_block>\w+)'
|
||||||
'|' '(?P<sync>sync)\('
|
'\s*\(\s*(?P<erase_size>\w+)\s*\)' '\s*\)'
|
||||||
'\s*(?P<sync_ctx>\w+)' '\s*\)' ')\s*$')
|
'|' '(?P<sync>sync)\('
|
||||||
|
'\s*(?P<sync_ctx>\w+)' '\s*\)'
|
||||||
|
')\s*$')
|
||||||
def parse(line):
|
def parse(line):
|
||||||
nonlocal bmap
|
nonlocal bmap
|
||||||
nonlocal readed
|
nonlocal readed
|
||||||
@@ -852,21 +854,21 @@ def main(path='-', *,
|
|||||||
|
|
||||||
if reset:
|
if reset:
|
||||||
bmap = Bmap(
|
bmap = Bmap(
|
||||||
block_size=block_size_,
|
block_size=block_size_,
|
||||||
block_count=block_count_,
|
block_count=block_count_,
|
||||||
block_window=bmap.block_window,
|
block_window=bmap.block_window,
|
||||||
off_window=bmap.off_window,
|
off_window=bmap.off_window,
|
||||||
width=bmap.width,
|
width=bmap.width,
|
||||||
height=bmap.height)
|
height=bmap.height)
|
||||||
elif ((block_size is None
|
elif ((block_size is None
|
||||||
and block_size_ != bmap.block_size)
|
and block_size_ != bmap.block_size)
|
||||||
or (block_count is None
|
or (block_count is None
|
||||||
and block_count_ != bmap.block_count)):
|
and block_count_ != bmap.block_count)):
|
||||||
bmap.resize(
|
bmap.resize(
|
||||||
block_size=block_size if block_size is not None
|
block_size=block_size if block_size is not None
|
||||||
else block_size_,
|
else block_size_,
|
||||||
block_count=block_count if block_count is not None
|
block_count=block_count if block_count is not None
|
||||||
else block_count_)
|
else block_count_)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
elif m.group('read') and read:
|
elif m.group('read') and read:
|
||||||
@@ -877,10 +879,10 @@ def main(path='-', *,
|
|||||||
if ((block_size is None and off+size > bmap.block_size)
|
if ((block_size is None and off+size > bmap.block_size)
|
||||||
or (block_count is None and block >= bmap.block_count)):
|
or (block_count is None and block >= bmap.block_count)):
|
||||||
bmap.resize(
|
bmap.resize(
|
||||||
block_size=block_size if block_size is not None
|
block_size=block_size if block_size is not None
|
||||||
else max(off+size, bmap.block_size),
|
else max(off+size, bmap.block_size),
|
||||||
block_count=block_count if block_count is not None
|
block_count=block_count if block_count is not None
|
||||||
else max(block+1, bmap.block_count))
|
else max(block+1, bmap.block_count))
|
||||||
|
|
||||||
bmap.read(block, off, size)
|
bmap.read(block, off, size)
|
||||||
readed += size
|
readed += size
|
||||||
@@ -894,10 +896,10 @@ def main(path='-', *,
|
|||||||
if ((block_size is None and off+size > bmap.block_size)
|
if ((block_size is None and off+size > bmap.block_size)
|
||||||
or (block_count is None and block >= bmap.block_count)):
|
or (block_count is None and block >= bmap.block_count)):
|
||||||
bmap.resize(
|
bmap.resize(
|
||||||
block_size=block_size if block_size is not None
|
block_size=block_size if block_size is not None
|
||||||
else max(off+size, bmap.block_size),
|
else max(off+size, bmap.block_size),
|
||||||
block_count=block_count if block_count is not None
|
block_count=block_count if block_count is not None
|
||||||
else max(block+1, bmap.block_count))
|
else max(block+1, bmap.block_count))
|
||||||
|
|
||||||
bmap.prog(block, off, size)
|
bmap.prog(block, off, size)
|
||||||
proged += size
|
proged += size
|
||||||
@@ -910,10 +912,10 @@ def main(path='-', *,
|
|||||||
if ((block_size is None and size > bmap.block_size)
|
if ((block_size is None and size > bmap.block_size)
|
||||||
or (block_count is None and block >= bmap.block_count)):
|
or (block_count is None and block >= bmap.block_count)):
|
||||||
bmap.resize(
|
bmap.resize(
|
||||||
block_size=block_size if block_size is not None
|
block_size=block_size if block_size is not None
|
||||||
else max(size, bmap.block_size),
|
else max(size, bmap.block_size),
|
||||||
block_count=block_count if block_count is not None
|
block_count=block_count if block_count is not None
|
||||||
else max(block+1, bmap.block_count))
|
else max(block+1, bmap.block_count))
|
||||||
|
|
||||||
bmap.erase(block, size)
|
bmap.erase(block, size)
|
||||||
erased += size
|
erased += size
|
||||||
@@ -936,20 +938,20 @@ def main(path='-', *,
|
|||||||
# don't forget we've scaled this for braille/dots!
|
# don't forget we've scaled this for braille/dots!
|
||||||
for row in range(
|
for row in range(
|
||||||
mt.ceil(bmap.height/4) if braille
|
mt.ceil(bmap.height/4) if braille
|
||||||
else mt.ceil(bmap.height/2) if dots
|
else mt.ceil(bmap.height/2) if dots
|
||||||
else bmap.height):
|
else bmap.height):
|
||||||
line = bmap.draw(row,
|
line = bmap.draw(row,
|
||||||
read=read,
|
read=read,
|
||||||
prog=prog,
|
prog=prog,
|
||||||
erase=erase,
|
erase=erase,
|
||||||
wear=wear,
|
wear=wear,
|
||||||
block_cycles=block_cycles,
|
block_cycles=block_cycles,
|
||||||
color=color,
|
color=color,
|
||||||
dots=dots,
|
dots=dots,
|
||||||
braille=braille,
|
braille=braille,
|
||||||
hilbert=hilbert,
|
hilbert=hilbert,
|
||||||
lebesgue=lebesgue,
|
lebesgue=lebesgue,
|
||||||
**args)
|
**args)
|
||||||
if line:
|
if line:
|
||||||
f.writeln(line)
|
f.writeln(line)
|
||||||
|
|
||||||
@@ -965,9 +967,9 @@ def main(path='-', *,
|
|||||||
# what we have
|
# what we have
|
||||||
if wear:
|
if wear:
|
||||||
mean = (sum(p.wear for p in bmap.pixels)
|
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)
|
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)
|
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
|
# 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:
|
if len(f.lines) == 0:
|
||||||
f.lines.append('')
|
f.lines.append('')
|
||||||
f.lines[0] = 'bd %dx%d%s%s%s%s' % (
|
f.lines[0] = 'bd %dx%d%s%s%s%s' % (
|
||||||
bmap.block_size, bmap.block_count,
|
bmap.block_size, bmap.block_count,
|
||||||
', %6s read' % ('%.1f%%' % (100*readed / max(total, 1)))
|
', %6s read' % ('%.1f%%' % (100*readed / max(total, 1)))
|
||||||
if read else '',
|
if read else '',
|
||||||
', %6s prog' % ('%.1f%%' % (100*proged / max(total, 1)))
|
', %6s prog' % ('%.1f%%' % (100*proged / max(total, 1)))
|
||||||
if prog else '',
|
if prog else '',
|
||||||
', %6s erase' % ('%.1f%%' % (100*erased / max(total, 1)))
|
', %6s erase' % ('%.1f%%' % (100*erased / max(total, 1)))
|
||||||
if erase else '',
|
if erase else '',
|
||||||
', %13s wear' % ('%.1fσ (%.1f%%)' % (
|
', %13s wear' % ('%.1fσ (%.1f%%)' % (
|
||||||
worst / max(stddev, 1),
|
worst / max(stddev, 1),
|
||||||
100*stddev / max(worst, 1)))
|
100*stddev / max(worst, 1)))
|
||||||
if wear else '')
|
if wear else '')
|
||||||
|
|
||||||
bmap.clear()
|
bmap.clear()
|
||||||
readed = 0
|
readed = 0
|
||||||
@@ -1040,7 +1042,7 @@ def main(path='-', *,
|
|||||||
time.sleep(sleep or 0.1)
|
time.sleep(sleep or 0.1)
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
print("error: file not found %r" % path,
|
print("error: file not found %r" % path,
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
@@ -1056,145 +1058,147 @@ if __name__ == "__main__":
|
|||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Render operations on block devices based on "
|
description="Render operations on block devices based on "
|
||||||
"trace output.",
|
"trace output.",
|
||||||
allow_abbrev=False)
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'path',
|
'path',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
help="Path to read from.")
|
help="Path to read from.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--block-size',
|
'-b', '--block-size',
|
||||||
type=bdgeom,
|
type=bdgeom,
|
||||||
help="Block size/geometry in bytes.")
|
help="Block size/geometry in bytes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--block-count',
|
'--block-count',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Block count in blocks.")
|
help="Block count in blocks.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-c', '--block-cycles',
|
'-c', '--block-cycles',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Assumed maximum number of erase cycles when measuring wear.")
|
help="Assumed maximum number of erase cycles when measuring "
|
||||||
|
"wear.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-@', '--block',
|
'-@', '--block',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: tuple(
|
type=lambda x: tuple(
|
||||||
rbydaddr(x) if x.strip() else None
|
rbydaddr(x) if x.strip() else None
|
||||||
for x in x.split(',')),
|
for x in x.split(',')),
|
||||||
help="Optional block to show, may be a range.")
|
help="Optional block to show, may be a range.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--off',
|
'--off',
|
||||||
type=lambda x: tuple(
|
type=lambda x: tuple(
|
||||||
int(x, 0) if x.strip() else None
|
int(x, 0) if x.strip() else None
|
||||||
for x in x.split(',')),
|
for x in x.split(',')),
|
||||||
help="Show a specific offset, may be a range.")
|
help="Show a specific offset, may be a range.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--size',
|
'--size',
|
||||||
type=lambda x: tuple(
|
type=lambda x: tuple(
|
||||||
int(x, 0) if x.strip() else None
|
int(x, 0) if x.strip() else None
|
||||||
for x in x.split(',')),
|
for x in x.split(',')),
|
||||||
help="Show this many bytes, may be a range.")
|
help="Show this many bytes, may be a range.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-r', '--read',
|
'-r', '--read',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Render reads.")
|
help="Render reads.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-p', '--prog',
|
'-p', '--prog',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Render progs.")
|
help="Render progs.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-e', '--erase',
|
'-e', '--erase',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Render erases.")
|
help="Render erases.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-w', '--wear',
|
'-w', '--wear',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Render wear.")
|
help="Render wear.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-R', '--reset',
|
'-R', '--reset',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Reset wear on block device initialization.")
|
help="Reset wear on block device initialization.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-N', '--no-header',
|
'-N', '--no-header',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Don't show the header.")
|
help="Don't show the header.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--color',
|
'--color',
|
||||||
choices=['never', 'always', 'auto'],
|
choices=['never', 'always', 'auto'],
|
||||||
default='auto',
|
default='auto',
|
||||||
help="When to use terminal colors. Defaults to 'auto'.")
|
help="When to use terminal colors. Defaults to 'auto'.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-:', '--dots',
|
'-:', '--dots',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Use 1x2 ascii dot characters.")
|
help="Use 1x2 ascii dot characters.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-⣿', '--braille',
|
'-⣿', '--braille',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Use 2x4 unicode braille characters. Note that braille characters "
|
help="Use 2x4 unicode braille characters. Note that braille "
|
||||||
"sometimes suffer from inconsistent widths.")
|
"characters sometimes suffer from inconsistent widths.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--chars',
|
'--chars',
|
||||||
help="Characters to use for read, prog, erase, noop operations.")
|
help="Characters to use for read, prog, erase, noop operations.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--wear-chars',
|
'--wear-chars',
|
||||||
help="Characters to use for showing wear.")
|
help="Characters to use for showing wear.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--colors',
|
'--colors',
|
||||||
type=lambda x: [x.strip() for x in x.split(',')],
|
type=lambda x: [x.strip() for x in x.split(',')],
|
||||||
help="Colors to use for read, prog, erase, noop operations.")
|
help="Colors to use for read, prog, erase, noop operations.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--wear-colors',
|
'--wear-colors',
|
||||||
type=lambda x: [x.strip() for x in x.split(',')],
|
type=lambda x: [x.strip() for x in x.split(',')],
|
||||||
help="Colors to use for showing wear.")
|
help="Colors to use for showing wear.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-W', '--width',
|
'-W', '--width',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
const=0,
|
const=0,
|
||||||
help="Width in columns. 0 uses the terminal width. Defaults to "
|
help="Width in columns. 0 uses the terminal width. Defaults to "
|
||||||
"min(terminal, 80).")
|
"min(terminal, 80).")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-H', '--height',
|
'-H', '--height',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
const=0,
|
const=0,
|
||||||
help="Height in rows. 0 uses the terminal height. Defaults to 1.")
|
help="Height in rows. 0 uses the terminal height. Defaults to 1.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-n', '--lines',
|
'-n', '--lines',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
const=0,
|
const=0,
|
||||||
help="Show this many lines of history. 0 uses the terminal height. "
|
help="Show this many lines of history. 0 uses the terminal "
|
||||||
"Defaults to 5.")
|
"height. Defaults to 5.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-^', '--head',
|
'-^', '--head',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show the first n lines.")
|
help="Show the first n lines.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-z', '--cat',
|
'-z', '--cat',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Pipe directly to stdout.")
|
help="Pipe directly to stdout.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-U', '--hilbert',
|
'-U', '--hilbert',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Render as a space-filling Hilbert curve.")
|
help="Render as a space-filling Hilbert curve.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-Z', '--lebesgue',
|
'-Z', '--lebesgue',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Render as a space-filling Z-curve.")
|
help="Render as a space-filling Z-curve.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-S', '--coalesce',
|
'-S', '--coalesce',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
help="Number of operations to coalesce together.")
|
help="Number of operations to coalesce together.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-s', '--sleep',
|
'-s', '--sleep',
|
||||||
type=float,
|
type=float,
|
||||||
help="Time in seconds to sleep between reads, coalescing operations.")
|
help="Time in seconds to sleep between reads, coalescing "
|
||||||
|
"operations.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-k', '--keep-open',
|
'-k', '--keep-open',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Reopen the pipe on EOF, useful when multiple "
|
help="Reopen the pipe on EOF, useful when multiple "
|
||||||
"processes are writing.")
|
"processes are writing.")
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_intermixed_args()).items()
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
+55
-54
@@ -49,13 +49,13 @@ else:
|
|||||||
|
|
||||||
# wait for interesting events
|
# wait for interesting events
|
||||||
flags = (inotify_simple.flags.ATTRIB
|
flags = (inotify_simple.flags.ATTRIB
|
||||||
| inotify_simple.flags.CREATE
|
| inotify_simple.flags.CREATE
|
||||||
| inotify_simple.flags.DELETE
|
| inotify_simple.flags.DELETE
|
||||||
| inotify_simple.flags.DELETE_SELF
|
| inotify_simple.flags.DELETE_SELF
|
||||||
| inotify_simple.flags.MODIFY
|
| inotify_simple.flags.MODIFY
|
||||||
| inotify_simple.flags.MOVED_FROM
|
| inotify_simple.flags.MOVED_FROM
|
||||||
| inotify_simple.flags.MOVED_TO
|
| inotify_simple.flags.MOVED_TO
|
||||||
| inotify_simple.flags.MOVE_SELF)
|
| inotify_simple.flags.MOVE_SELF)
|
||||||
|
|
||||||
# recurse into directories
|
# recurse into directories
|
||||||
for path in paths:
|
for path in paths:
|
||||||
@@ -113,8 +113,8 @@ class RingIO:
|
|||||||
# pad to fill any existing canvas, but truncate to terminal size
|
# pad to fill any existing canvas, but truncate to terminal size
|
||||||
h = shutil.get_terminal_size((80, 5))[1]
|
h = shutil.get_terminal_size((80, 5))[1]
|
||||||
lines.extend('' for _ in range(
|
lines.extend('' for _ in range(
|
||||||
len(lines),
|
len(lines),
|
||||||
min(RingIO.canvas_lines, h)))
|
min(RingIO.canvas_lines, h)))
|
||||||
while len(lines) > h:
|
while len(lines) > h:
|
||||||
if self.head:
|
if self.head:
|
||||||
lines.pop()
|
lines.pop()
|
||||||
@@ -154,7 +154,7 @@ def main(command, *,
|
|||||||
exit_on_error=False):
|
exit_on_error=False):
|
||||||
if not command:
|
if not command:
|
||||||
print('usage: %s [options] command' % sys.argv[0],
|
print('usage: %s [options] command' % sys.argv[0],
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# if we have keep_open_paths, assume user wanted keep_open
|
# if we have keep_open_paths, assume user wanted keep_open
|
||||||
@@ -199,12 +199,12 @@ def main(command, *,
|
|||||||
if lines:
|
if lines:
|
||||||
h = lines
|
h = lines
|
||||||
fcntl.ioctl(spty, termios.TIOCSWINSZ,
|
fcntl.ioctl(spty, termios.TIOCSWINSZ,
|
||||||
struct.pack('HHHH', h, w, 0, 0))
|
struct.pack('HHHH', h, w, 0, 0))
|
||||||
|
|
||||||
proc = sp.Popen(command,
|
proc = sp.Popen(command,
|
||||||
stdout=spty,
|
stdout=spty,
|
||||||
stderr=spty,
|
stderr=spty,
|
||||||
close_fds=False)
|
close_fds=False)
|
||||||
os.close(spty)
|
os.close(spty)
|
||||||
mpty = os.fdopen(mpty, 'r', 1)
|
mpty = os.fdopen(mpty, 'r', 1)
|
||||||
|
|
||||||
@@ -261,54 +261,55 @@ if __name__ == "__main__":
|
|||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Traditional watch command, but with higher resolution "
|
description="Traditional watch command, but with higher "
|
||||||
"updates and a bit different options/output format.",
|
"resolution updates and a bit different options/output "
|
||||||
allow_abbrev=False)
|
"format.",
|
||||||
|
allow_abbrev=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'command',
|
'command',
|
||||||
nargs=argparse.REMAINDER,
|
nargs=argparse.REMAINDER,
|
||||||
help="Command to run.")
|
help="Command to run.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-n', '--lines',
|
'-n', '--lines',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
type=lambda x: int(x, 0),
|
type=lambda x: int(x, 0),
|
||||||
const=0,
|
const=0,
|
||||||
help="Show this many lines of history. 0 uses the terminal height. "
|
help="Show this many lines of history. 0 uses the terminal "
|
||||||
"Defaults to 0.")
|
"height. Defaults to 0.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-^', '--head',
|
'-^', '--head',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Show the first n lines.")
|
help="Show the first n lines.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-z', '--cat',
|
'-z', '--cat',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Pipe directly to stdout.")
|
help="Pipe directly to stdout.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-s', '--sleep',
|
'-s', '--sleep',
|
||||||
type=float,
|
type=float,
|
||||||
help="Seconds to sleep between runs. Defaults to 0.1.")
|
help="Seconds to sleep between runs. Defaults to 0.1.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-k', '--keep-open',
|
'-k', '--keep-open',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Try to use inotify to wait for changes.")
|
help="Try to use inotify to wait for changes.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-K', '--keep-open-path',
|
'-K', '--keep-open-path',
|
||||||
dest='keep_open_paths',
|
dest='keep_open_paths',
|
||||||
action='append',
|
action='append',
|
||||||
help="Use this path for inotify. Defaults to guessing. Implies "
|
help="Use this path for inotify. Defaults to guessing. Implies "
|
||||||
"--keep-open.")
|
"--keep-open.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-b', '--buffer',
|
'-b', '--buffer',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Wait until command finishes to show the output.")
|
help="Wait until command finishes to show the output.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-i', '--ignore-errors',
|
'-i', '--ignore-errors',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Only show output after successful runs. Implies --buffer.")
|
help="Only show output after successful runs. Implies --buffer.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-e', '--exit-on-error',
|
'-e', '--exit-on-error',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="Exit on error.")
|
help="Exit on error.")
|
||||||
sys.exit(main(**{k: v
|
sys.exit(main(**{k: v
|
||||||
for k, v in vars(parser.parse_args()).items()
|
for k, v in vars(parser.parse_args()).items()
|
||||||
if v is not None}))
|
if v is not None}))
|
||||||
|
|||||||
Reference in New Issue
Block a user