scripts: Added -U/--undefine as inverse -D/--define to most scripts

This adds -U/--undefine as an inverse -D/--define, allowing you to
select results where a given field does _not_ match a set of
values/globs.

For example, make bench-marks, which need to ignore stack/heap/usage
probes as a special case, can easily filter like so:

  $ ./scripts/csv.py test.csv -Uprobe=stack,heap,usage

---

One thing globbing is pretty bad at is inverse matches. This is
_usually_ easy enough to work around, but has been an annoyance enough
times that I think _some_ option to inverse filter is warranted.

I'm not sure -U/--undefine is the best name for this, since field isn't
really "undefined" as a result (well kinda? if you're relying on
implicit by/field rules?), but it gets the job done.
This commit is contained in:
Christopher Haster
2026-02-07 00:16:00 -06:00
parent 37288fceab
commit ba1f5e730d
14 changed files with 438 additions and 101 deletions
+41 -9
View File
@@ -262,7 +262,9 @@ def dat(x, *args):
else:
raise
def collect(csv_paths, defines=[]):
def collect(csv_paths, *,
defines=[],
undefines=[]):
# collect results from CSV files
fields = []
results = []
@@ -279,22 +281,34 @@ def collect(csv_paths, defines=[]):
for v in vs)
for k, vs in defines):
continue
if any(any(fnmatch.fnmatchcase(r.get(k, ''), v)
for v in vs)
for k, vs in undefines):
continue
results.append(r)
except FileNotFoundError:
pass
return fields, results
def fold(results, by=None, fields=None, defines=[]):
def fold(results, by=None, fields=None, *,
defines=[],
undefines=[]):
# filter by matching defines
if defines:
if defines or undefines:
results_ = []
for r in results:
if all(any(fnmatch.fnmatchcase(r.get(k, ''), v)
if not all(any(fnmatch.fnmatchcase(r.get(k, ''), v)
for v in vs)
for k, vs in defines):
results_.append(r)
continue
if any(any(fnmatch.fnmatchcase(r.get(k, ''), v)
for v in vs)
for k, vs in undefines):
continue
results_.append(r)
results = results_
if by:
@@ -1017,6 +1031,7 @@ def main_(ring, csv_paths, *,
by=None,
fields=None,
defines=[],
undefines=[],
labels=[],
chars=[],
colors=[],
@@ -1087,7 +1102,9 @@ def main_(ring, csv_paths, *,
height_ = max(0, shutil.get_terminal_size((80, 5))[1] + height)
# first collect results from CSV files
fields_, results = collect(csv_paths, defines)
fields_, results = collect(csv_paths,
defines=defines,
undefines=undefines)
if not by and not fields:
print("error: needs --by or --fields to figure out fields",
@@ -1098,16 +1115,20 @@ def main_(ring, csv_paths, *,
if not by:
by = [k for k in fields_
if k not in (fields or [])
and not any(k == k_ for k_, _ in defines)]
and not any(k == k_ for k_, _ in defines)
and not any(k == k_ for k_, _ in undefines)]
# if fields not specified, guess it's anything not in by/defines
if not fields:
fields = [k for k in fields_
if k not in (by or [])
and not any(k == k_ for k_, _ in defines)]
and not any(k == k_ for k_, _ in defines)
and not any(k == k_ for k_, _ in undefines)]
# then extract the requested dataset
datasets, dataattrs = fold(results, by, fields, defines)
datasets, dataattrs = fold(results, by, fields,
defines=defines,
undefines=undefines)
# build tile heirarchy
children = []
@@ -1421,6 +1442,17 @@ if __name__ == "__main__":
)(*x.split('=', 1)),
help="Only include results where this field is this value. May "
"include comma-separated options and globs.")
parser.add_argument(
'-U', '--undefine',
dest='undefines',
action='append',
type=lambda x: (
lambda k, vs: (
k.strip(),
{v.strip() for v in vs.split(',')})
)(*x.split('=', 1)),
help="Don't include results where this field is this value. May "
"include comma-separated options and globs.")
parser.add_argument(
'-L', '--add-label',
dest='labels',