Made summary.py more powerful, dropped -m from size scripts
With more scripts generating CSV files this moves most CSV manipulation into summary.py, which can now handle more or less any arbitrary CSV file with arbitrary names and fields. This also includes a bunch of additional, probably unnecessary, tweaks: - summary.py/coverage.py use a custom fractional type for encoding fractions, this will also be used for test counts. - Added a smaller diff output for size scripts with the --percent flag. - Added line and hit info to coverage.py's CSV files. - Added --tree flag to stack.py to show only the call tree without other noise. - Renamed structs.py to struct.py. - Changed a few flags around for consistency between size/summary scripts. - Added `make sizes` alias. - Added `make lfs.code.csv` rules
This commit is contained in:
@@ -67,7 +67,7 @@ override TESTFLAGS += -v
|
|||||||
override CODEFLAGS += -v
|
override CODEFLAGS += -v
|
||||||
override DATAFLAGS += -v
|
override DATAFLAGS += -v
|
||||||
override STACKFLAGS += -v
|
override STACKFLAGS += -v
|
||||||
override STRUCTSFLAGS += -v
|
override STRUCTFLAGS += -v
|
||||||
override COVERAGEFLAGS += -v
|
override COVERAGEFLAGS += -v
|
||||||
override TESTFLAGS += -v
|
override TESTFLAGS += -v
|
||||||
override TESTCFLAGS += -v
|
override TESTCFLAGS += -v
|
||||||
@@ -76,11 +76,10 @@ ifdef EXEC
|
|||||||
override TESTFLAGS += --exec="$(EXEC)"
|
override TESTFLAGS += --exec="$(EXEC)"
|
||||||
endif
|
endif
|
||||||
ifdef BUILDDIR
|
ifdef BUILDDIR
|
||||||
override TESTFLAGS += --build-dir="$(BUILDDIR:/=)"
|
|
||||||
override CODEFLAGS += --build-dir="$(BUILDDIR:/=)"
|
override CODEFLAGS += --build-dir="$(BUILDDIR:/=)"
|
||||||
override DATAFLAGS += --build-dir="$(BUILDDIR:/=)"
|
override DATAFLAGS += --build-dir="$(BUILDDIR:/=)"
|
||||||
override STACKFLAGS += --build-dir="$(BUILDDIR:/=)"
|
override STACKFLAGS += --build-dir="$(BUILDDIR:/=)"
|
||||||
override STRUCTSFLAGS += --build-dir="$(BUILDDIR:/=)"
|
override STRUCTFLAGS += --build-dir="$(BUILDDIR:/=)"
|
||||||
override COVERAGEFLAGS += --build-dir="$(BUILDDIR:/=)"
|
override COVERAGEFLAGS += --build-dir="$(BUILDDIR:/=)"
|
||||||
endif
|
endif
|
||||||
ifneq ($(NM),nm)
|
ifneq ($(NM),nm)
|
||||||
@@ -88,7 +87,7 @@ override CODEFLAGS += --nm-tool="$(NM)"
|
|||||||
override DATAFLAGS += --nm-tool="$(NM)"
|
override DATAFLAGS += --nm-tool="$(NM)"
|
||||||
endif
|
endif
|
||||||
ifneq ($(OBJDUMP),objdump)
|
ifneq ($(OBJDUMP),objdump)
|
||||||
override STRUCTSFLAGS += --objdump-tool="$(OBJDUMP)"
|
override STRUCTFLAGS += --objdump-tool="$(OBJDUMP)"
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
@@ -132,17 +131,22 @@ data: $(OBJ)
|
|||||||
stack: $(CI)
|
stack: $(CI)
|
||||||
./scripts/stack.py $^ -S $(STACKFLAGS)
|
./scripts/stack.py $^ -S $(STACKFLAGS)
|
||||||
|
|
||||||
.PHONY: structs
|
.PHONY: struct
|
||||||
structs: $(OBJ)
|
struct: $(OBJ)
|
||||||
./scripts/structs.py $^ -S $(STRUCTSFLAGS)
|
./scripts/struct.py $^ -S $(STRUCTFLAGS)
|
||||||
|
|
||||||
.PHONY: coverage
|
.PHONY: coverage
|
||||||
coverage: $(GCDA)
|
coverage: $(GCDA)
|
||||||
./scripts/coverage.py $^ -s $(COVERAGEFLAGS)
|
./scripts/coverage.py $^ -s $(COVERAGEFLAGS)
|
||||||
|
|
||||||
.PHONY: summary
|
.PHONY: summary sizes
|
||||||
summary: $(BUILDDIR)lfs.csv
|
summary sizes: $(BUILDDIR)lfs.csv
|
||||||
./scripts/summary.py -Y $^ $(SUMMARYFLAGS)
|
$(strip ./scripts/summary.py -Y $^ \
|
||||||
|
-f code=code_size,$\
|
||||||
|
data=data_size,$\
|
||||||
|
stack=stack_limit,$\
|
||||||
|
struct=struct_size \
|
||||||
|
$(SUMMARYFLAGS))
|
||||||
|
|
||||||
|
|
||||||
# rules
|
# rules
|
||||||
@@ -157,11 +161,27 @@ $(BUILDDIR)lfs: $(OBJ)
|
|||||||
$(BUILDDIR)lfs.a: $(OBJ)
|
$(BUILDDIR)lfs.a: $(OBJ)
|
||||||
$(AR) rcs $@ $^
|
$(AR) rcs $@ $^
|
||||||
|
|
||||||
$(BUILDDIR)lfs.csv: $(OBJ) $(CI)
|
$(BUILDDIR)lfs.code.csv: $(OBJ)
|
||||||
./scripts/code.py $(OBJ) -q $(CODEFLAGS) -o $@
|
./scripts/code.py $^ -q $(CODEFLAGS) -o $@
|
||||||
./scripts/data.py $(OBJ) -q -m $@ $(DATAFLAGS) -o $@
|
|
||||||
./scripts/stack.py $(CI) -q -m $@ $(STACKFLAGS) -o $@
|
$(BUILDDIR)lfs.data.csv: $(OBJ)
|
||||||
./scripts/structs.py $(OBJ) -q -m $@ $(STRUCTSFLAGS) -o $@
|
./scripts/data.py $^ -q $(CODEFLAGS) -o $@
|
||||||
|
|
||||||
|
$(BUILDDIR)lfs.stack.csv: $(CI)
|
||||||
|
./scripts/stack.py $^ -q $(CODEFLAGS) -o $@
|
||||||
|
|
||||||
|
$(BUILDDIR)lfs.struct.csv: $(OBJ)
|
||||||
|
./scripts/struct.py $^ -q $(CODEFLAGS) -o $@
|
||||||
|
|
||||||
|
$(BUILDDIR)lfs.coverage.csv: $(GCDA)
|
||||||
|
./scripts/coverage.py $^ -q $(COVERAGEFLAGS) -o $@
|
||||||
|
|
||||||
|
$(BUILDDIR)lfs.csv: \
|
||||||
|
$(BUILDDIR)lfs.code.csv \
|
||||||
|
$(BUILDDIR)lfs.data.csv \
|
||||||
|
$(BUILDDIR)lfs.stack.csv \
|
||||||
|
$(BUILDDIR)lfs.struct.csv
|
||||||
|
./scripts/summary.py $^ -q $(SUMMARYFLAGS) -o $@
|
||||||
|
|
||||||
$(BUILDDIR)runners/test_runner: $(TEST_OBJ)
|
$(BUILDDIR)runners/test_runner: $(TEST_OBJ)
|
||||||
$(CC) $(CFLAGS) $^ $(LFLAGS) -o $@
|
$(CC) $(CFLAGS) $^ $(LFLAGS) -o $@
|
||||||
@@ -191,7 +211,13 @@ $(BUILDDIR)%.t.c: %.c $(TESTS)
|
|||||||
clean:
|
clean:
|
||||||
rm -f $(BUILDDIR)lfs
|
rm -f $(BUILDDIR)lfs
|
||||||
rm -f $(BUILDDIR)lfs.a
|
rm -f $(BUILDDIR)lfs.a
|
||||||
rm -f $(BUILDDIR)lfs.csv
|
$(strip rm -f \
|
||||||
|
$(BUILDDIR)lfs.csv \
|
||||||
|
$(BUILDDIR)lfs.code.csv \
|
||||||
|
$(BUILDDIR)lfs.data.csv \
|
||||||
|
$(BUILDDIR)lfs.stack.csv \
|
||||||
|
$(BUILDDIR)lfs.struct.csv \
|
||||||
|
$(BUILDDIR)lfs.coverage.csv)
|
||||||
rm -f $(BUILDDIR)runners/test_runner
|
rm -f $(BUILDDIR)runners/test_runner
|
||||||
rm -f $(OBJ)
|
rm -f $(OBJ)
|
||||||
rm -f $(DEP)
|
rm -f $(DEP)
|
||||||
|
|||||||
+375
-196
@@ -5,71 +5,123 @@
|
|||||||
# by Linux's Bloat-O-Meter.
|
# by Linux's Bloat-O-Meter.
|
||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import collections as co
|
||||||
|
import csv
|
||||||
import glob
|
import glob
|
||||||
import itertools as it
|
import itertools as it
|
||||||
import subprocess as sp
|
import math as m
|
||||||
import shlex
|
import os
|
||||||
import re
|
import re
|
||||||
import csv
|
import shlex
|
||||||
import collections as co
|
import subprocess as sp
|
||||||
|
|
||||||
|
|
||||||
OBJ_PATHS = ['*.o']
|
OBJ_PATHS = ['*.o']
|
||||||
|
NM_TOOL = ['nm']
|
||||||
|
TYPE = 'tTrRdD'
|
||||||
|
|
||||||
class CodeResult(co.namedtuple('CodeResult', 'code_size')):
|
|
||||||
|
# integer fields
|
||||||
|
class IntField(co.namedtuple('IntField', 'x')):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
def __new__(cls, code_size=0):
|
def __new__(cls, x):
|
||||||
return super().__new__(cls, int(code_size))
|
if isinstance(x, IntField):
|
||||||
|
return x
|
||||||
|
if isinstance(x, str):
|
||||||
|
try:
|
||||||
|
x = int(x, 0)
|
||||||
|
except ValueError:
|
||||||
|
# also accept +-∞ and +-inf
|
||||||
|
if re.match('^\s*\+?\s*(?:∞|inf)\s*$', x):
|
||||||
|
x = float('inf')
|
||||||
|
elif re.match('^\s*-\s*(?:∞|inf)\s*$', x):
|
||||||
|
x = float('-inf')
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
return super().__new__(cls, x)
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
assert not m.isinf(self.x)
|
||||||
|
return self.x
|
||||||
|
|
||||||
|
def __float__(self):
|
||||||
|
return float(self.x)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.x == float('inf'):
|
||||||
|
return '∞'
|
||||||
|
elif self.x == float('-inf'):
|
||||||
|
return '-∞'
|
||||||
|
else:
|
||||||
|
return str(self.x)
|
||||||
|
|
||||||
|
none = '%7s' % '-'
|
||||||
|
def table(self):
|
||||||
|
return '%7s' % (self,)
|
||||||
|
|
||||||
|
diff_none = '%7s' % '-'
|
||||||
|
diff_table = table
|
||||||
|
|
||||||
|
def diff_diff(self, other):
|
||||||
|
new = self.x if self else 0
|
||||||
|
old = other.x if other else 0
|
||||||
|
diff = new - old
|
||||||
|
if diff == float('+inf'):
|
||||||
|
return '%7s' % '+∞'
|
||||||
|
elif diff == float('-inf'):
|
||||||
|
return '%7s' % '-∞'
|
||||||
|
else:
|
||||||
|
return '%+7d' % diff
|
||||||
|
|
||||||
|
def ratio(self, other):
|
||||||
|
new = self.x if self else 0
|
||||||
|
old = other.x if other else 0
|
||||||
|
if m.isinf(new) and m.isinf(old):
|
||||||
|
return 0.0
|
||||||
|
elif m.isinf(new):
|
||||||
|
return float('+inf')
|
||||||
|
elif m.isinf(old):
|
||||||
|
return float('-inf')
|
||||||
|
elif not old and not new:
|
||||||
|
return 0.0
|
||||||
|
elif not old:
|
||||||
|
return 1.0
|
||||||
|
else:
|
||||||
|
return (new-old) / old
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
return self.__class__(self.code_size + other.code_size)
|
return IntField(self.x + other.x)
|
||||||
|
|
||||||
def __sub__(self, other):
|
def __mul__(self, other):
|
||||||
return CodeDiff(other, self)
|
return IntField(self.x * other.x)
|
||||||
|
|
||||||
def __rsub__(self, other):
|
def __lt__(self, other):
|
||||||
return self.__class__.__sub__(other, self)
|
return self.x < other.x
|
||||||
|
|
||||||
def key(self, **args):
|
def __gt__(self, other):
|
||||||
if args.get('size_sort'):
|
return self.__class__.__lt__(other, self)
|
||||||
return -self.code_size
|
|
||||||
elif args.get('reverse_size_sort'):
|
def __le__(self, other):
|
||||||
return +self.code_size
|
return not self.__gt__(other)
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return not self.__lt__(other)
|
||||||
|
|
||||||
|
def __truediv__(self, n):
|
||||||
|
if m.isinf(self.x):
|
||||||
|
return self
|
||||||
else:
|
else:
|
||||||
return None
|
return IntField(round(self.x / n))
|
||||||
|
|
||||||
_header = '%7s' % 'size'
|
# code size results
|
||||||
def __str__(self):
|
class CodeResult(co.namedtuple('CodeResult', 'file,function,code_size')):
|
||||||
return '%7d' % self.code_size
|
|
||||||
|
|
||||||
class CodeDiff(co.namedtuple('CodeDiff', 'old,new')):
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
def __new__(cls, file, function, code_size):
|
||||||
|
return super().__new__(cls, file, function, IntField(code_size))
|
||||||
|
|
||||||
def ratio(self):
|
def __add__(self, other):
|
||||||
old = self.old.code_size if self.old is not None else 0
|
return CodeResult(self.file, self.function,
|
||||||
new = self.new.code_size if self.new is not None else 0
|
self.code_size + other.code_size)
|
||||||
return (new-old) / old if old else 1.0
|
|
||||||
|
|
||||||
def key(self, **args):
|
|
||||||
return (
|
|
||||||
self.new.key(**args) if self.new is not None else 0,
|
|
||||||
-self.ratio())
|
|
||||||
|
|
||||||
def __bool__(self):
|
|
||||||
return bool(self.ratio())
|
|
||||||
|
|
||||||
_header = '%7s %7s %7s' % ('old', 'new', 'diff')
|
|
||||||
def __str__(self):
|
|
||||||
old = self.old.code_size if self.old is not None else 0
|
|
||||||
new = self.new.code_size if self.new is not None else 0
|
|
||||||
diff = new - old
|
|
||||||
ratio = self.ratio()
|
|
||||||
return '%7s %7s %+7d%s' % (
|
|
||||||
old or "-",
|
|
||||||
new or "-",
|
|
||||||
diff,
|
|
||||||
' (%+.1f%%)' % (100*ratio) if ratio else '')
|
|
||||||
|
|
||||||
|
|
||||||
def openio(path, mode='r'):
|
def openio(path, mode='r'):
|
||||||
@@ -81,20 +133,25 @@ def openio(path, mode='r'):
|
|||||||
else:
|
else:
|
||||||
return open(path, mode)
|
return open(path, mode)
|
||||||
|
|
||||||
def collect(paths, **args):
|
def collect(paths, *,
|
||||||
results = co.defaultdict(lambda: CodeResult())
|
nm_tool=NM_TOOL,
|
||||||
|
type=TYPE,
|
||||||
|
build_dir=None,
|
||||||
|
everything=False,
|
||||||
|
**args):
|
||||||
|
results = []
|
||||||
pattern = re.compile(
|
pattern = re.compile(
|
||||||
'^(?P<size>[0-9a-fA-F]+)' +
|
'^(?P<size>[0-9a-fA-F]+)' +
|
||||||
' (?P<type>[%s])' % re.escape(args['type']) +
|
' (?P<type>[%s])' % re.escape(type) +
|
||||||
' (?P<func>.+?)$')
|
' (?P<func>.+?)$')
|
||||||
for path in paths:
|
for path in paths:
|
||||||
# map to source file
|
# map to source file
|
||||||
src_path = re.sub('\.o$', '.c', path)
|
src_path = re.sub('\.o$', '.c', path)
|
||||||
if args.get('build_dir'):
|
if build_dir:
|
||||||
src_path = re.sub('%s/*' % re.escape(args['build_dir']), '',
|
src_path = re.sub('%s/*' % re.escape(build_dir), '',
|
||||||
src_path)
|
src_path)
|
||||||
# note nm-tool may contain extra args
|
# note nm-tool may contain extra args
|
||||||
cmd = args['nm_tool'] + ['--size-sort', path]
|
cmd = nm_tool + ['--size-sort', 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,
|
||||||
@@ -107,12 +164,15 @@ def collect(paths, **args):
|
|||||||
if m:
|
if m:
|
||||||
func = m.group('func')
|
func = m.group('func')
|
||||||
# discard internal functions
|
# discard internal functions
|
||||||
if not args.get('everything') and func.startswith('__'):
|
if not everything and func.startswith('__'):
|
||||||
continue
|
continue
|
||||||
# discard .8449 suffixes created by optimizer
|
# discard .8449 suffixes created by optimizer
|
||||||
func = re.sub('\.[0-9]+', '', func)
|
func = re.sub('\.[0-9]+', '', func)
|
||||||
results[(src_path, func)] += CodeResult(
|
|
||||||
int(m.group('size'), 16))
|
results.append(CodeResult(
|
||||||
|
src_path, func,
|
||||||
|
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'):
|
||||||
@@ -122,12 +182,167 @@ def collect(paths, **args):
|
|||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def main(**args):
|
|
||||||
|
def fold(results, *,
|
||||||
|
by=['file', 'function'],
|
||||||
|
**_):
|
||||||
|
folding = co.OrderedDict()
|
||||||
|
for r in results:
|
||||||
|
name = tuple(getattr(r, k) for k in by)
|
||||||
|
if name not in folding:
|
||||||
|
folding[name] = []
|
||||||
|
folding[name].append(r)
|
||||||
|
|
||||||
|
folded = []
|
||||||
|
for rs in folding.values():
|
||||||
|
folded.append(sum(rs[1:], start=rs[0]))
|
||||||
|
|
||||||
|
return folded
|
||||||
|
|
||||||
|
|
||||||
|
def table(results, diff_results=None, *,
|
||||||
|
by_file=False,
|
||||||
|
size_sort=False,
|
||||||
|
reverse_size_sort=False,
|
||||||
|
summary=False,
|
||||||
|
all=False,
|
||||||
|
percent=False,
|
||||||
|
**_):
|
||||||
|
all_, all = all, __builtins__.all
|
||||||
|
|
||||||
|
# fold
|
||||||
|
results = fold(results, by=['file' if by_file else 'function'])
|
||||||
|
if diff_results is not None:
|
||||||
|
diff_results = fold(diff_results,
|
||||||
|
by=['file' if by_file else 'function'])
|
||||||
|
|
||||||
|
table = {
|
||||||
|
r.file if by_file else r.function: r
|
||||||
|
for r in results}
|
||||||
|
diff_table = {
|
||||||
|
r.file if by_file else r.function: r
|
||||||
|
for r in diff_results or []}
|
||||||
|
|
||||||
|
# sort, note that python's sort is stable
|
||||||
|
names = list(table.keys() | diff_table.keys())
|
||||||
|
names.sort()
|
||||||
|
if diff_results is not None:
|
||||||
|
names.sort(key=lambda n: -IntField.ratio(
|
||||||
|
table[n].code_size if n in table else None,
|
||||||
|
diff_table[n].code_size if n in diff_table else None))
|
||||||
|
if size_sort:
|
||||||
|
names.sort(key=lambda n: (table[n].code_size,) if n in table else (),
|
||||||
|
reverse=True)
|
||||||
|
elif reverse_size_sort:
|
||||||
|
names.sort(key=lambda n: (table[n].code_size,) if n in table else (),
|
||||||
|
reverse=False)
|
||||||
|
|
||||||
|
# print header
|
||||||
|
print('%-36s' % ('%s%s' % (
|
||||||
|
'file' if by_file else 'function',
|
||||||
|
' (%d added, %d removed)' % (
|
||||||
|
sum(1 for n in table if n not in diff_table),
|
||||||
|
sum(1 for n in diff_table if n not in table))
|
||||||
|
if diff_results is not None and not percent else '')
|
||||||
|
if not summary else ''),
|
||||||
|
end='')
|
||||||
|
if diff_results is None:
|
||||||
|
print(' %s' % ('size'.rjust(len(IntField.none))))
|
||||||
|
elif percent:
|
||||||
|
print(' %s' % ('size'.rjust(len(IntField.diff_none))))
|
||||||
|
else:
|
||||||
|
print(' %s %s %s' % (
|
||||||
|
'old'.rjust(len(IntField.diff_none)),
|
||||||
|
'new'.rjust(len(IntField.diff_none)),
|
||||||
|
'diff'.rjust(len(IntField.diff_none))))
|
||||||
|
|
||||||
|
# print entries
|
||||||
|
if not summary:
|
||||||
|
for name in names:
|
||||||
|
r = table.get(name)
|
||||||
|
if diff_results is not None:
|
||||||
|
diff_r = diff_table.get(name)
|
||||||
|
ratio = IntField.ratio(
|
||||||
|
r.code_size if r else None,
|
||||||
|
diff_r.code_size if diff_r else None)
|
||||||
|
if not ratio and not all_:
|
||||||
|
continue
|
||||||
|
|
||||||
|
print('%-36s' % name, end='')
|
||||||
|
if diff_results is None:
|
||||||
|
print(' %s' % (
|
||||||
|
r.code_size.table()
|
||||||
|
if r else IntField.none))
|
||||||
|
elif percent:
|
||||||
|
print(' %s%s' % (
|
||||||
|
r.code_size.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
' (%s)' % (
|
||||||
|
'+∞%' if ratio == float('+inf')
|
||||||
|
else '-∞%' if ratio == float('-inf')
|
||||||
|
else '%+.1f%%' % (100*ratio))))
|
||||||
|
else:
|
||||||
|
print(' %s %s %s%s' % (
|
||||||
|
diff_r.code_size.diff_table()
|
||||||
|
if diff_r else IntField.diff_none,
|
||||||
|
r.code_size.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
IntField.diff_diff(
|
||||||
|
r.code_size if r else None,
|
||||||
|
diff_r.code_size if diff_r else None)
|
||||||
|
if r or diff_r else IntField.diff_none,
|
||||||
|
' (%s)' % (
|
||||||
|
'+∞%' if ratio == float('+inf')
|
||||||
|
else '-∞%' if ratio == float('-inf')
|
||||||
|
else '%+.1f%%' % (100*ratio))
|
||||||
|
if ratio else ''))
|
||||||
|
|
||||||
|
# print total
|
||||||
|
total = fold(results, by=[])
|
||||||
|
r = total[0] if total else None
|
||||||
|
if diff_results is not None:
|
||||||
|
diff_total = fold(diff_results, by=[])
|
||||||
|
diff_r = diff_total[0] if diff_total else None
|
||||||
|
ratio = IntField.ratio(
|
||||||
|
r.code_size if r else None,
|
||||||
|
diff_r.code_size if diff_r else None)
|
||||||
|
|
||||||
|
print('%-36s' % 'TOTAL', end='')
|
||||||
|
if diff_results is None:
|
||||||
|
print(' %s' % (
|
||||||
|
r.code_size.table()
|
||||||
|
if r else IntField.none))
|
||||||
|
elif percent:
|
||||||
|
print(' %s%s' % (
|
||||||
|
r.code_size.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
' (%s)' % (
|
||||||
|
'+∞%' if ratio == float('+inf')
|
||||||
|
else '-∞%' if ratio == float('-inf')
|
||||||
|
else '%+.1f%%' % (100*ratio))))
|
||||||
|
else:
|
||||||
|
print(' %s %s %s%s' % (
|
||||||
|
diff_r.code_size.diff_table()
|
||||||
|
if diff_r else IntField.diff_none,
|
||||||
|
r.code_size.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
IntField.diff_diff(
|
||||||
|
r.code_size if r else None,
|
||||||
|
diff_r.code_size if diff_r else None)
|
||||||
|
if r or diff_r else IntField.diff_none,
|
||||||
|
' (%s)' % (
|
||||||
|
'+∞%' if ratio == float('+inf')
|
||||||
|
else '-∞%' if ratio == float('-inf')
|
||||||
|
else '%+.1f%%' % (100*ratio))
|
||||||
|
if ratio else ''))
|
||||||
|
|
||||||
|
|
||||||
|
def main(obj_paths, **args):
|
||||||
# find sizes
|
# find sizes
|
||||||
if not args.get('use', None):
|
if not args.get('use', None):
|
||||||
# find .o files
|
# find .o files
|
||||||
paths = []
|
paths = []
|
||||||
for path in args['obj_paths']:
|
for path in obj_paths:
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
path = path + '/*.o'
|
path = path + '/*.o'
|
||||||
|
|
||||||
@@ -135,127 +350,61 @@ def main(**args):
|
|||||||
paths.append(path)
|
paths.append(path)
|
||||||
|
|
||||||
if not paths:
|
if not paths:
|
||||||
print('no .obj files found in %r?' % args['obj_paths'])
|
print('no .obj files found in %r?' % obj_paths)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
results = collect(paths, **args)
|
results = collect(paths, **args)
|
||||||
else:
|
else:
|
||||||
|
results = []
|
||||||
with openio(args['use']) as f:
|
with openio(args['use']) as f:
|
||||||
r = csv.DictReader(f)
|
reader = csv.DictReader(f)
|
||||||
results = {
|
for r in reader:
|
||||||
(result['file'], result['name']): CodeResult(
|
try:
|
||||||
*(result[f] for f in CodeResult._fields))
|
results.append(CodeResult(**{
|
||||||
for result in r
|
k: v for k, v in r.items()
|
||||||
if all(result.get(f) not in {None, ''}
|
if k in CodeResult._fields}))
|
||||||
for f in CodeResult._fields)}
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
# find previous results?
|
# fold to remove duplicates
|
||||||
if args.get('diff'):
|
results = fold(results)
|
||||||
try:
|
|
||||||
with openio(args['diff']) as f:
|
# sort because why not
|
||||||
r = csv.DictReader(f)
|
results.sort()
|
||||||
prev_results = {
|
|
||||||
(result['file'], result['name']): CodeResult(
|
|
||||||
*(result[f] for f in CodeResult._fields))
|
|
||||||
for result in r
|
|
||||||
if all(result.get(f) not in {None, ''}
|
|
||||||
for f in CodeResult._fields)}
|
|
||||||
except FileNotFoundError:
|
|
||||||
prev_results = []
|
|
||||||
|
|
||||||
# write results to CSV
|
# write results to CSV
|
||||||
if args.get('output'):
|
if args.get('output'):
|
||||||
merged_results = co.defaultdict(lambda: {})
|
|
||||||
other_fields = []
|
|
||||||
|
|
||||||
# merge?
|
|
||||||
if args.get('merge'):
|
|
||||||
try:
|
|
||||||
with openio(args['merge']) as f:
|
|
||||||
r = csv.DictReader(f)
|
|
||||||
for result in r:
|
|
||||||
file = result.pop('file', '')
|
|
||||||
func = result.pop('name', '')
|
|
||||||
for f in CodeResult._fields:
|
|
||||||
result.pop(f, None)
|
|
||||||
merged_results[(file, func)] = result
|
|
||||||
other_fields = result.keys()
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for (file, func), result in results.items():
|
|
||||||
merged_results[(file, func)] |= result._asdict()
|
|
||||||
|
|
||||||
with openio(args['output'], 'w') as f:
|
with openio(args['output'], 'w') as f:
|
||||||
w = csv.DictWriter(f, ['file', 'name',
|
writer = csv.DictWriter(f, CodeResult._fields)
|
||||||
*other_fields, *CodeResult._fields])
|
writer.writeheader()
|
||||||
w.writeheader()
|
for r in results:
|
||||||
for (file, func), result in sorted(merged_results.items()):
|
writer.writerow(r._asdict())
|
||||||
w.writerow({'file': file, 'name': func, **result})
|
|
||||||
|
|
||||||
# print results
|
# find previous results?
|
||||||
def print_header(by):
|
if args.get('diff'):
|
||||||
if by == 'total':
|
diff_results = []
|
||||||
entry = lambda k: 'TOTAL'
|
try:
|
||||||
elif by == 'file':
|
with openio(args['diff']) as f:
|
||||||
entry = lambda k: k[0]
|
reader = csv.DictReader(f)
|
||||||
else:
|
for r in reader:
|
||||||
entry = lambda k: k[1]
|
try:
|
||||||
|
diff_results.append(CodeResult(**{
|
||||||
|
k: v for k, v in r.items()
|
||||||
|
if k in CodeResult._fields}))
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
if not args.get('diff'):
|
# fold to remove duplicates
|
||||||
print('%-36s %s' % (by, CodeResult._header))
|
diff_results = fold(diff_results)
|
||||||
else:
|
|
||||||
old = {entry(k) for k in results.keys()}
|
|
||||||
new = {entry(k) for k in prev_results.keys()}
|
|
||||||
print('%-36s %s' % (
|
|
||||||
'%s (%d added, %d removed)' % (by,
|
|
||||||
sum(1 for k in new if k not in old),
|
|
||||||
sum(1 for k in old if k not in new))
|
|
||||||
if by else '',
|
|
||||||
CodeDiff._header))
|
|
||||||
|
|
||||||
def print_entries(by):
|
# print table
|
||||||
if by == 'total':
|
if not args.get('quiet'):
|
||||||
entry = lambda k: 'TOTAL'
|
table(
|
||||||
elif by == 'file':
|
results,
|
||||||
entry = lambda k: k[0]
|
diff_results if args.get('diff') else None,
|
||||||
else:
|
**args)
|
||||||
entry = lambda k: k[1]
|
|
||||||
|
|
||||||
entries = co.defaultdict(lambda: CodeResult())
|
|
||||||
for k, result in results.items():
|
|
||||||
entries[entry(k)] += result
|
|
||||||
|
|
||||||
if not args.get('diff'):
|
|
||||||
for name, result in sorted(entries.items(),
|
|
||||||
key=lambda p: (p[1].key(**args), p)):
|
|
||||||
print('%-36s %s' % (name, result))
|
|
||||||
else:
|
|
||||||
prev_entries = co.defaultdict(lambda: CodeResult())
|
|
||||||
for k, result in prev_results.items():
|
|
||||||
prev_entries[entry(k)] += result
|
|
||||||
|
|
||||||
diff_entries = {name: entries.get(name) - prev_entries.get(name)
|
|
||||||
for name in (entries.keys() | prev_entries.keys())}
|
|
||||||
|
|
||||||
for name, diff in sorted(diff_entries.items(),
|
|
||||||
key=lambda p: (p[1].key(**args), p)):
|
|
||||||
if diff or args.get('all'):
|
|
||||||
print('%-36s %s' % (name, diff))
|
|
||||||
|
|
||||||
if args.get('quiet'):
|
|
||||||
pass
|
|
||||||
elif args.get('summary'):
|
|
||||||
print_header('')
|
|
||||||
print_entries('total')
|
|
||||||
elif args.get('files'):
|
|
||||||
print_header('file')
|
|
||||||
print_entries('file')
|
|
||||||
print_entries('total')
|
|
||||||
else:
|
|
||||||
print_header('function')
|
|
||||||
print_entries('function')
|
|
||||||
print_entries('total')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -263,42 +412,72 @@ if __name__ == "__main__":
|
|||||||
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.")
|
||||||
parser.add_argument('obj_paths', nargs='*', default=OBJ_PATHS,
|
parser.add_argument(
|
||||||
help="Description of where to find *.o files. May be a directory \
|
'obj_paths',
|
||||||
or a list of paths. Defaults to %r." % OBJ_PATHS)
|
nargs='*',
|
||||||
parser.add_argument('-v', '--verbose', action='store_true',
|
default=OBJ_PATHS,
|
||||||
|
help="Description of where to find *.o files. May be a directory "
|
||||||
|
"or a list of paths. Defaults to %(default)r.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-v', '--verbose',
|
||||||
|
action='store_true',
|
||||||
help="Output commands that run behind the scenes.")
|
help="Output commands that run behind the scenes.")
|
||||||
parser.add_argument('-q', '--quiet', action='store_true',
|
parser.add_argument(
|
||||||
|
'-q', '--quiet',
|
||||||
|
action='store_true',
|
||||||
help="Don't show anything, useful with -o.")
|
help="Don't show anything, useful with -o.")
|
||||||
parser.add_argument('-o', '--output',
|
parser.add_argument(
|
||||||
|
'-o', '--output',
|
||||||
help="Specify CSV file to store results.")
|
help="Specify CSV file to store results.")
|
||||||
parser.add_argument('-u', '--use',
|
parser.add_argument(
|
||||||
help="Don't compile and find code sizes, instead use this CSV file.")
|
'-u', '--use',
|
||||||
parser.add_argument('-d', '--diff',
|
help="Don't parse anything, use this CSV file.")
|
||||||
help="Specify CSV file to diff code size against.")
|
parser.add_argument(
|
||||||
parser.add_argument('-m', '--merge',
|
'-d', '--diff',
|
||||||
help="Merge with an existing CSV file when writing to output.")
|
help="Specify CSV file to diff against.")
|
||||||
parser.add_argument('-a', '--all', action='store_true',
|
parser.add_argument(
|
||||||
help="Show all functions, not just the ones that changed.")
|
'-a', '--all',
|
||||||
parser.add_argument('-A', '--everything', action='store_true',
|
action='store_true',
|
||||||
help="Include builtin and libc specific symbols.")
|
help="Show all, not just the ones that changed.")
|
||||||
parser.add_argument('-s', '--size-sort', action='store_true',
|
parser.add_argument(
|
||||||
|
'-p', '--percent',
|
||||||
|
action='store_true',
|
||||||
|
help="Only show percentage change, not a full diff.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-b', '--by-file',
|
||||||
|
action='store_true',
|
||||||
|
help="Group by file. Note this does not include padding "
|
||||||
|
"so sizes may differ from other tools.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-s', '--size-sort',
|
||||||
|
action='store_true',
|
||||||
help="Sort by size.")
|
help="Sort by size.")
|
||||||
parser.add_argument('-S', '--reverse-size-sort', action='store_true',
|
parser.add_argument(
|
||||||
|
'-S', '--reverse-size-sort',
|
||||||
|
action='store_true',
|
||||||
help="Sort by size, but backwards.")
|
help="Sort by size, but backwards.")
|
||||||
parser.add_argument('-F', '--files', action='store_true',
|
parser.add_argument(
|
||||||
help="Show file-level code sizes. Note this does not include padding! "
|
'-Y', '--summary',
|
||||||
"So sizes may differ from other tools.")
|
action='store_true',
|
||||||
parser.add_argument('-Y', '--summary', action='store_true',
|
help="Only show the total size.")
|
||||||
help="Only show the total code size.")
|
parser.add_argument(
|
||||||
parser.add_argument('--type', default='tTrRdD',
|
'-A', '--everything',
|
||||||
|
action='store_true',
|
||||||
|
help="Include builtin and libc specific symbols.")
|
||||||
|
parser.add_argument(
|
||||||
|
'--type',
|
||||||
|
default=TYPE,
|
||||||
help="Type of symbols to report, this uses the same single-character "
|
help="Type of symbols to report, this uses the same single-character "
|
||||||
"type-names emitted by nm. Defaults to %(default)r.")
|
"type-names emitted by nm. Defaults to %(default)r.")
|
||||||
parser.add_argument('--nm-tool', default=['nm'], type=lambda x: x.split(),
|
parser.add_argument(
|
||||||
help="Path to the nm tool to use.")
|
'--nm-tool',
|
||||||
parser.add_argument('--build-dir',
|
type=lambda x: x.split(),
|
||||||
help="Specify the relative build directory. Used to map object files \
|
default=NM_TOOL,
|
||||||
to the correct source files.")
|
help="Path to the nm tool to use. Defaults to %(default)r")
|
||||||
|
parser.add_argument(
|
||||||
|
'--build-dir',
|
||||||
|
help="Specify the relative build directory. Used to map object files "
|
||||||
|
"to the correct source files.")
|
||||||
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}))
|
||||||
|
|||||||
+605
-336
File diff suppressed because it is too large
Load Diff
+375
-196
@@ -5,71 +5,123 @@
|
|||||||
# by Linux's Bloat-O-Meter.
|
# by Linux's Bloat-O-Meter.
|
||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import collections as co
|
||||||
|
import csv
|
||||||
import glob
|
import glob
|
||||||
import itertools as it
|
import itertools as it
|
||||||
import subprocess as sp
|
import math as m
|
||||||
import shlex
|
import os
|
||||||
import re
|
import re
|
||||||
import csv
|
import shlex
|
||||||
import collections as co
|
import subprocess as sp
|
||||||
|
|
||||||
|
|
||||||
OBJ_PATHS = ['*.o']
|
OBJ_PATHS = ['*.o']
|
||||||
|
NM_TOOL = ['nm']
|
||||||
|
TYPE = 'dDbB'
|
||||||
|
|
||||||
class DataResult(co.namedtuple('DataResult', 'data_size')):
|
|
||||||
|
# integer fields
|
||||||
|
class IntField(co.namedtuple('IntField', 'x')):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
def __new__(cls, data_size=0):
|
def __new__(cls, x):
|
||||||
return super().__new__(cls, int(data_size))
|
if isinstance(x, IntField):
|
||||||
|
return x
|
||||||
|
if isinstance(x, str):
|
||||||
|
try:
|
||||||
|
x = int(x, 0)
|
||||||
|
except ValueError:
|
||||||
|
# also accept +-∞ and +-inf
|
||||||
|
if re.match('^\s*\+?\s*(?:∞|inf)\s*$', x):
|
||||||
|
x = float('inf')
|
||||||
|
elif re.match('^\s*-\s*(?:∞|inf)\s*$', x):
|
||||||
|
x = float('-inf')
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
return super().__new__(cls, x)
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
assert not m.isinf(self.x)
|
||||||
|
return self.x
|
||||||
|
|
||||||
|
def __float__(self):
|
||||||
|
return float(self.x)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.x == float('inf'):
|
||||||
|
return '∞'
|
||||||
|
elif self.x == float('-inf'):
|
||||||
|
return '-∞'
|
||||||
|
else:
|
||||||
|
return str(self.x)
|
||||||
|
|
||||||
|
none = '%7s' % '-'
|
||||||
|
def table(self):
|
||||||
|
return '%7s' % (self,)
|
||||||
|
|
||||||
|
diff_none = '%7s' % '-'
|
||||||
|
diff_table = table
|
||||||
|
|
||||||
|
def diff_diff(self, other):
|
||||||
|
new = self.x if self else 0
|
||||||
|
old = other.x if other else 0
|
||||||
|
diff = new - old
|
||||||
|
if diff == float('+inf'):
|
||||||
|
return '%7s' % '+∞'
|
||||||
|
elif diff == float('-inf'):
|
||||||
|
return '%7s' % '-∞'
|
||||||
|
else:
|
||||||
|
return '%+7d' % diff
|
||||||
|
|
||||||
|
def ratio(self, other):
|
||||||
|
new = self.x if self else 0
|
||||||
|
old = other.x if other else 0
|
||||||
|
if m.isinf(new) and m.isinf(old):
|
||||||
|
return 0.0
|
||||||
|
elif m.isinf(new):
|
||||||
|
return float('+inf')
|
||||||
|
elif m.isinf(old):
|
||||||
|
return float('-inf')
|
||||||
|
elif not old and not new:
|
||||||
|
return 0.0
|
||||||
|
elif not old:
|
||||||
|
return 1.0
|
||||||
|
else:
|
||||||
|
return (new-old) / old
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
return self.__class__(self.data_size + other.data_size)
|
return IntField(self.x + other.x)
|
||||||
|
|
||||||
def __sub__(self, other):
|
def __mul__(self, other):
|
||||||
return DataDiff(other, self)
|
return IntField(self.x * other.x)
|
||||||
|
|
||||||
def __rsub__(self, other):
|
def __lt__(self, other):
|
||||||
return self.__class__.__sub__(other, self)
|
return self.x < other.x
|
||||||
|
|
||||||
def key(self, **args):
|
def __gt__(self, other):
|
||||||
if args.get('size_sort'):
|
return self.__class__.__lt__(other, self)
|
||||||
return -self.data_size
|
|
||||||
elif args.get('reverse_size_sort'):
|
def __le__(self, other):
|
||||||
return +self.data_size
|
return not self.__gt__(other)
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return not self.__lt__(other)
|
||||||
|
|
||||||
|
def __truediv__(self, n):
|
||||||
|
if m.isinf(self.x):
|
||||||
|
return self
|
||||||
else:
|
else:
|
||||||
return None
|
return IntField(round(self.x / n))
|
||||||
|
|
||||||
_header = '%7s' % 'size'
|
# data size results
|
||||||
def __str__(self):
|
class DataResult(co.namedtuple('DataResult', 'file,function,data_size')):
|
||||||
return '%7d' % self.data_size
|
|
||||||
|
|
||||||
class DataDiff(co.namedtuple('DataDiff', 'old,new')):
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
def __new__(cls, file, function, data_size):
|
||||||
|
return super().__new__(cls, file, function, IntField(data_size))
|
||||||
|
|
||||||
def ratio(self):
|
def __add__(self, other):
|
||||||
old = self.old.data_size if self.old is not None else 0
|
return DataResult(self.file, self.function,
|
||||||
new = self.new.data_size if self.new is not None else 0
|
self.data_size + other.data_size)
|
||||||
return (new-old) / old if old else 1.0
|
|
||||||
|
|
||||||
def key(self, **args):
|
|
||||||
return (
|
|
||||||
self.new.key(**args) if self.new is not None else 0,
|
|
||||||
-self.ratio())
|
|
||||||
|
|
||||||
def __bool__(self):
|
|
||||||
return bool(self.ratio())
|
|
||||||
|
|
||||||
_header = '%7s %7s %7s' % ('old', 'new', 'diff')
|
|
||||||
def __str__(self):
|
|
||||||
old = self.old.data_size if self.old is not None else 0
|
|
||||||
new = self.new.data_size if self.new is not None else 0
|
|
||||||
diff = new - old
|
|
||||||
ratio = self.ratio()
|
|
||||||
return '%7s %7s %+7d%s' % (
|
|
||||||
old or "-",
|
|
||||||
new or "-",
|
|
||||||
diff,
|
|
||||||
' (%+.1f%%)' % (100*ratio) if ratio else '')
|
|
||||||
|
|
||||||
|
|
||||||
def openio(path, mode='r'):
|
def openio(path, mode='r'):
|
||||||
@@ -81,20 +133,25 @@ def openio(path, mode='r'):
|
|||||||
else:
|
else:
|
||||||
return open(path, mode)
|
return open(path, mode)
|
||||||
|
|
||||||
def collect(paths, **args):
|
def collect(paths, *,
|
||||||
results = co.defaultdict(lambda: DataResult())
|
nm_tool=NM_TOOL,
|
||||||
|
type=TYPE,
|
||||||
|
build_dir=None,
|
||||||
|
everything=False,
|
||||||
|
**args):
|
||||||
|
results = []
|
||||||
pattern = re.compile(
|
pattern = re.compile(
|
||||||
'^(?P<size>[0-9a-fA-F]+)' +
|
'^(?P<size>[0-9a-fA-F]+)' +
|
||||||
' (?P<type>[%s])' % re.escape(args['type']) +
|
' (?P<type>[%s])' % re.escape(type) +
|
||||||
' (?P<func>.+?)$')
|
' (?P<func>.+?)$')
|
||||||
for path in paths:
|
for path in paths:
|
||||||
# map to source file
|
# map to source file
|
||||||
src_path = re.sub('\.o$', '.c', path)
|
src_path = re.sub('\.o$', '.c', path)
|
||||||
if args.get('build_dir'):
|
if build_dir:
|
||||||
src_path = re.sub('%s/*' % re.escape(args['build_dir']), '',
|
src_path = re.sub('%s/*' % re.escape(build_dir), '',
|
||||||
src_path)
|
src_path)
|
||||||
# note nm-tool may contain extra args
|
# note nm-tool may contain extra args
|
||||||
cmd = args['nm_tool'] + ['--size-sort', path]
|
cmd = nm_tool + ['--size-sort', 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,
|
||||||
@@ -107,12 +164,15 @@ def collect(paths, **args):
|
|||||||
if m:
|
if m:
|
||||||
func = m.group('func')
|
func = m.group('func')
|
||||||
# discard internal functions
|
# discard internal functions
|
||||||
if not args.get('everything') and func.startswith('__'):
|
if not everything and func.startswith('__'):
|
||||||
continue
|
continue
|
||||||
# discard .8449 suffixes created by optimizer
|
# discard .8449 suffixes created by optimizer
|
||||||
func = re.sub('\.[0-9]+', '', func)
|
func = re.sub('\.[0-9]+', '', func)
|
||||||
results[(src_path, func)] += DataResult(
|
|
||||||
int(m.group('size'), 16))
|
results.append(DataResult(
|
||||||
|
src_path, func,
|
||||||
|
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'):
|
||||||
@@ -122,12 +182,167 @@ def collect(paths, **args):
|
|||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def main(**args):
|
|
||||||
|
def fold(results, *,
|
||||||
|
by=['file', 'function'],
|
||||||
|
**_):
|
||||||
|
folding = co.OrderedDict()
|
||||||
|
for r in results:
|
||||||
|
name = tuple(getattr(r, k) for k in by)
|
||||||
|
if name not in folding:
|
||||||
|
folding[name] = []
|
||||||
|
folding[name].append(r)
|
||||||
|
|
||||||
|
folded = []
|
||||||
|
for rs in folding.values():
|
||||||
|
folded.append(sum(rs[1:], start=rs[0]))
|
||||||
|
|
||||||
|
return folded
|
||||||
|
|
||||||
|
|
||||||
|
def table(results, diff_results=None, *,
|
||||||
|
by_file=False,
|
||||||
|
size_sort=False,
|
||||||
|
reverse_size_sort=False,
|
||||||
|
summary=False,
|
||||||
|
all=False,
|
||||||
|
percent=False,
|
||||||
|
**_):
|
||||||
|
all_, all = all, __builtins__.all
|
||||||
|
|
||||||
|
# fold
|
||||||
|
results = fold(results, by=['file' if by_file else 'function'])
|
||||||
|
if diff_results is not None:
|
||||||
|
diff_results = fold(diff_results,
|
||||||
|
by=['file' if by_file else 'function'])
|
||||||
|
|
||||||
|
table = {
|
||||||
|
r.file if by_file else r.function: r
|
||||||
|
for r in results}
|
||||||
|
diff_table = {
|
||||||
|
r.file if by_file else r.function: r
|
||||||
|
for r in diff_results or []}
|
||||||
|
|
||||||
|
# sort, note that python's sort is stable
|
||||||
|
names = list(table.keys() | diff_table.keys())
|
||||||
|
names.sort()
|
||||||
|
if diff_results is not None:
|
||||||
|
names.sort(key=lambda n: -IntField.ratio(
|
||||||
|
table[n].data_size if n in table else None,
|
||||||
|
diff_table[n].data_size if n in diff_table else None))
|
||||||
|
if size_sort:
|
||||||
|
names.sort(key=lambda n: (table[n].data_size,) if n in table else (),
|
||||||
|
reverse=True)
|
||||||
|
elif reverse_size_sort:
|
||||||
|
names.sort(key=lambda n: (table[n].data_size,) if n in table else (),
|
||||||
|
reverse=False)
|
||||||
|
|
||||||
|
# print header
|
||||||
|
print('%-36s' % ('%s%s' % (
|
||||||
|
'file' if by_file else 'function',
|
||||||
|
' (%d added, %d removed)' % (
|
||||||
|
sum(1 for n in table if n not in diff_table),
|
||||||
|
sum(1 for n in diff_table if n not in table))
|
||||||
|
if diff_results is not None and not percent else '')
|
||||||
|
if not summary else ''),
|
||||||
|
end='')
|
||||||
|
if diff_results is None:
|
||||||
|
print(' %s' % ('size'.rjust(len(IntField.none))))
|
||||||
|
elif percent:
|
||||||
|
print(' %s' % ('size'.rjust(len(IntField.diff_none))))
|
||||||
|
else:
|
||||||
|
print(' %s %s %s' % (
|
||||||
|
'old'.rjust(len(IntField.diff_none)),
|
||||||
|
'new'.rjust(len(IntField.diff_none)),
|
||||||
|
'diff'.rjust(len(IntField.diff_none))))
|
||||||
|
|
||||||
|
# print entries
|
||||||
|
if not summary:
|
||||||
|
for name in names:
|
||||||
|
r = table.get(name)
|
||||||
|
if diff_results is not None:
|
||||||
|
diff_r = diff_table.get(name)
|
||||||
|
ratio = IntField.ratio(
|
||||||
|
r.data_size if r else None,
|
||||||
|
diff_r.data_size if diff_r else None)
|
||||||
|
if not ratio and not all_:
|
||||||
|
continue
|
||||||
|
|
||||||
|
print('%-36s' % name, end='')
|
||||||
|
if diff_results is None:
|
||||||
|
print(' %s' % (
|
||||||
|
r.data_size.table()
|
||||||
|
if r else IntField.none))
|
||||||
|
elif percent:
|
||||||
|
print(' %s%s' % (
|
||||||
|
r.data_size.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
' (%s)' % (
|
||||||
|
'+∞%' if ratio == float('+inf')
|
||||||
|
else '-∞%' if ratio == float('-inf')
|
||||||
|
else '%+.1f%%' % (100*ratio))))
|
||||||
|
else:
|
||||||
|
print(' %s %s %s%s' % (
|
||||||
|
diff_r.data_size.diff_table()
|
||||||
|
if diff_r else IntField.diff_none,
|
||||||
|
r.data_size.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
IntField.diff_diff(
|
||||||
|
r.data_size if r else None,
|
||||||
|
diff_r.data_size if diff_r else None)
|
||||||
|
if r or diff_r else IntField.diff_none,
|
||||||
|
' (%s)' % (
|
||||||
|
'+∞%' if ratio == float('+inf')
|
||||||
|
else '-∞%' if ratio == float('-inf')
|
||||||
|
else '%+.1f%%' % (100*ratio))
|
||||||
|
if ratio else ''))
|
||||||
|
|
||||||
|
# print total
|
||||||
|
total = fold(results, by=[])
|
||||||
|
r = total[0] if total else None
|
||||||
|
if diff_results is not None:
|
||||||
|
diff_total = fold(diff_results, by=[])
|
||||||
|
diff_r = diff_total[0] if diff_total else None
|
||||||
|
ratio = IntField.ratio(
|
||||||
|
r.data_size if r else None,
|
||||||
|
diff_r.data_size if diff_r else None)
|
||||||
|
|
||||||
|
print('%-36s' % 'TOTAL', end='')
|
||||||
|
if diff_results is None:
|
||||||
|
print(' %s' % (
|
||||||
|
r.data_size.table()
|
||||||
|
if r else IntField.none))
|
||||||
|
elif percent:
|
||||||
|
print(' %s%s' % (
|
||||||
|
r.data_size.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
' (%s)' % (
|
||||||
|
'+∞%' if ratio == float('+inf')
|
||||||
|
else '-∞%' if ratio == float('-inf')
|
||||||
|
else '%+.1f%%' % (100*ratio))))
|
||||||
|
else:
|
||||||
|
print(' %s %s %s%s' % (
|
||||||
|
diff_r.data_size.diff_table()
|
||||||
|
if diff_r else IntField.diff_none,
|
||||||
|
r.data_size.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
IntField.diff_diff(
|
||||||
|
r.data_size if r else None,
|
||||||
|
diff_r.data_size if diff_r else None)
|
||||||
|
if r or diff_r else IntField.diff_none,
|
||||||
|
' (%s)' % (
|
||||||
|
'+∞%' if ratio == float('+inf')
|
||||||
|
else '-∞%' if ratio == float('-inf')
|
||||||
|
else '%+.1f%%' % (100*ratio))
|
||||||
|
if ratio else ''))
|
||||||
|
|
||||||
|
|
||||||
|
def main(obj_paths, **args):
|
||||||
# find sizes
|
# find sizes
|
||||||
if not args.get('use', None):
|
if not args.get('use', None):
|
||||||
# find .o files
|
# find .o files
|
||||||
paths = []
|
paths = []
|
||||||
for path in args['obj_paths']:
|
for path in obj_paths:
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
path = path + '/*.o'
|
path = path + '/*.o'
|
||||||
|
|
||||||
@@ -135,127 +350,61 @@ def main(**args):
|
|||||||
paths.append(path)
|
paths.append(path)
|
||||||
|
|
||||||
if not paths:
|
if not paths:
|
||||||
print('no .obj files found in %r?' % args['obj_paths'])
|
print('no .obj files found in %r?' % obj_paths)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
results = collect(paths, **args)
|
results = collect(paths, **args)
|
||||||
else:
|
else:
|
||||||
|
results = []
|
||||||
with openio(args['use']) as f:
|
with openio(args['use']) as f:
|
||||||
r = csv.DictReader(f)
|
reader = csv.DictReader(f)
|
||||||
results = {
|
for r in reader:
|
||||||
(result['file'], result['name']): DataResult(
|
try:
|
||||||
*(result[f] for f in DataResult._fields))
|
results.append(DataResult(**{
|
||||||
for result in r
|
k: v for k, v in r.items()
|
||||||
if all(result.get(f) not in {None, ''}
|
if k in DataResult._fields}))
|
||||||
for f in DataResult._fields)}
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
# find previous results?
|
# fold to remove duplicates
|
||||||
if args.get('diff'):
|
results = fold(results)
|
||||||
try:
|
|
||||||
with openio(args['diff']) as f:
|
# sort because why not
|
||||||
r = csv.DictReader(f)
|
results.sort()
|
||||||
prev_results = {
|
|
||||||
(result['file'], result['name']): DataResult(
|
|
||||||
*(result[f] for f in DataResult._fields))
|
|
||||||
for result in r
|
|
||||||
if all(result.get(f) not in {None, ''}
|
|
||||||
for f in DataResult._fields)}
|
|
||||||
except FileNotFoundError:
|
|
||||||
prev_results = []
|
|
||||||
|
|
||||||
# write results to CSV
|
# write results to CSV
|
||||||
if args.get('output'):
|
if args.get('output'):
|
||||||
merged_results = co.defaultdict(lambda: {})
|
|
||||||
other_fields = []
|
|
||||||
|
|
||||||
# merge?
|
|
||||||
if args.get('merge'):
|
|
||||||
try:
|
|
||||||
with openio(args['merge']) as f:
|
|
||||||
r = csv.DictReader(f)
|
|
||||||
for result in r:
|
|
||||||
file = result.pop('file', '')
|
|
||||||
func = result.pop('name', '')
|
|
||||||
for f in DataResult._fields:
|
|
||||||
result.pop(f, None)
|
|
||||||
merged_results[(file, func)] = result
|
|
||||||
other_fields = result.keys()
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for (file, func), result in results.items():
|
|
||||||
merged_results[(file, func)] |= result._asdict()
|
|
||||||
|
|
||||||
with openio(args['output'], 'w') as f:
|
with openio(args['output'], 'w') as f:
|
||||||
w = csv.DictWriter(f, ['file', 'name',
|
writer = csv.DictWriter(f, DataResult._fields)
|
||||||
*other_fields, *DataResult._fields])
|
writer.writeheader()
|
||||||
w.writeheader()
|
for r in results:
|
||||||
for (file, func), result in sorted(merged_results.items()):
|
writer.writerow(r._asdict())
|
||||||
w.writerow({'file': file, 'name': func, **result})
|
|
||||||
|
|
||||||
# print results
|
# find previous results?
|
||||||
def print_header(by):
|
if args.get('diff'):
|
||||||
if by == 'total':
|
diff_results = []
|
||||||
entry = lambda k: 'TOTAL'
|
try:
|
||||||
elif by == 'file':
|
with openio(args['diff']) as f:
|
||||||
entry = lambda k: k[0]
|
reader = csv.DictReader(f)
|
||||||
else:
|
for r in reader:
|
||||||
entry = lambda k: k[1]
|
try:
|
||||||
|
diff_results.append(DataResult(**{
|
||||||
|
k: v for k, v in r.items()
|
||||||
|
if k in DataResult._fields}))
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
if not args.get('diff'):
|
# fold to remove duplicates
|
||||||
print('%-36s %s' % (by, DataResult._header))
|
diff_results = fold(diff_results)
|
||||||
else:
|
|
||||||
old = {entry(k) for k in results.keys()}
|
|
||||||
new = {entry(k) for k in prev_results.keys()}
|
|
||||||
print('%-36s %s' % (
|
|
||||||
'%s (%d added, %d removed)' % (by,
|
|
||||||
sum(1 for k in new if k not in old),
|
|
||||||
sum(1 for k in old if k not in new))
|
|
||||||
if by else '',
|
|
||||||
DataDiff._header))
|
|
||||||
|
|
||||||
def print_entries(by):
|
# print table
|
||||||
if by == 'total':
|
if not args.get('quiet'):
|
||||||
entry = lambda k: 'TOTAL'
|
table(
|
||||||
elif by == 'file':
|
results,
|
||||||
entry = lambda k: k[0]
|
diff_results if args.get('diff') else None,
|
||||||
else:
|
**args)
|
||||||
entry = lambda k: k[1]
|
|
||||||
|
|
||||||
entries = co.defaultdict(lambda: DataResult())
|
|
||||||
for k, result in results.items():
|
|
||||||
entries[entry(k)] += result
|
|
||||||
|
|
||||||
if not args.get('diff'):
|
|
||||||
for name, result in sorted(entries.items(),
|
|
||||||
key=lambda p: (p[1].key(**args), p)):
|
|
||||||
print('%-36s %s' % (name, result))
|
|
||||||
else:
|
|
||||||
prev_entries = co.defaultdict(lambda: DataResult())
|
|
||||||
for k, result in prev_results.items():
|
|
||||||
prev_entries[entry(k)] += result
|
|
||||||
|
|
||||||
diff_entries = {name: entries.get(name) - prev_entries.get(name)
|
|
||||||
for name in (entries.keys() | prev_entries.keys())}
|
|
||||||
|
|
||||||
for name, diff in sorted(diff_entries.items(),
|
|
||||||
key=lambda p: (p[1].key(**args), p)):
|
|
||||||
if diff or args.get('all'):
|
|
||||||
print('%-36s %s' % (name, diff))
|
|
||||||
|
|
||||||
if args.get('quiet'):
|
|
||||||
pass
|
|
||||||
elif args.get('summary'):
|
|
||||||
print_header('')
|
|
||||||
print_entries('total')
|
|
||||||
elif args.get('files'):
|
|
||||||
print_header('file')
|
|
||||||
print_entries('file')
|
|
||||||
print_entries('total')
|
|
||||||
else:
|
|
||||||
print_header('function')
|
|
||||||
print_entries('function')
|
|
||||||
print_entries('total')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -263,42 +412,72 @@ if __name__ == "__main__":
|
|||||||
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.")
|
||||||
parser.add_argument('obj_paths', nargs='*', default=OBJ_PATHS,
|
parser.add_argument(
|
||||||
help="Description of where to find *.o files. May be a directory \
|
'obj_paths',
|
||||||
or a list of paths. Defaults to %r." % OBJ_PATHS)
|
nargs='*',
|
||||||
parser.add_argument('-v', '--verbose', action='store_true',
|
default=OBJ_PATHS,
|
||||||
|
help="Description of where to find *.o files. May be a directory "
|
||||||
|
"or a list of paths. Defaults to %(default)r.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-v', '--verbose',
|
||||||
|
action='store_true',
|
||||||
help="Output commands that run behind the scenes.")
|
help="Output commands that run behind the scenes.")
|
||||||
parser.add_argument('-q', '--quiet', action='store_true',
|
parser.add_argument(
|
||||||
|
'-q', '--quiet',
|
||||||
|
action='store_true',
|
||||||
help="Don't show anything, useful with -o.")
|
help="Don't show anything, useful with -o.")
|
||||||
parser.add_argument('-o', '--output',
|
parser.add_argument(
|
||||||
|
'-o', '--output',
|
||||||
help="Specify CSV file to store results.")
|
help="Specify CSV file to store results.")
|
||||||
parser.add_argument('-u', '--use',
|
parser.add_argument(
|
||||||
help="Don't compile and find data sizes, instead use this CSV file.")
|
'-u', '--use',
|
||||||
parser.add_argument('-d', '--diff',
|
help="Don't parse anything, use this CSV file.")
|
||||||
help="Specify CSV file to diff data size against.")
|
parser.add_argument(
|
||||||
parser.add_argument('-m', '--merge',
|
'-d', '--diff',
|
||||||
help="Merge with an existing CSV file when writing to output.")
|
help="Specify CSV file to diff against.")
|
||||||
parser.add_argument('-a', '--all', action='store_true',
|
parser.add_argument(
|
||||||
help="Show all functions, not just the ones that changed.")
|
'-a', '--all',
|
||||||
parser.add_argument('-A', '--everything', action='store_true',
|
action='store_true',
|
||||||
help="Include builtin and libc specific symbols.")
|
help="Show all, not just the ones that changed.")
|
||||||
parser.add_argument('-s', '--size-sort', action='store_true',
|
parser.add_argument(
|
||||||
|
'-p', '--percent',
|
||||||
|
action='store_true',
|
||||||
|
help="Only show percentage change, not a full diff.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-b', '--by-file',
|
||||||
|
action='store_true',
|
||||||
|
help="Group by file. Note this does not include padding "
|
||||||
|
"so sizes may differ from other tools.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-s', '--size-sort',
|
||||||
|
action='store_true',
|
||||||
help="Sort by size.")
|
help="Sort by size.")
|
||||||
parser.add_argument('-S', '--reverse-size-sort', action='store_true',
|
parser.add_argument(
|
||||||
|
'-S', '--reverse-size-sort',
|
||||||
|
action='store_true',
|
||||||
help="Sort by size, but backwards.")
|
help="Sort by size, but backwards.")
|
||||||
parser.add_argument('-F', '--files', action='store_true',
|
parser.add_argument(
|
||||||
help="Show file-level data sizes. Note this does not include padding! "
|
'-Y', '--summary',
|
||||||
"So sizes may differ from other tools.")
|
action='store_true',
|
||||||
parser.add_argument('-Y', '--summary', action='store_true',
|
help="Only show the total size.")
|
||||||
help="Only show the total data size.")
|
parser.add_argument(
|
||||||
parser.add_argument('--type', default='dDbB',
|
'-A', '--everything',
|
||||||
|
action='store_true',
|
||||||
|
help="Include builtin and libc specific symbols.")
|
||||||
|
parser.add_argument(
|
||||||
|
'--type',
|
||||||
|
default=TYPE,
|
||||||
help="Type of symbols to report, this uses the same single-character "
|
help="Type of symbols to report, this uses the same single-character "
|
||||||
"type-names emitted by nm. Defaults to %(default)r.")
|
"type-names emitted by nm. Defaults to %(default)r.")
|
||||||
parser.add_argument('--nm-tool', default=['nm'], type=lambda x: x.split(),
|
parser.add_argument(
|
||||||
help="Path to the nm tool to use.")
|
'--nm-tool',
|
||||||
parser.add_argument('--build-dir',
|
type=lambda x: x.split(),
|
||||||
help="Specify the relative build directory. Used to map object files \
|
default=NM_TOOL,
|
||||||
to the correct source files.")
|
help="Path to the nm tool to use. Defaults to %(default)r")
|
||||||
|
parser.add_argument(
|
||||||
|
'--build-dir',
|
||||||
|
help="Specify the relative build directory. Used to map object files "
|
||||||
|
"to the correct source files.")
|
||||||
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}))
|
||||||
|
|||||||
+488
-270
@@ -4,17 +4,124 @@
|
|||||||
# report as infinite stack usage.
|
# report as infinite stack usage.
|
||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import collections as co
|
||||||
|
import csv
|
||||||
import glob
|
import glob
|
||||||
import itertools as it
|
import itertools as it
|
||||||
import re
|
|
||||||
import csv
|
|
||||||
import collections as co
|
|
||||||
import math as m
|
import math as m
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
CI_PATHS = ['*.ci']
|
CI_PATHS = ['*.ci']
|
||||||
|
|
||||||
|
|
||||||
|
# integer fields
|
||||||
|
class IntField(co.namedtuple('IntField', 'x')):
|
||||||
|
__slots__ = ()
|
||||||
|
def __new__(cls, x):
|
||||||
|
if isinstance(x, IntField):
|
||||||
|
return x
|
||||||
|
if isinstance(x, str):
|
||||||
|
try:
|
||||||
|
x = int(x, 0)
|
||||||
|
except ValueError:
|
||||||
|
# also accept +-∞ and +-inf
|
||||||
|
if re.match('^\s*\+?\s*(?:∞|inf)\s*$', x):
|
||||||
|
x = float('inf')
|
||||||
|
elif re.match('^\s*-\s*(?:∞|inf)\s*$', x):
|
||||||
|
x = float('-inf')
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
return super().__new__(cls, x)
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
assert not m.isinf(self.x)
|
||||||
|
return self.x
|
||||||
|
|
||||||
|
def __float__(self):
|
||||||
|
return float(self.x)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.x == float('inf'):
|
||||||
|
return '∞'
|
||||||
|
elif self.x == float('-inf'):
|
||||||
|
return '-∞'
|
||||||
|
else:
|
||||||
|
return str(self.x)
|
||||||
|
|
||||||
|
none = '%7s' % '-'
|
||||||
|
def table(self):
|
||||||
|
return '%7s' % (self,)
|
||||||
|
|
||||||
|
diff_none = '%7s' % '-'
|
||||||
|
diff_table = table
|
||||||
|
|
||||||
|
def diff_diff(self, other):
|
||||||
|
new = self.x if self else 0
|
||||||
|
old = other.x if other else 0
|
||||||
|
diff = new - old
|
||||||
|
if diff == float('+inf'):
|
||||||
|
return '%7s' % '+∞'
|
||||||
|
elif diff == float('-inf'):
|
||||||
|
return '%7s' % '-∞'
|
||||||
|
else:
|
||||||
|
return '%+7d' % diff
|
||||||
|
|
||||||
|
def ratio(self, other):
|
||||||
|
new = self.x if self else 0
|
||||||
|
old = other.x if other else 0
|
||||||
|
if m.isinf(new) and m.isinf(old):
|
||||||
|
return 0.0
|
||||||
|
elif m.isinf(new):
|
||||||
|
return float('+inf')
|
||||||
|
elif m.isinf(old):
|
||||||
|
return float('-inf')
|
||||||
|
elif not old and not new:
|
||||||
|
return 0.0
|
||||||
|
elif not old:
|
||||||
|
return 1.0
|
||||||
|
else:
|
||||||
|
return (new-old) / old
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
return IntField(self.x + other.x)
|
||||||
|
|
||||||
|
def __mul__(self, other):
|
||||||
|
return IntField(self.x * other.x)
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.x < other.x
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self.__class__.__lt__(other, self)
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return not self.__gt__(other)
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return not self.__lt__(other)
|
||||||
|
|
||||||
|
def __truediv__(self, n):
|
||||||
|
if m.isinf(self.x):
|
||||||
|
return self
|
||||||
|
else:
|
||||||
|
return IntField(round(self.x / n))
|
||||||
|
|
||||||
|
# size results
|
||||||
|
class StackResult(co.namedtuple('StackResult',
|
||||||
|
'file,function,stack_frame,stack_limit')):
|
||||||
|
__slots__ = ()
|
||||||
|
def __new__(cls, file, function, stack_frame, stack_limit):
|
||||||
|
return super().__new__(cls, file, function,
|
||||||
|
IntField(stack_frame), IntField(stack_limit))
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
return StackResult(self.file, self.function,
|
||||||
|
self.stack_frame + other.stack_frame,
|
||||||
|
max(self.stack_limit, other.stack_limit))
|
||||||
|
|
||||||
|
|
||||||
def openio(path, mode='r'):
|
def openio(path, mode='r'):
|
||||||
if path == '-':
|
if path == '-':
|
||||||
if 'r' in mode:
|
if 'r' in mode:
|
||||||
@@ -24,91 +131,10 @@ def openio(path, mode='r'):
|
|||||||
else:
|
else:
|
||||||
return open(path, mode)
|
return open(path, mode)
|
||||||
|
|
||||||
class StackResult(co.namedtuple('StackResult', 'stack_frame,stack_limit')):
|
|
||||||
__slots__ = ()
|
|
||||||
def __new__(cls, stack_frame=0, stack_limit=0):
|
|
||||||
return super().__new__(cls,
|
|
||||||
int(stack_frame),
|
|
||||||
float(stack_limit))
|
|
||||||
|
|
||||||
def __add__(self, other):
|
def collect(paths, *,
|
||||||
return self.__class__(
|
everything=False,
|
||||||
self.stack_frame + other.stack_frame,
|
**args):
|
||||||
max(self.stack_limit, other.stack_limit))
|
|
||||||
|
|
||||||
def __sub__(self, other):
|
|
||||||
return StackDiff(other, self)
|
|
||||||
|
|
||||||
def __rsub__(self, other):
|
|
||||||
return self.__class__.__sub__(other, self)
|
|
||||||
|
|
||||||
def key(self, **args):
|
|
||||||
if args.get('limit_sort'):
|
|
||||||
return -self.stack_limit
|
|
||||||
elif args.get('reverse_limit_sort'):
|
|
||||||
return +self.stack_limit
|
|
||||||
elif args.get('frame_sort'):
|
|
||||||
return -self.stack_frame
|
|
||||||
elif args.get('reverse_frame_sort'):
|
|
||||||
return +self.stack_frame
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
_header = '%7s %7s' % ('frame', 'limit')
|
|
||||||
def __str__(self):
|
|
||||||
return '%7d %7s' % (
|
|
||||||
self.stack_frame,
|
|
||||||
'∞' if m.isinf(self.stack_limit) else int(self.stack_limit))
|
|
||||||
|
|
||||||
class StackDiff(co.namedtuple('StackDiff', 'old,new')):
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def ratio(self):
|
|
||||||
old_limit = self.old.stack_limit if self.old is not None else 0
|
|
||||||
new_limit = self.new.stack_limit if self.new is not None else 0
|
|
||||||
return (0.0 if m.isinf(new_limit) and m.isinf(old_limit)
|
|
||||||
else +float('inf') if m.isinf(new_limit)
|
|
||||||
else -float('inf') if m.isinf(old_limit)
|
|
||||||
else 0.0 if not old_limit and not new_limit
|
|
||||||
else 1.0 if not old_limit
|
|
||||||
else (new_limit-old_limit) / old_limit)
|
|
||||||
|
|
||||||
def key(self, **args):
|
|
||||||
return (
|
|
||||||
self.new.key(**args) if self.new is not None else 0,
|
|
||||||
-self.ratio())
|
|
||||||
|
|
||||||
def __bool__(self):
|
|
||||||
return bool(self.ratio())
|
|
||||||
|
|
||||||
_header = '%15s %15s %15s' % ('old', 'new', 'diff')
|
|
||||||
def __str__(self):
|
|
||||||
old_frame = self.old.stack_frame if self.old is not None else 0
|
|
||||||
old_limit = self.old.stack_limit if self.old is not None else 0
|
|
||||||
new_frame = self.new.stack_frame if self.new is not None else 0
|
|
||||||
new_limit = self.new.stack_limit if self.new is not None else 0
|
|
||||||
diff_frame = new_frame - old_frame
|
|
||||||
diff_limit = (0 if m.isinf(new_limit) and m.isinf(old_limit)
|
|
||||||
else new_limit - old_limit)
|
|
||||||
ratio = self.ratio()
|
|
||||||
return '%7s %7s %7s %7s %+7d %7s%s' % (
|
|
||||||
old_frame if self.old is not None else '-',
|
|
||||||
('∞' if m.isinf(old_limit) else int(old_limit))
|
|
||||||
if self.old is not None else '-',
|
|
||||||
new_frame if self.new is not None else '-',
|
|
||||||
('∞' if m.isinf(new_limit) else int(new_limit))
|
|
||||||
if self.new is not None else '-',
|
|
||||||
diff_frame,
|
|
||||||
'+∞' if diff_limit > 0 and m.isinf(diff_limit)
|
|
||||||
else '-∞' if diff_limit < 0 and m.isinf(diff_limit)
|
|
||||||
else '%+d' % diff_limit,
|
|
||||||
'' if not ratio
|
|
||||||
else ' (+∞%)' if ratio > 0 and m.isinf(ratio)
|
|
||||||
else ' (-∞%)' if ratio < 0 and m.isinf(ratio)
|
|
||||||
else ' (%+.1f%%)' % (100*ratio))
|
|
||||||
|
|
||||||
|
|
||||||
def collect(paths, **args):
|
|
||||||
# parse the vcg format
|
# parse the vcg format
|
||||||
k_pattern = re.compile('([a-z]+)\s*:', re.DOTALL)
|
k_pattern = re.compile('([a-z]+)\s*:', re.DOTALL)
|
||||||
v_pattern = re.compile('(?:"(.*?)"|([a-z]+))', re.DOTALL)
|
v_pattern = re.compile('(?:"(.*?)"|([a-z]+))', re.DOTALL)
|
||||||
@@ -154,9 +180,11 @@ def collect(paths, **args):
|
|||||||
m = f_pattern.match(info['label'])
|
m = f_pattern.match(info['label'])
|
||||||
if m:
|
if m:
|
||||||
function, file, size, type = m.groups()
|
function, file, size, type = m.groups()
|
||||||
if not args.get('quiet') and type != 'static':
|
if (not args.get('quiet')
|
||||||
|
and 'static' not in type
|
||||||
|
and 'bounded' not in type):
|
||||||
print('warning: found non-static stack for %s (%s)'
|
print('warning: found non-static stack for %s (%s)'
|
||||||
% (function, type))
|
% (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)
|
||||||
@@ -167,7 +195,7 @@ def collect(paths, **args):
|
|||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not args.get('everything'):
|
if not everything:
|
||||||
for source, (s_file, s_function, _, _) in list(callgraph.items()):
|
for source, (s_file, s_function, _, _) in list(callgraph.items()):
|
||||||
# discard internal functions
|
# discard internal functions
|
||||||
if s_file.startswith('<') or s_file.startswith('/usr/include'):
|
if s_file.startswith('<') or s_file.startswith('/usr/include'):
|
||||||
@@ -200,22 +228,266 @@ def collect(paths, **args):
|
|||||||
return calls
|
return calls
|
||||||
|
|
||||||
# build results
|
# build results
|
||||||
results = {}
|
results = []
|
||||||
result_calls = {}
|
calls = {}
|
||||||
for source, (s_file, s_function, frame, targets) in callgraph.items():
|
for source, (s_file, s_function, frame, targets) in callgraph.items():
|
||||||
limit = find_limit(source)
|
limit = find_limit(source)
|
||||||
calls = find_calls(targets)
|
cs = find_calls(targets)
|
||||||
results[(s_file, s_function)] = StackResult(frame, limit)
|
results.append(StackResult(s_file, s_function, frame, limit))
|
||||||
result_calls[(s_file, s_function)] = calls
|
calls[(s_file, s_function)] = cs
|
||||||
|
|
||||||
return results, result_calls
|
return results, calls
|
||||||
|
|
||||||
def main(**args):
|
|
||||||
|
def fold(results, *,
|
||||||
|
by=['file', 'function'],
|
||||||
|
**_):
|
||||||
|
folding = co.OrderedDict()
|
||||||
|
for r in results:
|
||||||
|
name = tuple(getattr(r, k) for k in by)
|
||||||
|
if name not in folding:
|
||||||
|
folding[name] = []
|
||||||
|
folding[name].append(r)
|
||||||
|
|
||||||
|
folded = []
|
||||||
|
for rs in folding.values():
|
||||||
|
folded.append(sum(rs[1:], start=rs[0]))
|
||||||
|
|
||||||
|
return folded
|
||||||
|
|
||||||
|
def fold_calls(calls, *,
|
||||||
|
by=['file', 'function'],
|
||||||
|
**_):
|
||||||
|
def by_(name):
|
||||||
|
file, function = name
|
||||||
|
return (((file,) if 'file' in by else ())
|
||||||
|
+ ((function,) if 'function' in by else ()))
|
||||||
|
|
||||||
|
folded = {}
|
||||||
|
for name, cs in calls.items():
|
||||||
|
name = by_(name)
|
||||||
|
if name not in folded:
|
||||||
|
folded[name] = set()
|
||||||
|
folded[name] |= {by_(c) for c in cs}
|
||||||
|
|
||||||
|
return folded
|
||||||
|
|
||||||
|
|
||||||
|
def table(results, calls, diff_results=None, *,
|
||||||
|
by_file=False,
|
||||||
|
limit_sort=False,
|
||||||
|
reverse_limit_sort=False,
|
||||||
|
frame_sort=False,
|
||||||
|
reverse_frame_sort=False,
|
||||||
|
summary=False,
|
||||||
|
all=False,
|
||||||
|
percent=False,
|
||||||
|
tree=False,
|
||||||
|
depth=None,
|
||||||
|
**_):
|
||||||
|
all_, all = all, __builtins__.all
|
||||||
|
|
||||||
|
# tree doesn't really make sense with depth=0, assume depth=inf
|
||||||
|
if depth is None:
|
||||||
|
depth = float('inf') if tree else 0
|
||||||
|
|
||||||
|
# fold
|
||||||
|
results = fold(results, by=['file' if by_file else 'function'])
|
||||||
|
calls = fold_calls(calls, by=['file' if by_file else 'function'])
|
||||||
|
if diff_results is not None:
|
||||||
|
diff_results = fold(diff_results,
|
||||||
|
by=['file' if by_file else 'function'])
|
||||||
|
|
||||||
|
table = {
|
||||||
|
r.file if by_file else r.function: r
|
||||||
|
for r in results}
|
||||||
|
diff_table = {
|
||||||
|
r.file if by_file else r.function: r
|
||||||
|
for r in diff_results or []}
|
||||||
|
|
||||||
|
# sort, note that python's sort is stable
|
||||||
|
names = list(table.keys() | diff_table.keys())
|
||||||
|
names.sort()
|
||||||
|
if diff_results is not None:
|
||||||
|
names.sort(key=lambda n: -IntField.ratio(
|
||||||
|
table[n].stack_frame if n in table else None,
|
||||||
|
diff_table[n].stack_frame if n in diff_table else None))
|
||||||
|
if limit_sort:
|
||||||
|
names.sort(key=lambda n: (table[n].stack_limit,) if n in table else (),
|
||||||
|
reverse=True)
|
||||||
|
elif reverse_limit_sort:
|
||||||
|
names.sort(key=lambda n: (table[n].stack_limit,) if n in table else (),
|
||||||
|
reverse=False)
|
||||||
|
elif frame_sort:
|
||||||
|
names.sort(key=lambda n: (table[n].stack_frame,) if n in table else (),
|
||||||
|
reverse=True)
|
||||||
|
elif reverse_frame_sort:
|
||||||
|
names.sort(key=lambda n: (table[n].stack_frame,) if n in table else (),
|
||||||
|
reverse=False)
|
||||||
|
|
||||||
|
# adjust the name width based on the expected call depth, note that we
|
||||||
|
# can't always find the depth due to recursion
|
||||||
|
width = 36 + (4*depth if not m.isinf(depth) else 0)
|
||||||
|
|
||||||
|
# print header
|
||||||
|
if not tree:
|
||||||
|
print('%-*s' % (width, '%s%s' % (
|
||||||
|
'file' if by_file else 'function',
|
||||||
|
' (%d added, %d removed)' % (
|
||||||
|
sum(1 for n in table if n not in diff_table),
|
||||||
|
sum(1 for n in diff_table if n not in table))
|
||||||
|
if diff_results is not None and not percent else '')
|
||||||
|
if not summary else ''),
|
||||||
|
end='')
|
||||||
|
if diff_results is None:
|
||||||
|
print(' %s %s' % (
|
||||||
|
'frame'.rjust(len(IntField.none)),
|
||||||
|
'limit'.rjust(len(IntField.none))))
|
||||||
|
elif percent:
|
||||||
|
print(' %s %s' % (
|
||||||
|
'frame'.rjust(len(IntField.diff_none)),
|
||||||
|
'limit'.rjust(len(IntField.diff_none))))
|
||||||
|
else:
|
||||||
|
print(' %s %s %s %s %s %s' % (
|
||||||
|
'oframe'.rjust(len(IntField.diff_none)),
|
||||||
|
'olimit'.rjust(len(IntField.diff_none)),
|
||||||
|
'nframe'.rjust(len(IntField.diff_none)),
|
||||||
|
'nlimit'.rjust(len(IntField.diff_none)),
|
||||||
|
'dframe'.rjust(len(IntField.diff_none)),
|
||||||
|
'dlimit'.rjust(len(IntField.diff_none))))
|
||||||
|
|
||||||
|
# print entries
|
||||||
|
if not summary:
|
||||||
|
# print the tree recursively
|
||||||
|
def table_calls(names_, depth,
|
||||||
|
prefixes=('', '', '', '')):
|
||||||
|
for i, name in enumerate(names_):
|
||||||
|
r = table.get(name)
|
||||||
|
if diff_results is not None:
|
||||||
|
diff_r = diff_table.get(name)
|
||||||
|
ratio = IntField.ratio(
|
||||||
|
r.stack_limit if r else None,
|
||||||
|
diff_r.stack_limit if diff_r else None)
|
||||||
|
if not ratio and not all_:
|
||||||
|
continue
|
||||||
|
|
||||||
|
is_last = (i == len(names_)-1)
|
||||||
|
print('%-*s' % (width, prefixes[0+is_last] + name), end='')
|
||||||
|
if tree:
|
||||||
|
print()
|
||||||
|
elif diff_results is None:
|
||||||
|
print(' %s %s' % (
|
||||||
|
r.stack_frame.table()
|
||||||
|
if r else IntField.none,
|
||||||
|
r.stack_limit.table()
|
||||||
|
if r else IntField.none))
|
||||||
|
elif percent:
|
||||||
|
print(' %s %s%s' % (
|
||||||
|
r.stack_frame.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
r.stack_limit.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
' (%s)' % (
|
||||||
|
'+∞%' if ratio == float('+inf')
|
||||||
|
else '-∞%' if ratio == float('-inf')
|
||||||
|
else '%+.1f%%' % (100*ratio))))
|
||||||
|
else:
|
||||||
|
print(' %s %s %s %s %s %s%s' % (
|
||||||
|
diff_r.stack_frame.diff_table()
|
||||||
|
if diff_r else IntField.diff_none,
|
||||||
|
diff_r.stack_limit.diff_table()
|
||||||
|
if diff_r else IntField.diff_none,
|
||||||
|
r.stack_frame.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
r.stack_limit.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
IntField.diff_diff(
|
||||||
|
r.stack_frame if r else None,
|
||||||
|
diff_r.stack_frame if diff_r else None)
|
||||||
|
if r or diff_r else IntField.diff_none,
|
||||||
|
IntField.diff_diff(
|
||||||
|
r.stack_limit if r else None,
|
||||||
|
diff_r.stack_limit if diff_r else None)
|
||||||
|
if r or diff_r else IntField.diff_none,
|
||||||
|
' (%s)' % (
|
||||||
|
'+∞%' if ratio == float('+inf')
|
||||||
|
else '-∞%' if ratio == float('-inf')
|
||||||
|
else '%+.1f%%' % (100*ratio))
|
||||||
|
if ratio else ''))
|
||||||
|
|
||||||
|
# recurse?
|
||||||
|
if depth > 0:
|
||||||
|
cs = calls.get((name,), set())
|
||||||
|
table_calls(
|
||||||
|
[n for n in names if (n,) in cs],
|
||||||
|
depth-1,
|
||||||
|
( prefixes[2+is_last] + "|-> ",
|
||||||
|
prefixes[2+is_last] + "'-> ",
|
||||||
|
prefixes[2+is_last] + "| ",
|
||||||
|
prefixes[2+is_last] + " "))
|
||||||
|
|
||||||
|
|
||||||
|
table_calls(names, depth)
|
||||||
|
|
||||||
|
# print total
|
||||||
|
if not tree:
|
||||||
|
total = fold(results, by=[])
|
||||||
|
r = total[0] if total else None
|
||||||
|
if diff_results is not None:
|
||||||
|
diff_total = fold(diff_results, by=[])
|
||||||
|
diff_r = diff_total[0] if diff_total else None
|
||||||
|
ratio = IntField.ratio(
|
||||||
|
r.stack_limit if r else None,
|
||||||
|
diff_r.stack_limit if diff_r else None)
|
||||||
|
|
||||||
|
print('%-*s' % (width, 'TOTAL'), end='')
|
||||||
|
if diff_results is None:
|
||||||
|
print(' %s %s' % (
|
||||||
|
r.stack_frame.table()
|
||||||
|
if r else IntField.none,
|
||||||
|
r.stack_limit.table()
|
||||||
|
if r else IntField.none))
|
||||||
|
elif percent:
|
||||||
|
print(' %s %s%s' % (
|
||||||
|
r.stack_frame.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
r.stack_limit.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
' (%s)' % (
|
||||||
|
'+∞%' if ratio == float('+inf')
|
||||||
|
else '-∞%' if ratio == float('-inf')
|
||||||
|
else '%+.1f%%' % (100*ratio))))
|
||||||
|
else:
|
||||||
|
print(' %s %s %s %s %s %s%s' % (
|
||||||
|
diff_r.stack_frame.diff_table()
|
||||||
|
if diff_r else IntField.diff_none,
|
||||||
|
diff_r.stack_limit.diff_table()
|
||||||
|
if diff_r else IntField.diff_none,
|
||||||
|
r.stack_frame.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
r.stack_limit.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
IntField.diff_diff(
|
||||||
|
r.stack_frame if r else None,
|
||||||
|
diff_r.stack_frame if diff_r else None)
|
||||||
|
if r or diff_r else IntField.diff_none,
|
||||||
|
IntField.diff_diff(
|
||||||
|
r.stack_limit if r else None,
|
||||||
|
diff_r.stack_limit if diff_r else None)
|
||||||
|
if r or diff_r else IntField.diff_none,
|
||||||
|
' (%s)' % (
|
||||||
|
'+∞%' if ratio == float('+inf')
|
||||||
|
else '-∞%' if ratio == float('-inf')
|
||||||
|
else '%+.1f%%' % (100*ratio))
|
||||||
|
if ratio else ''))
|
||||||
|
|
||||||
|
|
||||||
|
def main(ci_paths, **args):
|
||||||
# find sizes
|
# find sizes
|
||||||
if not args.get('use', None):
|
if not args.get('use', None):
|
||||||
# find .ci files
|
# find .ci files
|
||||||
paths = []
|
paths = []
|
||||||
for path in args['ci_paths']:
|
for path in ci_paths:
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
path = path + '/*.ci'
|
path = path + '/*.ci'
|
||||||
|
|
||||||
@@ -223,160 +495,68 @@ def main(**args):
|
|||||||
paths.append(path)
|
paths.append(path)
|
||||||
|
|
||||||
if not paths:
|
if not paths:
|
||||||
print('no .ci files found in %r?' % args['ci_paths'])
|
print('no .ci files found in %r?' % ci_paths)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
results, result_calls = collect(paths, **args)
|
results, calls = collect(paths, **args)
|
||||||
else:
|
else:
|
||||||
|
results = []
|
||||||
with openio(args['use']) as f:
|
with openio(args['use']) as f:
|
||||||
r = csv.DictReader(f)
|
reader = csv.DictReader(f)
|
||||||
results = {
|
for r in reader:
|
||||||
(result['file'], result['name']): StackResult(
|
try:
|
||||||
*(result[f] for f in StackResult._fields))
|
results.append(StackResult(**{
|
||||||
for result in r
|
k: v for k, v in r.items()
|
||||||
if all(result.get(f) not in {None, ''}
|
if k in StackResult._fields}))
|
||||||
for f in StackResult._fields)}
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
result_calls = {}
|
calls = {}
|
||||||
|
|
||||||
# find previous results?
|
# fold to remove duplicates
|
||||||
if args.get('diff'):
|
results = fold(results)
|
||||||
try:
|
|
||||||
with openio(args['diff']) as f:
|
# sort because why not
|
||||||
r = csv.DictReader(f)
|
results.sort()
|
||||||
prev_results = {
|
|
||||||
(result['file'], result['name']): StackResult(
|
|
||||||
*(result[f] for f in StackResult._fields))
|
|
||||||
for result in r
|
|
||||||
if all(result.get(f) not in {None, ''}
|
|
||||||
for f in StackResult._fields)}
|
|
||||||
except FileNotFoundError:
|
|
||||||
prev_results = []
|
|
||||||
|
|
||||||
# write results to CSV
|
# write results to CSV
|
||||||
if args.get('output'):
|
if args.get('output'):
|
||||||
merged_results = co.defaultdict(lambda: {})
|
|
||||||
other_fields = []
|
|
||||||
|
|
||||||
# merge?
|
|
||||||
if args.get('merge'):
|
|
||||||
try:
|
|
||||||
with openio(args['merge']) as f:
|
|
||||||
r = csv.DictReader(f)
|
|
||||||
for result in r:
|
|
||||||
file = result.pop('file', '')
|
|
||||||
func = result.pop('name', '')
|
|
||||||
for f in StackResult._fields:
|
|
||||||
result.pop(f, None)
|
|
||||||
merged_results[(file, func)] = result
|
|
||||||
other_fields = result.keys()
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for (file, func), result in results.items():
|
|
||||||
merged_results[(file, func)] |= result._asdict()
|
|
||||||
|
|
||||||
with openio(args['output'], 'w') as f:
|
with openio(args['output'], 'w') as f:
|
||||||
w = csv.DictWriter(f, ['file', 'name',
|
writer = csv.DictWriter(f, StackResult._fields)
|
||||||
*other_fields, *StackResult._fields])
|
writer.writeheader()
|
||||||
w.writeheader()
|
for r in results:
|
||||||
for (file, func), result in sorted(merged_results.items()):
|
writer.writerow(r._asdict())
|
||||||
w.writerow({'file': file, 'name': func, **result})
|
|
||||||
|
|
||||||
# print results
|
# find previous results?
|
||||||
def print_header(by):
|
if args.get('diff'):
|
||||||
if by == 'total':
|
diff_results = []
|
||||||
entry = lambda k: 'TOTAL'
|
try:
|
||||||
elif by == 'file':
|
with openio(args['diff']) as f:
|
||||||
entry = lambda k: k[0]
|
reader = csv.DictReader(f)
|
||||||
else:
|
for r in reader:
|
||||||
entry = lambda k: k[1]
|
try:
|
||||||
|
diff_results.append(StackResult(**{
|
||||||
|
k: v for k, v in r.items()
|
||||||
|
if k in StackResult._fields}))
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
if not args.get('diff'):
|
# fold to remove duplicates
|
||||||
print('%-36s %s' % (by, StackResult._header))
|
diff_results = fold(diff_results)
|
||||||
else:
|
|
||||||
old = {entry(k) for k in results.keys()}
|
|
||||||
new = {entry(k) for k in prev_results.keys()}
|
|
||||||
print('%-36s %s' % (
|
|
||||||
'%s (%d added, %d removed)' % (by,
|
|
||||||
sum(1 for k in new if k not in old),
|
|
||||||
sum(1 for k in old if k not in new))
|
|
||||||
if by else '',
|
|
||||||
StackDiff._header))
|
|
||||||
|
|
||||||
def print_entries(by):
|
# print table
|
||||||
# print optional tree of dependencies
|
if not args.get('quiet'):
|
||||||
def print_calls(entries, entry_calls, depth,
|
table(
|
||||||
filter=lambda _: True,
|
results,
|
||||||
prefixes=('', '', '', '')):
|
calls,
|
||||||
filtered_entries = {
|
diff_results if args.get('diff') else None,
|
||||||
name: result for name, result in entries.items()
|
**args)
|
||||||
if filter(name)}
|
|
||||||
for i, (name, result) in enumerate(sorted(filtered_entries.items(),
|
|
||||||
key=lambda p: (p[1].key(**args), p))):
|
|
||||||
last = (i == len(filtered_entries)-1)
|
|
||||||
print('%-36s %s' % (prefixes[0+last] + name, result))
|
|
||||||
|
|
||||||
if depth > 0 and by != 'total':
|
# error on recursion
|
||||||
calls = entry_calls.get(name, set())
|
|
||||||
print_calls(entries, entry_calls, depth-1,
|
|
||||||
lambda name: name in calls,
|
|
||||||
( prefixes[2+last] + "|-> ",
|
|
||||||
prefixes[2+last] + "'-> ",
|
|
||||||
prefixes[2+last] + "| ",
|
|
||||||
prefixes[2+last] + " "))
|
|
||||||
|
|
||||||
if by == 'total':
|
|
||||||
entry = lambda k: 'TOTAL'
|
|
||||||
elif by == 'file':
|
|
||||||
entry = lambda k: k[0]
|
|
||||||
else:
|
|
||||||
entry = lambda k: k[1]
|
|
||||||
|
|
||||||
entries = co.defaultdict(lambda: StackResult())
|
|
||||||
for k, result in results.items():
|
|
||||||
entries[entry(k)] += result
|
|
||||||
|
|
||||||
entry_calls = co.defaultdict(lambda: set())
|
|
||||||
for k, calls in result_calls.items():
|
|
||||||
entry_calls[entry(k)] |= {entry(c) for c in calls}
|
|
||||||
|
|
||||||
if not args.get('diff'):
|
|
||||||
print_calls(
|
|
||||||
entries,
|
|
||||||
entry_calls,
|
|
||||||
args.get('depth', 0))
|
|
||||||
else:
|
|
||||||
prev_entries = co.defaultdict(lambda: StackResult())
|
|
||||||
for k, result in prev_results.items():
|
|
||||||
prev_entries[entry(k)] += result
|
|
||||||
|
|
||||||
diff_entries = {name: entries.get(name) - prev_entries.get(name)
|
|
||||||
for name in (entries.keys() | prev_entries.keys())}
|
|
||||||
|
|
||||||
print_calls(
|
|
||||||
{name: diff for name, diff in diff_entries.items()
|
|
||||||
if diff or args.get('all')},
|
|
||||||
entry_calls,
|
|
||||||
args.get('depth', 0))
|
|
||||||
|
|
||||||
if args.get('quiet'):
|
|
||||||
pass
|
|
||||||
elif args.get('summary'):
|
|
||||||
print_header('')
|
|
||||||
print_entries('total')
|
|
||||||
elif args.get('files'):
|
|
||||||
print_header('file')
|
|
||||||
print_entries('file')
|
|
||||||
print_entries('total')
|
|
||||||
else:
|
|
||||||
print_header('function')
|
|
||||||
print_entries('function')
|
|
||||||
print_entries('total')
|
|
||||||
|
|
||||||
# catch recursion
|
|
||||||
if args.get('error_on_recursion') and any(
|
if args.get('error_on_recursion') and any(
|
||||||
m.isinf(limit) for _, _, _, limit, _ in results):
|
m.isinf(float(r.stack_limit)) for r in results):
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
|
|
||||||
@@ -385,45 +565,83 @@ if __name__ == "__main__":
|
|||||||
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.")
|
||||||
parser.add_argument('ci_paths', nargs='*', default=CI_PATHS,
|
parser.add_argument(
|
||||||
help="Description of where to find *.ci files. May be a directory \
|
'ci_paths',
|
||||||
or a list of paths. Defaults to %r." % CI_PATHS)
|
nargs='*',
|
||||||
parser.add_argument('-v', '--verbose', action='store_true',
|
default=CI_PATHS,
|
||||||
|
help="Description of where to find *.ci files. May be a directory "
|
||||||
|
"or a list of paths. Defaults to %r." % CI_PATHS)
|
||||||
|
parser.add_argument(
|
||||||
|
'-v', '--verbose',
|
||||||
|
action='store_true',
|
||||||
help="Output commands that run behind the scenes.")
|
help="Output commands that run behind the scenes.")
|
||||||
parser.add_argument('-q', '--quiet', action='store_true',
|
parser.add_argument(
|
||||||
|
'-q', '--quiet',
|
||||||
|
action='store_true',
|
||||||
help="Don't show anything, useful with -o.")
|
help="Don't show anything, useful with -o.")
|
||||||
parser.add_argument('-o', '--output',
|
parser.add_argument(
|
||||||
|
'-o', '--output',
|
||||||
help="Specify CSV file to store results.")
|
help="Specify CSV file to store results.")
|
||||||
parser.add_argument('-u', '--use',
|
parser.add_argument(
|
||||||
help="Don't parse callgraph files, instead use this CSV file.")
|
'-u', '--use',
|
||||||
parser.add_argument('-d', '--diff',
|
help="Don't parse anything, use this CSV file.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-d', '--diff',
|
||||||
help="Specify CSV file to diff against.")
|
help="Specify CSV file to diff against.")
|
||||||
parser.add_argument('-m', '--merge',
|
parser.add_argument(
|
||||||
help="Merge with an existing CSV file when writing to output.")
|
'-a', '--all',
|
||||||
parser.add_argument('-a', '--all', action='store_true',
|
action='store_true',
|
||||||
help="Show all functions, not just the ones that changed.")
|
help="Show all, not just the ones that changed.")
|
||||||
parser.add_argument('-A', '--everything', action='store_true',
|
parser.add_argument(
|
||||||
help="Include builtin and libc specific symbols.")
|
'-p', '--percent',
|
||||||
parser.add_argument('--frame-sort', action='store_true',
|
action='store_true',
|
||||||
help="Sort by stack frame size.")
|
help="Only show percentage change, not a full diff.")
|
||||||
parser.add_argument('--reverse-frame-sort', action='store_true',
|
parser.add_argument(
|
||||||
help="Sort by stack frame size, but backwards.")
|
'-t', '--tree',
|
||||||
parser.add_argument('-s', '--limit-sort', action='store_true',
|
action='store_true',
|
||||||
|
help="Only show the function call tree.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-b', '--by-file',
|
||||||
|
action='store_true',
|
||||||
|
help="Group by file.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-s', '--limit-sort',
|
||||||
|
action='store_true',
|
||||||
help="Sort by stack limit.")
|
help="Sort by stack limit.")
|
||||||
parser.add_argument('-S', '--reverse-limit-sort', action='store_true',
|
parser.add_argument(
|
||||||
|
'-S', '--reverse-limit-sort',
|
||||||
|
action='store_true',
|
||||||
help="Sort by stack limit, but backwards.")
|
help="Sort by stack limit, but backwards.")
|
||||||
parser.add_argument('-L', '--depth', default=0, type=lambda x: int(x, 0),
|
parser.add_argument(
|
||||||
nargs='?', const=float('inf'),
|
'--frame-sort',
|
||||||
help="Depth of dependencies to show.")
|
action='store_true',
|
||||||
parser.add_argument('-F', '--files', action='store_true',
|
help="Sort by stack frame.")
|
||||||
help="Show file-level calls.")
|
parser.add_argument(
|
||||||
parser.add_argument('-Y', '--summary', action='store_true',
|
'--reverse-frame-sort',
|
||||||
help="Only show the total stack size.")
|
action='store_true',
|
||||||
parser.add_argument('-e', '--error-on-recursion', action='store_true',
|
help="Sort by stack frame, but backwards.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-Y', '--summary',
|
||||||
|
action='store_true',
|
||||||
|
help="Only show the total size.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-L', '--depth',
|
||||||
|
nargs='?',
|
||||||
|
type=lambda x: int(x, 0),
|
||||||
|
const=float('inf'),
|
||||||
|
help="Depth of function calls to show.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-e', '--error-on-recursion',
|
||||||
|
action='store_true',
|
||||||
help="Error if any functions are recursive.")
|
help="Error if any functions are recursive.")
|
||||||
parser.add_argument('--build-dir',
|
parser.add_argument(
|
||||||
help="Specify the relative build directory. Used to map object files \
|
'-A', '--everything',
|
||||||
to the correct source files.")
|
action='store_true',
|
||||||
|
help="Include builtin and libc specific symbols.")
|
||||||
|
parser.add_argument(
|
||||||
|
'--build-dir',
|
||||||
|
help="Specify the relative build directory. Used to map object files "
|
||||||
|
"to the correct source files.")
|
||||||
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}))
|
||||||
|
|||||||
Executable
+522
@@ -0,0 +1,522 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Script to find struct sizes.
|
||||||
|
#
|
||||||
|
|
||||||
|
import collections as co
|
||||||
|
import csv
|
||||||
|
import glob
|
||||||
|
import itertools as it
|
||||||
|
import math as m
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shlex
|
||||||
|
import subprocess as sp
|
||||||
|
|
||||||
|
|
||||||
|
OBJ_PATHS = ['*.o']
|
||||||
|
OBJDUMP_TOOL = ['objdump']
|
||||||
|
|
||||||
|
|
||||||
|
# integer fields
|
||||||
|
class IntField(co.namedtuple('IntField', 'x')):
|
||||||
|
__slots__ = ()
|
||||||
|
def __new__(cls, x):
|
||||||
|
if isinstance(x, IntField):
|
||||||
|
return x
|
||||||
|
if isinstance(x, str):
|
||||||
|
try:
|
||||||
|
x = int(x, 0)
|
||||||
|
except ValueError:
|
||||||
|
# also accept +-∞ and +-inf
|
||||||
|
if re.match('^\s*\+?\s*(?:∞|inf)\s*$', x):
|
||||||
|
x = float('inf')
|
||||||
|
elif re.match('^\s*-\s*(?:∞|inf)\s*$', x):
|
||||||
|
x = float('-inf')
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
return super().__new__(cls, x)
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
assert not m.isinf(self.x)
|
||||||
|
return self.x
|
||||||
|
|
||||||
|
def __float__(self):
|
||||||
|
return float(self.x)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.x == float('inf'):
|
||||||
|
return '∞'
|
||||||
|
elif self.x == float('-inf'):
|
||||||
|
return '-∞'
|
||||||
|
else:
|
||||||
|
return str(self.x)
|
||||||
|
|
||||||
|
none = '%7s' % '-'
|
||||||
|
def table(self):
|
||||||
|
return '%7s' % (self,)
|
||||||
|
|
||||||
|
diff_none = '%7s' % '-'
|
||||||
|
diff_table = table
|
||||||
|
|
||||||
|
def diff_diff(self, other):
|
||||||
|
new = self.x if self else 0
|
||||||
|
old = other.x if other else 0
|
||||||
|
diff = new - old
|
||||||
|
if diff == float('+inf'):
|
||||||
|
return '%7s' % '+∞'
|
||||||
|
elif diff == float('-inf'):
|
||||||
|
return '%7s' % '-∞'
|
||||||
|
else:
|
||||||
|
return '%+7d' % diff
|
||||||
|
|
||||||
|
def ratio(self, other):
|
||||||
|
new = self.x if self else 0
|
||||||
|
old = other.x if other else 0
|
||||||
|
if m.isinf(new) and m.isinf(old):
|
||||||
|
return 0.0
|
||||||
|
elif m.isinf(new):
|
||||||
|
return float('+inf')
|
||||||
|
elif m.isinf(old):
|
||||||
|
return float('-inf')
|
||||||
|
elif not old and not new:
|
||||||
|
return 0.0
|
||||||
|
elif not old:
|
||||||
|
return 1.0
|
||||||
|
else:
|
||||||
|
return (new-old) / old
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
return IntField(self.x + other.x)
|
||||||
|
|
||||||
|
def __mul__(self, other):
|
||||||
|
return IntField(self.x * other.x)
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.x < other.x
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self.__class__.__lt__(other, self)
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return not self.__gt__(other)
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return not self.__lt__(other)
|
||||||
|
|
||||||
|
def __truediv__(self, n):
|
||||||
|
if m.isinf(self.x):
|
||||||
|
return self
|
||||||
|
else:
|
||||||
|
return IntField(round(self.x / n))
|
||||||
|
|
||||||
|
# struct size results
|
||||||
|
class StructResult(co.namedtuple('StructResult', 'file,struct,struct_size')):
|
||||||
|
__slots__ = ()
|
||||||
|
def __new__(cls, file, struct, struct_size):
|
||||||
|
return super().__new__(cls, file, struct, IntField(struct_size))
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
return StructResult(self.file, self.struct,
|
||||||
|
self.struct_size + other.struct_size)
|
||||||
|
|
||||||
|
|
||||||
|
def openio(path, mode='r'):
|
||||||
|
if path == '-':
|
||||||
|
if 'r' in mode:
|
||||||
|
return os.fdopen(os.dup(sys.stdin.fileno()), 'r')
|
||||||
|
else:
|
||||||
|
return os.fdopen(os.dup(sys.stdout.fileno()), 'w')
|
||||||
|
else:
|
||||||
|
return open(path, mode)
|
||||||
|
|
||||||
|
def collect(paths, *,
|
||||||
|
objdump_tool=OBJDUMP_TOOL,
|
||||||
|
build_dir=None,
|
||||||
|
everything=False,
|
||||||
|
**args):
|
||||||
|
decl_pattern = re.compile(
|
||||||
|
'^\s+(?P<no>[0-9]+)'
|
||||||
|
'\s+(?P<dir>[0-9]+)'
|
||||||
|
'\s+.*'
|
||||||
|
'\s+(?P<file>[^\s]+)$')
|
||||||
|
struct_pattern = re.compile(
|
||||||
|
'^(?:.*DW_TAG_(?P<tag>[a-z_]+).*'
|
||||||
|
'|^.*DW_AT_name.*:\s*(?P<name>[^:\s]+)\s*'
|
||||||
|
'|^.*DW_AT_decl_file.*:\s*(?P<decl>[0-9]+)\s*'
|
||||||
|
'|^.*DW_AT_byte_size.*:\s*(?P<size>[0-9]+)\s*)$')
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for path in paths:
|
||||||
|
# find decl, we want to filter by structs in .h files
|
||||||
|
decls = {}
|
||||||
|
# note objdump-tool may contain extra args
|
||||||
|
cmd = objdump_tool + ['--dwarf=rawline', path]
|
||||||
|
if args.get('verbose'):
|
||||||
|
print(' '.join(shlex.quote(c) for c in cmd))
|
||||||
|
proc = sp.Popen(cmd,
|
||||||
|
stdout=sp.PIPE,
|
||||||
|
stderr=sp.PIPE if not args.get('verbose') else None,
|
||||||
|
universal_newlines=True,
|
||||||
|
errors='replace')
|
||||||
|
for line in proc.stdout:
|
||||||
|
# find file numbers
|
||||||
|
m = decl_pattern.match(line)
|
||||||
|
if m:
|
||||||
|
decls[int(m.group('no'))] = m.group('file')
|
||||||
|
proc.wait()
|
||||||
|
if proc.returncode != 0:
|
||||||
|
if not args.get('verbose'):
|
||||||
|
for line in proc.stderr:
|
||||||
|
sys.stdout.write(line)
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
# collect structs as we parse dwarf info
|
||||||
|
found = False
|
||||||
|
name = None
|
||||||
|
decl = None
|
||||||
|
size = None
|
||||||
|
|
||||||
|
# note objdump-tool may contain extra args
|
||||||
|
cmd = objdump_tool + ['--dwarf=info', path]
|
||||||
|
if args.get('verbose'):
|
||||||
|
print(' '.join(shlex.quote(c) for c in cmd))
|
||||||
|
proc = sp.Popen(cmd,
|
||||||
|
stdout=sp.PIPE,
|
||||||
|
stderr=sp.PIPE if not args.get('verbose') else None,
|
||||||
|
universal_newlines=True,
|
||||||
|
errors='replace')
|
||||||
|
for line in proc.stdout:
|
||||||
|
# state machine here to find structs
|
||||||
|
m = struct_pattern.match(line)
|
||||||
|
if m:
|
||||||
|
if m.group('tag'):
|
||||||
|
if (name is not None
|
||||||
|
and decl is not None
|
||||||
|
and size is not None):
|
||||||
|
file = decls.get(decl, '?')
|
||||||
|
# map to source file
|
||||||
|
file = re.sub('\.o$', '.c', file)
|
||||||
|
if build_dir:
|
||||||
|
file = re.sub(
|
||||||
|
'%s/*' % re.escape(build_dir), '',
|
||||||
|
file)
|
||||||
|
# only include structs declared in header files in the
|
||||||
|
# current directory, ignore internal-only structs (
|
||||||
|
# these are represented in other measurements)
|
||||||
|
if everything or file.endswith('.h'):
|
||||||
|
results.append(StructResult(file, name, size))
|
||||||
|
|
||||||
|
found = (m.group('tag') == 'structure_type')
|
||||||
|
name = None
|
||||||
|
decl = None
|
||||||
|
size = None
|
||||||
|
elif found and m.group('name'):
|
||||||
|
name = m.group('name')
|
||||||
|
elif found and name and m.group('decl'):
|
||||||
|
decl = int(m.group('decl'))
|
||||||
|
elif found and name and m.group('size'):
|
||||||
|
size = int(m.group('size'))
|
||||||
|
proc.wait()
|
||||||
|
if proc.returncode != 0:
|
||||||
|
if not args.get('verbose'):
|
||||||
|
for line in proc.stderr:
|
||||||
|
sys.stdout.write(line)
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def fold(results, *,
|
||||||
|
by=['file', 'struct'],
|
||||||
|
**_):
|
||||||
|
folding = co.OrderedDict()
|
||||||
|
for r in results:
|
||||||
|
name = tuple(getattr(r, k) for k in by)
|
||||||
|
if name not in folding:
|
||||||
|
folding[name] = []
|
||||||
|
folding[name].append(r)
|
||||||
|
|
||||||
|
folded = []
|
||||||
|
for rs in folding.values():
|
||||||
|
folded.append(sum(rs[1:], start=rs[0]))
|
||||||
|
|
||||||
|
return folded
|
||||||
|
|
||||||
|
|
||||||
|
def table(results, diff_results=None, *,
|
||||||
|
by_file=False,
|
||||||
|
size_sort=False,
|
||||||
|
reverse_size_sort=False,
|
||||||
|
summary=False,
|
||||||
|
all=False,
|
||||||
|
percent=False,
|
||||||
|
**_):
|
||||||
|
all_, all = all, __builtins__.all
|
||||||
|
|
||||||
|
# fold
|
||||||
|
results = fold(results, by=['file' if by_file else 'struct'])
|
||||||
|
if diff_results is not None:
|
||||||
|
diff_results = fold(diff_results,
|
||||||
|
by=['file' if by_file else 'struct'])
|
||||||
|
|
||||||
|
table = {
|
||||||
|
r.file if by_file else r.struct: r
|
||||||
|
for r in results}
|
||||||
|
diff_table = {
|
||||||
|
r.file if by_file else r.struct: r
|
||||||
|
for r in diff_results or []}
|
||||||
|
|
||||||
|
# sort, note that python's sort is stable
|
||||||
|
names = list(table.keys() | diff_table.keys())
|
||||||
|
names.sort()
|
||||||
|
if diff_results is not None:
|
||||||
|
names.sort(key=lambda n: -IntField.ratio(
|
||||||
|
table[n].struct_size if n in table else None,
|
||||||
|
diff_table[n].struct_size if n in diff_table else None))
|
||||||
|
if size_sort:
|
||||||
|
names.sort(key=lambda n: (table[n].struct_size,) if n in table else (),
|
||||||
|
reverse=True)
|
||||||
|
elif reverse_size_sort:
|
||||||
|
names.sort(key=lambda n: (table[n].struct_size,) if n in table else (),
|
||||||
|
reverse=False)
|
||||||
|
|
||||||
|
# print header
|
||||||
|
print('%-36s' % ('%s%s' % (
|
||||||
|
'file' if by_file else 'struct',
|
||||||
|
' (%d added, %d removed)' % (
|
||||||
|
sum(1 for n in table if n not in diff_table),
|
||||||
|
sum(1 for n in diff_table if n not in table))
|
||||||
|
if diff_results is not None and not percent else '')
|
||||||
|
if not summary else ''),
|
||||||
|
end='')
|
||||||
|
if diff_results is None:
|
||||||
|
print(' %s' % ('size'.rjust(len(IntField.none))))
|
||||||
|
elif percent:
|
||||||
|
print(' %s' % ('size'.rjust(len(IntField.diff_none))))
|
||||||
|
else:
|
||||||
|
print(' %s %s %s' % (
|
||||||
|
'old'.rjust(len(IntField.diff_none)),
|
||||||
|
'new'.rjust(len(IntField.diff_none)),
|
||||||
|
'diff'.rjust(len(IntField.diff_none))))
|
||||||
|
|
||||||
|
# print entries
|
||||||
|
if not summary:
|
||||||
|
for name in names:
|
||||||
|
r = table.get(name)
|
||||||
|
if diff_results is not None:
|
||||||
|
diff_r = diff_table.get(name)
|
||||||
|
ratio = IntField.ratio(
|
||||||
|
r.struct_size if r else None,
|
||||||
|
diff_r.struct_size if diff_r else None)
|
||||||
|
if not ratio and not all_:
|
||||||
|
continue
|
||||||
|
|
||||||
|
print('%-36s' % name, end='')
|
||||||
|
if diff_results is None:
|
||||||
|
print(' %s' % (
|
||||||
|
r.struct_size.table()
|
||||||
|
if r else IntField.none))
|
||||||
|
elif percent:
|
||||||
|
print(' %s%s' % (
|
||||||
|
r.struct_size.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
' (%s)' % (
|
||||||
|
'+∞%' if ratio == float('+inf')
|
||||||
|
else '-∞%' if ratio == float('-inf')
|
||||||
|
else '%+.1f%%' % (100*ratio))))
|
||||||
|
else:
|
||||||
|
print(' %s %s %s%s' % (
|
||||||
|
diff_r.struct_size.diff_table()
|
||||||
|
if diff_r else IntField.diff_none,
|
||||||
|
r.struct_size.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
IntField.diff_diff(
|
||||||
|
r.struct_size if r else None,
|
||||||
|
diff_r.struct_size if diff_r else None)
|
||||||
|
if r or diff_r else IntField.diff_none,
|
||||||
|
' (%s)' % (
|
||||||
|
'+∞%' if ratio == float('+inf')
|
||||||
|
else '-∞%' if ratio == float('-inf')
|
||||||
|
else '%+.1f%%' % (100*ratio))
|
||||||
|
if ratio else ''))
|
||||||
|
|
||||||
|
# print total
|
||||||
|
total = fold(results, by=[])
|
||||||
|
r = total[0] if total else None
|
||||||
|
if diff_results is not None:
|
||||||
|
diff_total = fold(diff_results, by=[])
|
||||||
|
diff_r = diff_total[0] if diff_total else None
|
||||||
|
ratio = IntField.ratio(
|
||||||
|
r.struct_size if r else None,
|
||||||
|
diff_r.struct_size if diff_r else None)
|
||||||
|
|
||||||
|
print('%-36s' % 'TOTAL', end='')
|
||||||
|
if diff_results is None:
|
||||||
|
print(' %s' % (
|
||||||
|
r.struct_size.table()
|
||||||
|
if r else IntField.none))
|
||||||
|
elif percent:
|
||||||
|
print(' %s%s' % (
|
||||||
|
r.struct_size.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
' (%s)' % (
|
||||||
|
'+∞%' if ratio == float('+inf')
|
||||||
|
else '-∞%' if ratio == float('-inf')
|
||||||
|
else '%+.1f%%' % (100*ratio))))
|
||||||
|
else:
|
||||||
|
print(' %s %s %s%s' % (
|
||||||
|
diff_r.struct_size.diff_table()
|
||||||
|
if diff_r else IntField.diff_none,
|
||||||
|
r.struct_size.diff_table()
|
||||||
|
if r else IntField.diff_none,
|
||||||
|
IntField.diff_diff(
|
||||||
|
r.struct_size if r else None,
|
||||||
|
diff_r.struct_size if diff_r else None)
|
||||||
|
if r or diff_r else IntField.diff_none,
|
||||||
|
' (%s)' % (
|
||||||
|
'+∞%' if ratio == float('+inf')
|
||||||
|
else '-∞%' if ratio == float('-inf')
|
||||||
|
else '%+.1f%%' % (100*ratio))
|
||||||
|
if ratio else ''))
|
||||||
|
|
||||||
|
|
||||||
|
def main(obj_paths, **args):
|
||||||
|
# find sizes
|
||||||
|
if not args.get('use', None):
|
||||||
|
# find .o files
|
||||||
|
paths = []
|
||||||
|
for path in obj_paths:
|
||||||
|
if os.path.isdir(path):
|
||||||
|
path = path + '/*.o'
|
||||||
|
|
||||||
|
for path in glob.glob(path):
|
||||||
|
paths.append(path)
|
||||||
|
|
||||||
|
if not paths:
|
||||||
|
print('no .obj files found in %r?' % obj_paths)
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
results = collect(paths, **args)
|
||||||
|
else:
|
||||||
|
results = []
|
||||||
|
with openio(args['use']) as f:
|
||||||
|
reader = csv.DictReader(f)
|
||||||
|
for r in reader:
|
||||||
|
try:
|
||||||
|
results.append(StructResult(**{
|
||||||
|
k: v for k, v in r.items()
|
||||||
|
if k in StructResult._fields}))
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# fold to remove duplicates
|
||||||
|
results = fold(results)
|
||||||
|
|
||||||
|
# sort because why not
|
||||||
|
results.sort()
|
||||||
|
|
||||||
|
# write results to CSV
|
||||||
|
if args.get('output'):
|
||||||
|
with openio(args['output'], 'w') as f:
|
||||||
|
writer = csv.DictWriter(f, StructResult._fields)
|
||||||
|
writer.writeheader()
|
||||||
|
for r in results:
|
||||||
|
writer.writerow(r._asdict())
|
||||||
|
|
||||||
|
# find previous results?
|
||||||
|
if args.get('diff'):
|
||||||
|
diff_results = []
|
||||||
|
try:
|
||||||
|
with openio(args['diff']) as f:
|
||||||
|
reader = csv.DictReader(f)
|
||||||
|
for r in reader:
|
||||||
|
try:
|
||||||
|
diff_results.append(StructResult(**{
|
||||||
|
k: v for k, v in r.items()
|
||||||
|
if k in StructResult._fields}))
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# fold to remove duplicates
|
||||||
|
diff_results = fold(diff_results)
|
||||||
|
|
||||||
|
# print table
|
||||||
|
if not args.get('quiet'):
|
||||||
|
table(
|
||||||
|
results,
|
||||||
|
diff_results if args.get('diff') else None,
|
||||||
|
**args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Find struct sizes.")
|
||||||
|
parser.add_argument(
|
||||||
|
'obj_paths',
|
||||||
|
nargs='*',
|
||||||
|
default=OBJ_PATHS,
|
||||||
|
help="Description of where to find *.o files. May be a directory "
|
||||||
|
"or a list of paths. Defaults to %(default)r.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-v', '--verbose',
|
||||||
|
action='store_true',
|
||||||
|
help="Output commands that run behind the scenes.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-q', '--quiet',
|
||||||
|
action='store_true',
|
||||||
|
help="Don't show anything, useful with -o.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-o', '--output',
|
||||||
|
help="Specify CSV file to store results.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-u', '--use',
|
||||||
|
help="Don't parse anything, use this CSV file.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-d', '--diff',
|
||||||
|
help="Specify CSV file to diff against.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-a', '--all',
|
||||||
|
action='store_true',
|
||||||
|
help="Show all, not just the ones that changed.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-p', '--percent',
|
||||||
|
action='store_true',
|
||||||
|
help="Only show percentage change, not a full diff.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-b', '--by-file',
|
||||||
|
action='store_true',
|
||||||
|
help="Group by file. Note this does not include padding "
|
||||||
|
"so sizes may differ from other tools.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-s', '--size-sort',
|
||||||
|
action='store_true',
|
||||||
|
help="Sort by size.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-S', '--reverse-size-sort',
|
||||||
|
action='store_true',
|
||||||
|
help="Sort by size, but backwards.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-Y', '--summary',
|
||||||
|
action='store_true',
|
||||||
|
help="Only show the total size.")
|
||||||
|
parser.add_argument(
|
||||||
|
'-A', '--everything',
|
||||||
|
action='store_true',
|
||||||
|
help="Include builtin and libc specific symbols.")
|
||||||
|
parser.add_argument(
|
||||||
|
'--objdump-tool',
|
||||||
|
type=lambda x: x.split(),
|
||||||
|
default=OBJDUMP_TOOL,
|
||||||
|
help="Path to the objdump tool to use.")
|
||||||
|
parser.add_argument(
|
||||||
|
'--build-dir',
|
||||||
|
help="Specify the relative build directory. Used to map object files "
|
||||||
|
"to the correct source files.")
|
||||||
|
sys.exit(main(**{k: v
|
||||||
|
for k, v in vars(parser.parse_args()).items()
|
||||||
|
if v is not None}))
|
||||||
@@ -1,348 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
#
|
|
||||||
# Script to find struct sizes.
|
|
||||||
#
|
|
||||||
|
|
||||||
import os
|
|
||||||
import glob
|
|
||||||
import itertools as it
|
|
||||||
import subprocess as sp
|
|
||||||
import shlex
|
|
||||||
import re
|
|
||||||
import csv
|
|
||||||
import collections as co
|
|
||||||
|
|
||||||
|
|
||||||
OBJ_PATHS = ['*.o']
|
|
||||||
|
|
||||||
def openio(path, mode='r'):
|
|
||||||
if path == '-':
|
|
||||||
if 'r' in mode:
|
|
||||||
return os.fdopen(os.dup(sys.stdin.fileno()), 'r')
|
|
||||||
else:
|
|
||||||
return os.fdopen(os.dup(sys.stdout.fileno()), 'w')
|
|
||||||
else:
|
|
||||||
return open(path, mode)
|
|
||||||
|
|
||||||
class StructsResult(co.namedtuple('StructsResult', 'struct_size')):
|
|
||||||
__slots__ = ()
|
|
||||||
def __new__(cls, struct_size=0):
|
|
||||||
return super().__new__(cls, int(struct_size))
|
|
||||||
|
|
||||||
def __add__(self, other):
|
|
||||||
return self.__class__(self.struct_size + other.struct_size)
|
|
||||||
|
|
||||||
def __sub__(self, other):
|
|
||||||
return StructsDiff(other, self)
|
|
||||||
|
|
||||||
def __rsub__(self, other):
|
|
||||||
return self.__class__.__sub__(other, self)
|
|
||||||
|
|
||||||
def key(self, **args):
|
|
||||||
if args.get('size_sort'):
|
|
||||||
return -self.struct_size
|
|
||||||
elif args.get('reverse_size_sort'):
|
|
||||||
return +self.struct_size
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
_header = '%7s' % 'size'
|
|
||||||
def __str__(self):
|
|
||||||
return '%7d' % self.struct_size
|
|
||||||
|
|
||||||
class StructsDiff(co.namedtuple('StructsDiff', 'old,new')):
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def ratio(self):
|
|
||||||
old = self.old.struct_size if self.old is not None else 0
|
|
||||||
new = self.new.struct_size if self.new is not None else 0
|
|
||||||
return (new-old) / old if old else 1.0
|
|
||||||
|
|
||||||
def key(self, **args):
|
|
||||||
return (
|
|
||||||
self.new.key(**args) if self.new is not None else 0,
|
|
||||||
-self.ratio())
|
|
||||||
|
|
||||||
def __bool__(self):
|
|
||||||
return bool(self.ratio())
|
|
||||||
|
|
||||||
_header = '%7s %7s %7s' % ('old', 'new', 'diff')
|
|
||||||
def __str__(self):
|
|
||||||
old = self.old.struct_size if self.old is not None else 0
|
|
||||||
new = self.new.struct_size if self.new is not None else 0
|
|
||||||
diff = new - old
|
|
||||||
ratio = self.ratio()
|
|
||||||
return '%7s %7s %+7d%s' % (
|
|
||||||
old or "-",
|
|
||||||
new or "-",
|
|
||||||
diff,
|
|
||||||
' (%+.1f%%)' % (100*ratio) if ratio else '')
|
|
||||||
|
|
||||||
def collect(paths, **args):
|
|
||||||
decl_pattern = re.compile(
|
|
||||||
'^\s+(?P<no>[0-9]+)'
|
|
||||||
'\s+(?P<dir>[0-9]+)'
|
|
||||||
'\s+.*'
|
|
||||||
'\s+(?P<file>[^\s]+)$')
|
|
||||||
struct_pattern = re.compile(
|
|
||||||
'^(?:.*DW_TAG_(?P<tag>[a-z_]+).*'
|
|
||||||
'|^.*DW_AT_name.*:\s*(?P<name>[^:\s]+)\s*'
|
|
||||||
'|^.*DW_AT_decl_file.*:\s*(?P<decl>[0-9]+)\s*'
|
|
||||||
'|^.*DW_AT_byte_size.*:\s*(?P<size>[0-9]+)\s*)$')
|
|
||||||
|
|
||||||
results = {}
|
|
||||||
for path in paths:
|
|
||||||
# find decl, we want to filter by structs in .h files
|
|
||||||
decls = {}
|
|
||||||
# note objdump-tool may contain extra args
|
|
||||||
cmd = args['objdump_tool'] + ['--dwarf=rawline', path]
|
|
||||||
if args.get('verbose'):
|
|
||||||
print(' '.join(shlex.quote(c) for c in cmd))
|
|
||||||
proc = sp.Popen(cmd,
|
|
||||||
stdout=sp.PIPE,
|
|
||||||
stderr=sp.PIPE if not args.get('verbose') else None,
|
|
||||||
universal_newlines=True,
|
|
||||||
errors='replace')
|
|
||||||
for line in proc.stdout:
|
|
||||||
# find file numbers
|
|
||||||
m = decl_pattern.match(line)
|
|
||||||
if m:
|
|
||||||
decls[int(m.group('no'))] = m.group('file')
|
|
||||||
proc.wait()
|
|
||||||
if proc.returncode != 0:
|
|
||||||
if not args.get('verbose'):
|
|
||||||
for line in proc.stderr:
|
|
||||||
sys.stdout.write(line)
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
# collect structs as we parse dwarf info
|
|
||||||
found = False
|
|
||||||
name = None
|
|
||||||
decl = None
|
|
||||||
size = None
|
|
||||||
|
|
||||||
# note objdump-tool may contain extra args
|
|
||||||
cmd = args['objdump_tool'] + ['--dwarf=info', path]
|
|
||||||
if args.get('verbose'):
|
|
||||||
print(' '.join(shlex.quote(c) for c in cmd))
|
|
||||||
proc = sp.Popen(cmd,
|
|
||||||
stdout=sp.PIPE,
|
|
||||||
stderr=sp.PIPE if not args.get('verbose') else None,
|
|
||||||
universal_newlines=True,
|
|
||||||
errors='replace')
|
|
||||||
for line in proc.stdout:
|
|
||||||
# state machine here to find structs
|
|
||||||
m = struct_pattern.match(line)
|
|
||||||
if m:
|
|
||||||
if m.group('tag'):
|
|
||||||
if (name is not None
|
|
||||||
and decl is not None
|
|
||||||
and size is not None):
|
|
||||||
file = decls.get(decl, '?')
|
|
||||||
# map to source file
|
|
||||||
file = re.sub('\.o$', '.c', file)
|
|
||||||
if args.get('build_dir'):
|
|
||||||
file = re.sub(
|
|
||||||
'%s/*' % re.escape(args['build_dir']), '',
|
|
||||||
file)
|
|
||||||
# only include structs declared in header files in the
|
|
||||||
# current directory, ignore internal-only structs (
|
|
||||||
# these are represented in other measurements)
|
|
||||||
if args.get('everything') or file.endswith('.h'):
|
|
||||||
results[(file, name)] = StructsResult(size)
|
|
||||||
found = (m.group('tag') == 'structure_type')
|
|
||||||
name = None
|
|
||||||
decl = None
|
|
||||||
size = None
|
|
||||||
elif found and m.group('name'):
|
|
||||||
name = m.group('name')
|
|
||||||
elif found and name and m.group('decl'):
|
|
||||||
decl = int(m.group('decl'))
|
|
||||||
elif found and name and m.group('size'):
|
|
||||||
size = int(m.group('size'))
|
|
||||||
proc.wait()
|
|
||||||
if proc.returncode != 0:
|
|
||||||
if not args.get('verbose'):
|
|
||||||
for line in proc.stderr:
|
|
||||||
sys.stdout.write(line)
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
def main(**args):
|
|
||||||
# find sizes
|
|
||||||
if not args.get('use', None):
|
|
||||||
# find .o files
|
|
||||||
paths = []
|
|
||||||
for path in args['obj_paths']:
|
|
||||||
if os.path.isdir(path):
|
|
||||||
path = path + '/*.o'
|
|
||||||
|
|
||||||
for path in glob.glob(path):
|
|
||||||
paths.append(path)
|
|
||||||
|
|
||||||
if not paths:
|
|
||||||
print('no .obj files found in %r?' % args['obj_paths'])
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
results = collect(paths, **args)
|
|
||||||
else:
|
|
||||||
with openio(args['use']) as f:
|
|
||||||
r = csv.DictReader(f)
|
|
||||||
results = {
|
|
||||||
(result['file'], result['name']): StructsResult(
|
|
||||||
*(result[f] for f in StructsResult._fields))
|
|
||||||
for result in r
|
|
||||||
if all(result.get(f) not in {None, ''}
|
|
||||||
for f in StructsResult._fields)}
|
|
||||||
|
|
||||||
# find previous results?
|
|
||||||
if args.get('diff'):
|
|
||||||
try:
|
|
||||||
with openio(args['diff']) as f:
|
|
||||||
r = csv.DictReader(f)
|
|
||||||
prev_results = {
|
|
||||||
(result['file'], result['name']): StructsResult(
|
|
||||||
*(result[f] for f in StructsResult._fields))
|
|
||||||
for result in r
|
|
||||||
if all(result.get(f) not in {None, ''}
|
|
||||||
for f in StructsResult._fields)}
|
|
||||||
except FileNotFoundError:
|
|
||||||
prev_results = []
|
|
||||||
|
|
||||||
# write results to CSV
|
|
||||||
if args.get('output'):
|
|
||||||
merged_results = co.defaultdict(lambda: {})
|
|
||||||
other_fields = []
|
|
||||||
|
|
||||||
# merge?
|
|
||||||
if args.get('merge'):
|
|
||||||
try:
|
|
||||||
with openio(args['merge']) as f:
|
|
||||||
r = csv.DictReader(f)
|
|
||||||
for result in r:
|
|
||||||
file = result.pop('file', '')
|
|
||||||
func = result.pop('name', '')
|
|
||||||
for f in StructsResult._fields:
|
|
||||||
result.pop(f, None)
|
|
||||||
merged_results[(file, func)] = result
|
|
||||||
other_fields = result.keys()
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for (file, func), result in results.items():
|
|
||||||
merged_results[(file, func)] |= result._asdict()
|
|
||||||
|
|
||||||
with openio(args['output'], 'w') as f:
|
|
||||||
w = csv.DictWriter(f, ['file', 'name',
|
|
||||||
*other_fields, *StructsResult._fields])
|
|
||||||
w.writeheader()
|
|
||||||
for (file, func), result in sorted(merged_results.items()):
|
|
||||||
w.writerow({'file': file, 'name': func, **result})
|
|
||||||
|
|
||||||
# print results
|
|
||||||
def print_header(by):
|
|
||||||
if by == 'total':
|
|
||||||
entry = lambda k: 'TOTAL'
|
|
||||||
elif by == 'file':
|
|
||||||
entry = lambda k: k[0]
|
|
||||||
else:
|
|
||||||
entry = lambda k: k[1]
|
|
||||||
|
|
||||||
if not args.get('diff'):
|
|
||||||
print('%-36s %s' % (by, StructsResult._header))
|
|
||||||
else:
|
|
||||||
old = {entry(k) for k in results.keys()}
|
|
||||||
new = {entry(k) for k in prev_results.keys()}
|
|
||||||
print('%-36s %s' % (
|
|
||||||
'%s (%d added, %d removed)' % (by,
|
|
||||||
sum(1 for k in new if k not in old),
|
|
||||||
sum(1 for k in old if k not in new))
|
|
||||||
if by else '',
|
|
||||||
StructsDiff._header))
|
|
||||||
|
|
||||||
def print_entries(by):
|
|
||||||
if by == 'total':
|
|
||||||
entry = lambda k: 'TOTAL'
|
|
||||||
elif by == 'file':
|
|
||||||
entry = lambda k: k[0]
|
|
||||||
else:
|
|
||||||
entry = lambda k: k[1]
|
|
||||||
|
|
||||||
entries = co.defaultdict(lambda: StructsResult())
|
|
||||||
for k, result in results.items():
|
|
||||||
entries[entry(k)] += result
|
|
||||||
|
|
||||||
if not args.get('diff'):
|
|
||||||
for name, result in sorted(entries.items(),
|
|
||||||
key=lambda p: (p[1].key(**args), p)):
|
|
||||||
print('%-36s %s' % (name, result))
|
|
||||||
else:
|
|
||||||
prev_entries = co.defaultdict(lambda: StructsResult())
|
|
||||||
for k, result in prev_results.items():
|
|
||||||
prev_entries[entry(k)] += result
|
|
||||||
|
|
||||||
diff_entries = {name: entries.get(name) - prev_entries.get(name)
|
|
||||||
for name in (entries.keys() | prev_entries.keys())}
|
|
||||||
|
|
||||||
for name, diff in sorted(diff_entries.items(),
|
|
||||||
key=lambda p: (p[1].key(**args), p)):
|
|
||||||
if diff or args.get('all'):
|
|
||||||
print('%-36s %s' % (name, diff))
|
|
||||||
|
|
||||||
if args.get('quiet'):
|
|
||||||
pass
|
|
||||||
elif args.get('summary'):
|
|
||||||
print_header('')
|
|
||||||
print_entries('total')
|
|
||||||
elif args.get('files'):
|
|
||||||
print_header('file')
|
|
||||||
print_entries('file')
|
|
||||||
print_entries('total')
|
|
||||||
else:
|
|
||||||
print_header('struct')
|
|
||||||
print_entries('struct')
|
|
||||||
print_entries('total')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import argparse
|
|
||||||
import sys
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Find struct sizes.")
|
|
||||||
parser.add_argument('obj_paths', nargs='*', default=OBJ_PATHS,
|
|
||||||
help="Description of where to find *.o files. May be a directory \
|
|
||||||
or a list of paths. Defaults to %r." % OBJ_PATHS)
|
|
||||||
parser.add_argument('-v', '--verbose', action='store_true',
|
|
||||||
help="Output commands that run behind the scenes.")
|
|
||||||
parser.add_argument('-q', '--quiet', action='store_true',
|
|
||||||
help="Don't show anything, useful with -o.")
|
|
||||||
parser.add_argument('-o', '--output',
|
|
||||||
help="Specify CSV file to store results.")
|
|
||||||
parser.add_argument('-u', '--use',
|
|
||||||
help="Don't compile and find struct sizes, instead use this CSV file.")
|
|
||||||
parser.add_argument('-d', '--diff',
|
|
||||||
help="Specify CSV file to diff struct size against.")
|
|
||||||
parser.add_argument('-m', '--merge',
|
|
||||||
help="Merge with an existing CSV file when writing to output.")
|
|
||||||
parser.add_argument('-a', '--all', action='store_true',
|
|
||||||
help="Show all structs, not just the ones that changed.")
|
|
||||||
parser.add_argument('-A', '--everything', action='store_true',
|
|
||||||
help="Include builtin and libc specific symbols.")
|
|
||||||
parser.add_argument('-s', '--size-sort', action='store_true',
|
|
||||||
help="Sort by size.")
|
|
||||||
parser.add_argument('-S', '--reverse-size-sort', action='store_true',
|
|
||||||
help="Sort by size, but backwards.")
|
|
||||||
parser.add_argument('-F', '--files', action='store_true',
|
|
||||||
help="Show file-level struct sizes.")
|
|
||||||
parser.add_argument('-Y', '--summary', action='store_true',
|
|
||||||
help="Only show the total struct size.")
|
|
||||||
parser.add_argument('--objdump-tool', default=['objdump'], type=lambda x: x.split(),
|
|
||||||
help="Path to the objdump tool to use.")
|
|
||||||
parser.add_argument('--build-dir',
|
|
||||||
help="Specify the relative build directory. Used to map object files \
|
|
||||||
to the correct source files.")
|
|
||||||
sys.exit(main(**{k: v
|
|
||||||
for k, v in vars(parser.parse_args()).items()
|
|
||||||
if v is not None}))
|
|
||||||
+674
-371
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user