Added recursive results to perf.py

This adds -P/--propagate and -Z/--depth to perf.py for showing recursive
results, making it easy to narrow down on where spikes in performance
come from.

This ended up being a bit different from stack.py's recursive results,
as we end up with different (diminishing) numbers as we descend.
This commit is contained in:
Christopher Haster
2022-10-08 20:49:44 -05:00
parent 490e1c4616
commit df283aeb48
8 changed files with 680 additions and 843 deletions
+1 -1
View File
@@ -231,7 +231,7 @@ coverage: $(GCDA)
perf: $(BENCH_PERF) perf: $(BENCH_PERF)
$(strip ./scripts/perf.py \ $(strip ./scripts/perf.py \
$^ $(patsubst %,-F%,$(SRC)) \ $^ $(patsubst %,-F%,$(SRC)) \
-scycles \ -Scycles \
$(PERFFLAGS)) $(PERFFLAGS))
.PHONY: summary sizes .PHONY: summary sizes
+67 -99
View File
@@ -257,6 +257,8 @@ def collect(paths, *,
f_name = m.group('name') f_name = m.group('name')
elif m.group('file'): elif m.group('file'):
f_file = int(m.group('file')) f_file = int(m.group('file'))
if is_func:
defs[f_name] = files.get(f_file, '?')
proc.wait() proc.wait()
if proc.returncode != 0: if proc.returncode != 0:
if not args.get('verbose'): if not args.get('verbose'):
@@ -303,7 +305,7 @@ def collect(paths, *,
else: else:
file = os.path.abspath(file) file = os.path.abspath(file)
results.append(CodeResult(file, r.function, r.size)) results.append(r._replace(file=file))
return results return results
@@ -393,8 +395,8 @@ def table(Result, results, diff_results=None, *,
lines = [] lines = []
# header # header
line = [] header = []
line.append('%s%s' % ( header.append('%s%s' % (
','.join(by), ','.join(by),
' (%d added, %d removed)' % ( ' (%d added, %d removed)' % (
sum(1 for n in table if n not in diff_table), sum(1 for n in table if n not in diff_table),
@@ -403,129 +405,95 @@ def table(Result, results, diff_results=None, *,
if not summary else '') if not summary else '')
if diff_results is None: if diff_results is None:
for k in fields: for k in fields:
line.append(k) header.append(k)
elif percent: elif percent:
for k in fields: for k in fields:
line.append(k) header.append(k)
else: else:
for k in fields: for k in fields:
line.append('o'+k) header.append('o'+k)
for k in fields: for k in fields:
line.append('n'+k) header.append('n'+k)
for k in fields: for k in fields:
line.append('d'+k) header.append('d'+k)
line.append('') header.append('')
lines.append(line) lines.append(header)
def table_entry(name, r, diff_r=None, ratios=[]):
entry = []
entry.append(name)
if diff_results is None:
for k in fields:
entry.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
entry.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
entry.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
entry.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
entry.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
entry.append('')
elif percent:
entry.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
entry.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
return entry
# entries # entries
if not summary: if not summary:
for name in names: for name in names:
r = table.get(name) r = table.get(name)
if diff_results is not None: if diff_results is None:
diff_r = None
ratios = None
else:
diff_r = diff_table.get(name) diff_r = diff_table.get(name)
ratios = [ ratios = [
types[k].ratio( types[k].ratio(
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None)) getattr(diff_r, k, None))
for k in fields] for k in fields]
if not any(ratios) and not all_: if not all_ and not any(ratios):
continue continue
lines.append(table_entry(name, r, diff_r, ratios))
line = []
line.append(name)
if diff_results is None:
for k in fields:
line.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
line.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
line.append('')
elif percent:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
lines.append(line)
# total # total
r = next(iter(fold(Result, results, by=[])), None) r = next(iter(fold(Result, results, by=[])), None)
if diff_results is not None: if diff_results is None:
diff_r = None
ratios = None
else:
diff_r = next(iter(fold(Result, diff_results, by=[])), None) diff_r = next(iter(fold(Result, diff_results, by=[])), None)
ratios = [ ratios = [
types[k].ratio( types[k].ratio(
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None)) getattr(diff_r, k, None))
for k in fields] for k in fields]
lines.append(table_entry('TOTAL', r, diff_r, ratios))
line = []
line.append('TOTAL')
if diff_results is None:
for k in fields:
line.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
line.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
line.append('')
elif percent:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
lines.append(line)
# find the best widths, note that column 0 contains the names and column -1 # find the best widths, note that column 0 contains the names and column -1
# the ratios, so those are handled a bit differently # the ratios, so those are handled a bit differently
+64 -98
View File
@@ -383,8 +383,8 @@ def table(Result, results, diff_results=None, *,
lines = [] lines = []
# header # header
line = [] header = []
line.append('%s%s' % ( header.append('%s%s' % (
','.join(by), ','.join(by),
' (%d added, %d removed)' % ( ' (%d added, %d removed)' % (
sum(1 for n in table if n not in diff_table), sum(1 for n in table if n not in diff_table),
@@ -393,129 +393,95 @@ def table(Result, results, diff_results=None, *,
if not summary else '') if not summary else '')
if diff_results is None: if diff_results is None:
for k in fields: for k in fields:
line.append(k) header.append(k)
elif percent: elif percent:
for k in fields: for k in fields:
line.append(k) header.append(k)
else: else:
for k in fields: for k in fields:
line.append('o'+k) header.append('o'+k)
for k in fields: for k in fields:
line.append('n'+k) header.append('n'+k)
for k in fields: for k in fields:
line.append('d'+k) header.append('d'+k)
line.append('') header.append('')
lines.append(line) lines.append(header)
def table_entry(name, r, diff_r=None, ratios=[]):
entry = []
entry.append(name)
if diff_results is None:
for k in fields:
entry.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
entry.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
entry.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
entry.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
entry.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
entry.append('')
elif percent:
entry.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
entry.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
return entry
# entries # entries
if not summary: if not summary:
for name in names: for name in names:
r = table.get(name) r = table.get(name)
if diff_results is not None: if diff_results is None:
diff_r = None
ratios = None
else:
diff_r = diff_table.get(name) diff_r = diff_table.get(name)
ratios = [ ratios = [
types[k].ratio( types[k].ratio(
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None)) getattr(diff_r, k, None))
for k in fields] for k in fields]
if not any(ratios) and not all_: if not all_ and not any(ratios):
continue continue
lines.append(table_entry(name, r, diff_r, ratios))
line = []
line.append(name)
if diff_results is None:
for k in fields:
line.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
line.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
line.append('')
elif percent:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
lines.append(line)
# total # total
r = next(iter(fold(Result, results, by=[])), None) r = next(iter(fold(Result, results, by=[])), None)
if diff_results is not None: if diff_results is None:
diff_r = None
ratios = None
else:
diff_r = next(iter(fold(Result, diff_results, by=[])), None) diff_r = next(iter(fold(Result, diff_results, by=[])), None)
ratios = [ ratios = [
types[k].ratio( types[k].ratio(
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None)) getattr(diff_r, k, None))
for k in fields] for k in fields]
lines.append(table_entry('TOTAL', r, diff_r, ratios))
line = []
line.append('TOTAL')
if diff_results is None:
for k in fields:
line.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
line.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
line.append('')
elif percent:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
lines.append(line)
# find the best widths, note that column 0 contains the names and column -1 # find the best widths, note that column 0 contains the names and column -1
# the ratios, so those are handled a bit differently # the ratios, so those are handled a bit differently
+67 -99
View File
@@ -257,6 +257,8 @@ def collect(paths, *,
f_name = m.group('name') f_name = m.group('name')
elif m.group('file'): elif m.group('file'):
f_file = int(m.group('file')) f_file = int(m.group('file'))
if is_func:
defs[f_name] = files.get(f_file, '?')
proc.wait() proc.wait()
if proc.returncode != 0: if proc.returncode != 0:
if not args.get('verbose'): if not args.get('verbose'):
@@ -303,7 +305,7 @@ def collect(paths, *,
else: else:
file = os.path.abspath(file) file = os.path.abspath(file)
results.append(DataResult(file, r.function, r.size)) results.append(r._replace(file=file))
return results return results
@@ -393,8 +395,8 @@ def table(Result, results, diff_results=None, *,
lines = [] lines = []
# header # header
line = [] header = []
line.append('%s%s' % ( header.append('%s%s' % (
','.join(by), ','.join(by),
' (%d added, %d removed)' % ( ' (%d added, %d removed)' % (
sum(1 for n in table if n not in diff_table), sum(1 for n in table if n not in diff_table),
@@ -403,129 +405,95 @@ def table(Result, results, diff_results=None, *,
if not summary else '') if not summary else '')
if diff_results is None: if diff_results is None:
for k in fields: for k in fields:
line.append(k) header.append(k)
elif percent: elif percent:
for k in fields: for k in fields:
line.append(k) header.append(k)
else: else:
for k in fields: for k in fields:
line.append('o'+k) header.append('o'+k)
for k in fields: for k in fields:
line.append('n'+k) header.append('n'+k)
for k in fields: for k in fields:
line.append('d'+k) header.append('d'+k)
line.append('') header.append('')
lines.append(line) lines.append(header)
def table_entry(name, r, diff_r=None, ratios=[]):
entry = []
entry.append(name)
if diff_results is None:
for k in fields:
entry.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
entry.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
entry.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
entry.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
entry.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
entry.append('')
elif percent:
entry.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
entry.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
return entry
# entries # entries
if not summary: if not summary:
for name in names: for name in names:
r = table.get(name) r = table.get(name)
if diff_results is not None: if diff_results is None:
diff_r = None
ratios = None
else:
diff_r = diff_table.get(name) diff_r = diff_table.get(name)
ratios = [ ratios = [
types[k].ratio( types[k].ratio(
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None)) getattr(diff_r, k, None))
for k in fields] for k in fields]
if not any(ratios) and not all_: if not all_ and not any(ratios):
continue continue
lines.append(table_entry(name, r, diff_r, ratios))
line = []
line.append(name)
if diff_results is None:
for k in fields:
line.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
line.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
line.append('')
elif percent:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
lines.append(line)
# total # total
r = next(iter(fold(Result, results, by=[])), None) r = next(iter(fold(Result, results, by=[])), None)
if diff_results is not None: if diff_results is None:
diff_r = None
ratios = None
else:
diff_r = next(iter(fold(Result, diff_results, by=[])), None) diff_r = next(iter(fold(Result, diff_results, by=[])), None)
ratios = [ ratios = [
types[k].ratio( types[k].ratio(
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None)) getattr(diff_r, k, None))
for k in fields] for k in fields]
lines.append(table_entry('TOTAL', r, diff_r, ratios))
line = []
line.append('TOTAL')
if diff_results is None:
for k in fields:
line.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
line.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
line.append('')
elif percent:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
lines.append(line)
# find the best widths, note that column 0 contains the names and column -1 # find the best widths, note that column 0 contains the names and column -1
# the ratios, so those are handled a bit differently # the ratios, so those are handled a bit differently
+263 -224
View File
@@ -28,6 +28,8 @@ import subprocess as sp
import tempfile import tempfile
import zipfile import zipfile
# TODO support non-zip perf results?
PERF_PATHS = ['*.perf'] PERF_PATHS = ['*.perf']
PERF_TOOL = ['perf'] PERF_TOOL = ['perf']
@@ -118,61 +120,31 @@ class Int(co.namedtuple('Int', 'x')):
# perf results # perf results
class PerfResult(co.namedtuple('PerfResult', [ class PerfResult(co.namedtuple('PerfResult', [
'file', 'function', 'line', 'file', 'function', 'line',
'self_cycles', 'cycles', 'bmisses', 'branches', 'cmisses', 'caches',
'self_bmisses', 'self_branches', 'children'])):
'self_cmisses', 'self_caches',
'cycles',
'bmisses', 'branches',
'cmisses', 'caches',
'children', 'parents'])):
_by = ['file', 'function', 'line'] _by = ['file', 'function', 'line']
_fields = [ _fields = ['cycles', 'bmisses', 'branches', 'cmisses', 'caches']
'self_cycles',
'self_bmisses', 'self_branches',
'self_cmisses', 'self_caches',
'cycles',
'bmisses', 'branches',
'cmisses', 'caches']
_types = { _types = {
'self_cycles': Int,
'self_bmisses': Int, 'self_branches': Int,
'self_cmisses': Int, 'self_caches': Int,
'cycles': Int, 'cycles': Int,
'bmisses': Int, 'branches': Int, 'bmisses': Int, 'branches': Int,
'cmisses': Int, 'caches': Int} 'cmisses': Int, 'caches': Int}
__slots__ = () __slots__ = ()
def __new__(cls, file='', function='', line=0, def __new__(cls, file='', function='', line=0,
self_cycles=0, cycles=0, bmisses=0, branches=0, cmisses=0, caches=0,
self_bmisses=0, self_branches=0, children=[]):
self_cmisses=0, self_caches=0,
cycles=0,
bmisses=0, branches=0,
cmisses=0, caches=0,
children=set(), parents=set()):
return super().__new__(cls, file, function, int(Int(line)), return super().__new__(cls, file, function, int(Int(line)),
Int(self_cycles), Int(cycles), Int(bmisses), Int(branches), Int(cmisses), Int(caches),
Int(self_bmisses), Int(self_branches), children)
Int(self_cmisses), Int(self_caches),
Int(cycles),
Int(bmisses), Int(branches),
Int(cmisses), Int(caches),
children, parents)
def __add__(self, other): def __add__(self, other):
return PerfResult(self.file, self.function, self.line, return PerfResult(self.file, self.function, self.line,
self.self_cycles + other.self_cycles,
self.self_bmisses + other.self_bmisses,
self.self_branches + other.self_branches,
self.self_cmisses + other.self_cmisses,
self.self_caches + other.self_caches,
self.cycles + other.cycles, self.cycles + other.cycles,
self.bmisses + other.bmisses, self.bmisses + other.bmisses,
self.branches + other.branches, self.branches + other.branches,
self.cmisses + other.cmisses, self.cmisses + other.cmisses,
self.caches + other.caches, self.caches + other.caches,
self.children | other.children, self.children + other.children)
self.parents | other.parents)
def openio(path, mode='r'): def openio(path, mode='r'):
@@ -245,7 +217,8 @@ def record(command, *,
def collect_decompressed(path, *, def collect_decompressed(path, *,
perf_tool=PERF_TOOL, perf_tool=PERF_TOOL,
everything=False, everything=False,
depth=0, propagate=0,
depth=1,
**args): **args):
sample_pattern = re.compile( sample_pattern = re.compile(
'(?P<comm>\w+)' '(?P<comm>\w+)'
@@ -278,10 +251,27 @@ def collect_decompressed(path, *,
close_fds=False) close_fds=False)
last_filtered = False last_filtered = False
last_has_frame = False
last_event = '' last_event = ''
last_period = 0 last_period = 0
results = co.defaultdict(lambda: co.defaultdict(lambda: (0, 0))) last_stack = []
results = {}
def commit():
# tail-recursively propagate measurements
for i in range(len(last_stack)):
results_ = results
for j in reversed(range(i+1)):
if i+1-j > depth:
break
# propagate
name = last_stack[j]
if name not in results_:
results_[name] = (co.defaultdict(lambda: 0), {})
results_[name][0][last_event] += last_period
# recurse
results_ = results_[name][1]
for line in proc.stdout: for line in proc.stdout:
# we need to process a lot of data, so wait to use regex as late # we need to process a lot of data, so wait to use regex as late
@@ -291,10 +281,12 @@ def collect_decompressed(path, *,
if not line.startswith('\t'): if not line.startswith('\t'):
m = sample_pattern.match(line) m = sample_pattern.match(line)
if m: if m:
if last_stack:
commit()
last_event = m.group('event') last_event = m.group('event')
last_filtered = last_event in events last_filtered = last_event in events
last_period = int(m.group('period'), 0) last_period = int(m.group('period'), 0)
last_has_frame = False last_stack = []
elif last_filtered: elif last_filtered:
m = frame_pattern.match(line) m = frame_pattern.match(line)
if m: if m:
@@ -305,20 +297,16 @@ def collect_decompressed(path, *,
or not m.group('sym')[:1].isalpha()): or not m.group('sym')[:1].isalpha()):
continue continue
name = ( last_stack.append((
m.group('dso'), m.group('dso'),
m.group('sym'), m.group('sym'),
int(m.group('addr'), 16)) int(m.group('addr'), 16)))
self, total = results[name][last_event]
if not last_has_frame: # stop propogating?
results[name][last_event] = ( if propagate and len(last_stack) >= propagate:
self + last_period, last_filtered = False
total + last_period) if last_stack:
last_has_frame = True commit()
else:
results[name][last_event] = (
self,
total + last_period)
proc.wait() proc.wait()
if proc.returncode != 0: if proc.returncode != 0:
@@ -328,14 +316,15 @@ def collect_decompressed(path, *,
sys.exit(-1) sys.exit(-1)
# rearrange results into result type # rearrange results into result type
results_ = [] def to_results(results):
for name, r in results.items(): results_ = []
results_.append(PerfResult(*name, for name, (r, children) in results.items():
**{'self_'+events[e]: s for e, (s, _) in r.items()}, results_.append(PerfResult(*name,
**{ events[e]: t for e, (_, t) in r.items()})) **{events[k]: v for k, v in r.items()},
results = results_ children=to_results(children)))
return results_
return results return to_results(results)
def collect_job(path, i, **args): def collect_job(path, i, **args):
# decompress into a temporary file, this is to work around # decompress into a temporary file, this is to work around
@@ -567,38 +556,46 @@ def collect(paths, *,
default=0) default=0)
# then try to map addrs -> file+line # then try to map addrs -> file+line
for r in results_: #
addr = r.line + delta # note we need to do this recursively
i = bisect.bisect(line_at, addr, key=lambda x: x[0]) def remap(results):
if i > 0: results_ = []
_, file, line = line_at[i-1] for r in results:
else: addr = r.line + delta
file, line = re.sub('(\.o)?$', '.c', r.file, 1), 0 i = bisect.bisect(line_at, addr, key=lambda x: x[0])
if i > 0:
_, file, line = line_at[i-1]
else:
file, line = re.sub('(\.o)?$', '.c', r.file, 1), 0
# ignore filtered sources # ignore filtered sources
if sources is not None: if sources is not None:
if not any( if not any(
os.path.abspath(file) == os.path.abspath(s) os.path.abspath(file) == os.path.abspath(s)
for s in sources): for s in sources):
continue continue
else: else:
# default to only cwd # default to only cwd
if not everything and not os.path.commonpath([ if not everything and not os.path.commonpath([
os.getcwd(),
os.path.abspath(file)]) == os.getcwd():
continue
# simplify path
if os.path.commonpath([
os.getcwd(), os.getcwd(),
os.path.abspath(file)]) == os.getcwd(): os.path.abspath(file)]) == os.getcwd():
continue file = os.path.relpath(file)
else:
file = os.path.abspath(file)
# simplify path function, *_ = r.function.split('+', 1)
if os.path.commonpath([ results_.append(r._replace(
os.getcwd(), file=file, function=function, line=line,
os.path.abspath(file)]) == os.getcwd(): children=remap(r.children)))
file = os.path.relpath(file) return results_
else:
file = os.path.abspath(file)
function, *_ = r.function.split('+', 1) results.extend(remap(results_))
results.append(PerfResult(file, function, line,
**{k: getattr(r, k) for k in PerfResult._fields}))
return results return results
@@ -636,6 +633,15 @@ def fold(Result, results, *,
for name, rs in folding.items(): for name, rs in folding.items():
folded.append(sum(rs[1:], start=rs[0])) folded.append(sum(rs[1:], start=rs[0]))
# fold recursively
folded_ = []
for r in folded:
folded_.append(r._replace(children=fold(
Result, r.children,
by=by,
defines=defines)))
folded = folded_
return folded return folded
def table(Result, results, diff_results=None, *, def table(Result, results, diff_results=None, *,
@@ -645,6 +651,7 @@ def table(Result, results, diff_results=None, *,
summary=False, summary=False,
all=False, all=False,
percent=False, percent=False,
depth=1,
**_): **_):
all_, all = all, __builtins__.all all_, all = all, __builtins__.all
@@ -683,13 +690,12 @@ def table(Result, results, diff_results=None, *,
if getattr(table.get(n), k, None) is not None else (), if getattr(table.get(n), k, None) is not None else (),
reverse=reverse ^ (not k or k in Result._fields)) reverse=reverse ^ (not k or k in Result._fields))
# build up our lines # build up our lines
lines = [] lines = []
# header # header
line = [] header = []
line.append('%s%s' % ( header.append('%s%s' % (
','.join(by), ','.join(by),
' (%d added, %d removed)' % ( ' (%d added, %d removed)' % (
sum(1 for n in table if n not in diff_table), sum(1 for n in table if n not in diff_table),
@@ -698,129 +704,95 @@ def table(Result, results, diff_results=None, *,
if not summary else '') if not summary else '')
if diff_results is None: if diff_results is None:
for k in fields: for k in fields:
line.append(k) header.append(k)
elif percent: elif percent:
for k in fields: for k in fields:
line.append(k) header.append(k)
else: else:
for k in fields: for k in fields:
line.append('o'+k) header.append('o'+k)
for k in fields: for k in fields:
line.append('n'+k) header.append('n'+k)
for k in fields: for k in fields:
line.append('d'+k) header.append('d'+k)
line.append('') header.append('')
lines.append(line) lines.append(header)
def table_entry(name, r, diff_r=None, ratios=[]):
entry = []
entry.append(name)
if diff_results is None:
for k in fields:
entry.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
entry.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
entry.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
entry.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
entry.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
entry.append('')
elif percent:
entry.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
entry.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
return entry
# entries # entries
if not summary: if not summary:
for name in names: for name in names:
r = table.get(name) r = table.get(name)
if diff_results is not None: if diff_results is None:
diff_r = None
ratios = None
else:
diff_r = diff_table.get(name) diff_r = diff_table.get(name)
ratios = [ ratios = [
types[k].ratio( types[k].ratio(
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None)) getattr(diff_r, k, None))
for k in fields] for k in fields]
if not any(ratios) and not all_: if not all_ and not any(ratios):
continue continue
lines.append(table_entry(name, r, diff_r, ratios))
line = []
line.append(name)
if diff_results is None:
for k in fields:
line.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
line.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
line.append('')
elif percent:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
lines.append(line)
# total # total
r = next(iter(fold(Result, results, by=[])), None) r = next(iter(fold(Result, results, by=[])), None)
if diff_results is not None: if diff_results is None:
diff_r = None
ratios = None
else:
diff_r = next(iter(fold(Result, diff_results, by=[])), None) diff_r = next(iter(fold(Result, diff_results, by=[])), None)
ratios = [ ratios = [
types[k].ratio( types[k].ratio(
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None)) getattr(diff_r, k, None))
for k in fields] for k in fields]
lines.append(table_entry('TOTAL', r, diff_r, ratios))
line = []
line.append('TOTAL')
if diff_results is None:
for k in fields:
line.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
line.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
line.append('')
elif percent:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
lines.append(line)
# find the best widths, note that column 0 contains the names and column -1 # find the best widths, note that column 0 contains the names and column -1
# the ratios, so those are handled a bit differently # the ratios, so those are handled a bit differently
@@ -830,13 +802,83 @@ def table(Result, results, diff_results=None, *,
it.chain([23], it.repeat(7)), it.chain([23], it.repeat(7)),
range(len(lines[0])-1))] range(len(lines[0])-1))]
# print our table # adjust the name width based on the expected call depth, though
for line in lines: # note this doesn't really work with unbounded recursion
print('%-*s %s%s' % ( if not summary and not m.isinf(depth):
widths[0], line[0], widths[0] += 4*(depth-1)
' '.join('%*s' % (w, x)
for w, x in zip(widths[1:], line[1:-1])), # print the tree recursively
line[-1])) print('%-*s %s%s' % (
widths[0], lines[0][0],
' '.join('%*s' % (w, x)
for w, x in zip(widths[1:], lines[0][1:-1])),
lines[0][-1]))
if not summary:
def recurse(results_, depth_, prefixes=('', '', '', '')):
# rebuild our tables at each layer
table_ = {
','.join(str(getattr(r, k) or '') for k in by): r
for r in results_}
names_ = list(table_.keys())
# sort again at each layer, keep in mind the numbers are
# changing as we descend
names_.sort()
if sort:
for k, reverse in reversed(sort):
names_.sort(key=lambda n: (getattr(table_[n], k),)
if getattr(table_.get(n), k, None) is not None else (),
reverse=reverse ^ (not k or k in Result._fields))
for i, name in enumerate(names_):
r = table_[name]
is_last = (i == len(names_)-1)
print('%s%-*s %s' % (
prefixes[0+is_last],
widths[0] - (
len(prefixes[0+is_last])
if not m.isinf(depth) else 0),
name,
' '.join('%*s' % (w, x)
for w, x in zip(
widths[1:],
table_entry(name, r)[1:]))))
# recurse?
if depth_ > 1:
recurse(
r.children,
depth_-1,
(prefixes[2+is_last] + "|-> ",
prefixes[2+is_last] + "'-> ",
prefixes[2+is_last] + "| ",
prefixes[2+is_last] + " "))
# we have enough going on with diffing to make the top layer
# a special case
for name, line in zip(names, lines[1:-1]):
print('%-*s %s%s' % (
widths[0], line[0],
' '.join('%*s' % (w, x)
for w, x in zip(widths[1:], line[1:-1])),
line[-1]))
if name in table and depth > 1:
recurse(
table[name].children,
depth-1,
("|-> ",
"'-> ",
"| ",
" "))
print('%-*s %s%s' % (
widths[0], lines[-1][0],
' '.join('%*s' % (w, x)
for w, x in zip(widths[1:], lines[-1][1:-1])),
lines[-1][-1]))
def annotate(Result, results, *, def annotate(Result, results, *,
@@ -855,11 +897,11 @@ def annotate(Result, results, *,
t0, t1 = min(t0, t1), max(t0, t1) t0, t1 = min(t0, t1), max(t0, t1)
if not branches and not caches: if not branches and not caches:
tk = 'self_cycles' tk = 'cycles'
elif branches: elif branches:
tk = 'self_bmisses' tk = 'bmisses'
else: else:
tk = 'self_cmisses' tk = 'cmisses'
# find max cycles # find max cycles
max_ = max(it.chain((float(getattr(r, tk)) for r in results), [1])) max_ = max(it.chain((float(getattr(r, tk)) for r in results), [1]))
@@ -913,23 +955,19 @@ def annotate(Result, results, *,
r = table.get(i+1) r = table.get(i+1)
if r is not None and ( if r is not None and (
float(r.self_cycles) > 0 float(r.cycles) > 0
if not branches and not caches if not branches and not caches
else float(r.self_bmisses) > 0 else float(r.bmisses) > 0 or float(r.branches) > 0
or float(r.self_branches) > 0
if branches if branches
else float(r.self_cmisses) > 0 else float(r.cmisses) > 0 or float(r.caches) > 0):
or float(r.self_caches) > 0):
line = '%-*s // %s' % ( line = '%-*s // %s' % (
args['width'], args['width'],
line, line,
'%s cycles' % r.self_cycles '%s cycles' % r.cycles
if not branches and not caches if not branches and not caches
else '%s bmisses, %s branches' % ( else '%s bmisses, %s branches' % (r.bmisses, r.branches)
r.self_bmisses, r.self_branches)
if branches if branches
else '%s cmisses, %s caches' % ( else '%s cmisses, %s caches' % (r.cmisses, r.caches))
r.self_cmisses, r.self_caches))
if args['color']: if args['color']:
if float(getattr(r, tk)) / max_ >= t1: if float(getattr(r, tk)) / max_ >= t1:
@@ -948,8 +986,6 @@ def report(perf_paths, *,
self=False, self=False,
branches=False, branches=False,
caches=False, caches=False,
tree=False,
depth=None,
**args): **args):
# figure out what color should be # figure out what color should be
if args.get('color') == 'auto': if args.get('color') == 'auto':
@@ -959,11 +995,8 @@ def report(perf_paths, *,
else: else:
args['color'] = False args['color'] = False
# it doesn't really make sense to not have a depth with tree, # depth of 0 == m.inf
# so assume depth=inf if tree by default if args.get('depth') == 0:
if args.get('depth') is None:
args['depth'] = m.inf if tree else 1
elif args.get('depth') == 0:
args['depth'] = m.inf args['depth'] = m.inf
# find sizes # find sizes
@@ -1055,12 +1088,10 @@ def report(perf_paths, *,
table(PerfResult, results, table(PerfResult, results,
diff_results if args.get('diff') else None, diff_results if args.get('diff') else None,
by=by if by is not None else ['function'], by=by if by is not None else ['function'],
fields=fields if fields is not None else [ fields=fields if fields is not None
'self_'+k if self else k else ['cycles'] if not branches and not caches
for k in ( else ['bmisses', 'branches'] if branches
['cycles'] if not branches and not caches else ['cmisses', 'caches'],
else ['bmisses', 'branches'] if branches
else ['cmisses', 'caches'])],
sort=sort, sort=sort,
**args) **args)
@@ -1164,10 +1195,6 @@ if __name__ == "__main__":
'--everything', '--everything',
action='store_true', action='store_true',
help="Include builtin and libc specific symbols.") help="Include builtin and libc specific symbols.")
parser.add_argument(
'--self',
action='store_true',
help="Show samples before propagation up the call-chain.")
parser.add_argument( parser.add_argument(
'--branches', '--branches',
action='store_true', action='store_true',
@@ -1176,6 +1203,18 @@ if __name__ == "__main__":
'--caches', '--caches',
action='store_true', action='store_true',
help="Show cache accesses and cache misses.") help="Show cache accesses and cache misses.")
parser.add_argument(
'-P', '--propagate',
type=lambda x: int(x, 0),
help="Depth to propagate samples up the call-stack. 0 propagates up "
"to the entry point, 1 does no propagation. Defaults to 0.")
parser.add_argument(
'-Z', '--depth',
nargs='?',
type=lambda x: int(x, 0),
const=0,
help="Depth of function calls to show. 0 shows all calls but may not "
"terminate!")
parser.add_argument( parser.add_argument(
'-A', '--annotate', '-A', '--annotate',
action='store_true', action='store_true',
+86 -125
View File
@@ -102,23 +102,23 @@ class Int(co.namedtuple('Int', 'x')):
# size results # size results
class StackResult(co.namedtuple('StackResult', [ class StackResult(co.namedtuple('StackResult', [
'file', 'function', 'frame', 'limit', 'calls'])): 'file', 'function', 'frame', 'limit', 'children'])):
_by = ['file', 'function'] _by = ['file', 'function']
_fields = ['frame', 'limit'] _fields = ['frame', 'limit']
_types = {'frame': Int, 'limit': Int} _types = {'frame': Int, 'limit': Int}
__slots__ = () __slots__ = ()
def __new__(cls, file='', function='', def __new__(cls, file='', function='',
frame=0, limit=0, calls=set()): frame=0, limit=0, children=set()):
return super().__new__(cls, file, function, return super().__new__(cls, file, function,
Int(frame), Int(limit), Int(frame), Int(limit),
calls) children)
def __add__(self, other): def __add__(self, other):
return StackResult(self.file, self.function, return StackResult(self.file, self.function,
self.frame + other.frame, self.frame + other.frame,
max(self.limit, other.limit), max(self.limit, other.limit),
self.calls | other.calls) self.children | other.children)
def openio(path, mode='r'): def openio(path, mode='r'):
@@ -256,20 +256,20 @@ def collect(paths, *,
return frame + limit return frame + limit
def find_calls(targets): def find_children(targets):
calls = set() children = set()
for target in targets: for target in targets:
if target in callgraph: if target in callgraph:
t_file, t_function, _, _ = callgraph[target] t_file, t_function, _, _ = callgraph[target]
calls.add((t_file, t_function)) children.add((t_file, t_function))
return calls return children
# build results # build results
results = [] results = []
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) children = find_children(targets)
results.append(StackResult(s_file, s_function, frame, limit, calls)) results.append(StackResult(s_file, s_function, frame, limit, children))
return results return results
@@ -361,8 +361,8 @@ def table(Result, results, diff_results=None, *,
lines = [] lines = []
# header # header
line = [] header = []
line.append('%s%s' % ( header.append('%s%s' % (
','.join(by), ','.join(by),
' (%d added, %d removed)' % ( ' (%d added, %d removed)' % (
sum(1 for n in table if n not in diff_table), sum(1 for n in table if n not in diff_table),
@@ -371,129 +371,95 @@ def table(Result, results, diff_results=None, *,
if not summary else '') if not summary else '')
if diff_results is None: if diff_results is None:
for k in fields: for k in fields:
line.append(k) header.append(k)
elif percent: elif percent:
for k in fields: for k in fields:
line.append(k) header.append(k)
else: else:
for k in fields: for k in fields:
line.append('o'+k) header.append('o'+k)
for k in fields: for k in fields:
line.append('n'+k) header.append('n'+k)
for k in fields: for k in fields:
line.append('d'+k) header.append('d'+k)
line.append('') header.append('')
lines.append(line) lines.append(header)
def table_entry(name, r, diff_r=None, ratios=[]):
entry = []
entry.append(name)
if diff_results is None:
for k in fields:
entry.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
entry.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
entry.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
entry.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
entry.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
entry.append('')
elif percent:
entry.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
entry.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
return entry
# entries # entries
if not summary: if not summary:
for name in names: for name in names:
r = table.get(name) r = table.get(name)
if diff_results is not None: if diff_results is None:
diff_r = None
ratios = None
else:
diff_r = diff_table.get(name) diff_r = diff_table.get(name)
ratios = [ ratios = [
types[k].ratio( types[k].ratio(
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None)) getattr(diff_r, k, None))
for k in fields] for k in fields]
if not any(ratios) and not all_: if not all_ and not any(ratios):
continue continue
lines.append(table_entry(name, r, diff_r, ratios))
line = []
line.append(name)
if diff_results is None:
for k in fields:
line.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
line.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
line.append('')
elif percent:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
lines.append(line)
# total # total
r = next(iter(fold(Result, results, by=[])), None) r = next(iter(fold(Result, results, by=[])), None)
if diff_results is not None: if diff_results is None:
diff_r = None
ratios = None
else:
diff_r = next(iter(fold(Result, diff_results, by=[])), None) diff_r = next(iter(fold(Result, diff_results, by=[])), None)
ratios = [ ratios = [
types[k].ratio( types[k].ratio(
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None)) getattr(diff_r, k, None))
for k in fields] for k in fields]
lines.append(table_entry('TOTAL', r, diff_r, ratios))
line = []
line.append('TOTAL')
if diff_results is None:
for k in fields:
line.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
line.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
line.append('')
elif percent:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
lines.append(line)
# find the best widths, note that column 0 contains the names and column -1 # find the best widths, note that column 0 contains the names and column -1
# the ratios, so those are handled a bit differently # the ratios, so those are handled a bit differently
@@ -505,22 +471,17 @@ def table(Result, results, diff_results=None, *,
# adjust the name width based on the expected call depth, though # adjust the name width based on the expected call depth, though
# note this doesn't really work with unbounded recursion # note this doesn't really work with unbounded recursion
if not summary: if not summary and not m.isinf(depth):
if not m.isinf(depth): widths[0] += 4*(depth-1)
widths[0] += 4*(depth-1)
# print our table with optional call info # print the tree recursively
#
# note we try to adjust our name width based on expected call depth, but
# this doesn't work if there's unbounded recursion
if not tree: if not tree:
print('%-*s %s%s' % ( print('%-*s %s%s' % (
widths[0], lines[0][0], widths[0], lines[0][0],
' '.join('%*s' % (w, x) ' '.join('%*s' % (w, x)
for w, x, in zip(widths[1:], lines[0][1:-1])), for w, x in zip(widths[1:], lines[0][1:-1])),
lines[0][-1])) lines[0][-1]))
# print the tree recursively
if not summary: if not summary:
line_table = {n: l for n, l in zip(names, lines[1:-1])} line_table = {n: l for n, l in zip(names, lines[1:-1])}
@@ -541,32 +502,32 @@ def table(Result, results, diff_results=None, *,
if not tree: if not tree:
print(' %s%s' % ( print(' %s%s' % (
' '.join('%*s' % (w, x) ' '.join('%*s' % (w, x)
for w, x, in zip(widths[1:], line[1:-1])), for w, x in zip(widths[1:], line[1:-1])),
line[-1]), line[-1]),
end='') end='')
print() print()
# recurse? # recurse?
if name in table and depth_ > 0: if name in table and depth_ > 1:
calls = { children = {
','.join(str(getattr(Result(*c), k) or '') for k in by) ','.join(str(getattr(Result(*c), k) or '') for k in by)
for c in table[name].calls} for c in table[name].children}
recurse( recurse(
# note we're maintaining sort order # note we're maintaining sort order
[n for n in names if n in calls], [n for n in names if n in children],
depth_-1, depth_-1,
(prefixes[2+is_last] + "|-> ", (prefixes[2+is_last] + "|-> ",
prefixes[2+is_last] + "'-> ", prefixes[2+is_last] + "'-> ",
prefixes[2+is_last] + "| ", prefixes[2+is_last] + "| ",
prefixes[2+is_last] + " ")) prefixes[2+is_last] + " "))
recurse(names, depth-1)
recurse(names, depth)
if not tree: if not tree:
print('%-*s %s%s' % ( print('%-*s %s%s' % (
widths[0], lines[-1][0], widths[0], lines[-1][0],
' '.join('%*s' % (w, x) ' '.join('%*s' % (w, x)
for w, x, in zip(widths[1:], lines[-1][1:-1])), for w, x in zip(widths[1:], lines[-1][1:-1])),
lines[-1][-1])) lines[-1][-1]))
+68 -99
View File
@@ -217,6 +217,9 @@ def collect(obj_paths, *,
s_file = int(m.group('file')) s_file = int(m.group('file'))
elif m.group('size'): elif m.group('size'):
s_size = int(m.group('size')) s_size = int(m.group('size'))
if is_struct:
file = files.get(s_file, '?')
results_.append(StructResult(file, s_name, s_size))
proc.wait() proc.wait()
if proc.returncode != 0: if proc.returncode != 0:
if not args.get('verbose'): if not args.get('verbose'):
@@ -250,7 +253,7 @@ def collect(obj_paths, *,
else: else:
file = os.path.abspath(r.file) file = os.path.abspath(r.file)
results.append(StructResult(r.file, r.struct, r.size)) results.append(r._replace(file=file))
return results return results
@@ -340,8 +343,8 @@ def table(Result, results, diff_results=None, *,
lines = [] lines = []
# header # header
line = [] header = []
line.append('%s%s' % ( header.append('%s%s' % (
','.join(by), ','.join(by),
' (%d added, %d removed)' % ( ' (%d added, %d removed)' % (
sum(1 for n in table if n not in diff_table), sum(1 for n in table if n not in diff_table),
@@ -350,129 +353,95 @@ def table(Result, results, diff_results=None, *,
if not summary else '') if not summary else '')
if diff_results is None: if diff_results is None:
for k in fields: for k in fields:
line.append(k) header.append(k)
elif percent: elif percent:
for k in fields: for k in fields:
line.append(k) header.append(k)
else: else:
for k in fields: for k in fields:
line.append('o'+k) header.append('o'+k)
for k in fields: for k in fields:
line.append('n'+k) header.append('n'+k)
for k in fields: for k in fields:
line.append('d'+k) header.append('d'+k)
line.append('') header.append('')
lines.append(line) lines.append(header)
def table_entry(name, r, diff_r=None, ratios=[]):
entry = []
entry.append(name)
if diff_results is None:
for k in fields:
entry.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
entry.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
entry.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
entry.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
entry.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
entry.append('')
elif percent:
entry.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
entry.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
return entry
# entries # entries
if not summary: if not summary:
for name in names: for name in names:
r = table.get(name) r = table.get(name)
if diff_results is not None: if diff_results is None:
diff_r = None
ratios = None
else:
diff_r = diff_table.get(name) diff_r = diff_table.get(name)
ratios = [ ratios = [
types[k].ratio( types[k].ratio(
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None)) getattr(diff_r, k, None))
for k in fields] for k in fields]
if not any(ratios) and not all_: if not all_ and not any(ratios):
continue continue
lines.append(table_entry(name, r, diff_r, ratios))
line = []
line.append(name)
if diff_results is None:
for k in fields:
line.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
line.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
line.append('')
elif percent:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
lines.append(line)
# total # total
r = next(iter(fold(Result, results, by=[])), None) r = next(iter(fold(Result, results, by=[])), None)
if diff_results is not None: if diff_results is None:
diff_r = None
ratios = None
else:
diff_r = next(iter(fold(Result, diff_results, by=[])), None) diff_r = next(iter(fold(Result, diff_results, by=[])), None)
ratios = [ ratios = [
types[k].ratio( types[k].ratio(
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None)) getattr(diff_r, k, None))
for k in fields] for k in fields]
lines.append(table_entry('TOTAL', r, diff_r, ratios))
line = []
line.append('TOTAL')
if diff_results is None:
for k in fields:
line.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
line.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
line.append('')
elif percent:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
lines.append(line)
# find the best widths, note that column 0 contains the names and column -1 # find the best widths, note that column 0 contains the names and column -1
# the ratios, so those are handled a bit differently # the ratios, so those are handled a bit differently
+64 -98
View File
@@ -431,8 +431,8 @@ def table(Result, results, diff_results=None, *,
lines = [] lines = []
# header # header
line = [] header = []
line.append('%s%s' % ( header.append('%s%s' % (
','.join(by), ','.join(by),
' (%d added, %d removed)' % ( ' (%d added, %d removed)' % (
sum(1 for n in table if n not in diff_table), sum(1 for n in table if n not in diff_table),
@@ -441,129 +441,95 @@ def table(Result, results, diff_results=None, *,
if not summary else '') if not summary else '')
if diff_results is None: if diff_results is None:
for k in fields: for k in fields:
line.append(k) header.append(k)
elif percent: elif percent:
for k in fields: for k in fields:
line.append(k) header.append(k)
else: else:
for k in fields: for k in fields:
line.append('o'+k) header.append('o'+k)
for k in fields: for k in fields:
line.append('n'+k) header.append('n'+k)
for k in fields: for k in fields:
line.append('d'+k) header.append('d'+k)
line.append('') header.append('')
lines.append(line) lines.append(header)
def table_entry(name, r, diff_r=None, ratios=[]):
entry = []
entry.append(name)
if diff_results is None:
for k in fields:
entry.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
entry.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
entry.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
entry.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
entry.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
entry.append('')
elif percent:
entry.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
entry.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
return entry
# entries # entries
if not summary: if not summary:
for name in names: for name in names:
r = table.get(name) r = table.get(name)
if diff_results is not None: if diff_results is None:
diff_r = None
ratios = None
else:
diff_r = diff_table.get(name) diff_r = diff_table.get(name)
ratios = [ ratios = [
types[k].ratio( types[k].ratio(
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None)) getattr(diff_r, k, None))
for k in fields] for k in fields]
if not any(ratios) and not all_: if not all_ and not any(ratios):
continue continue
lines.append(table_entry(name, r, diff_r, ratios))
line = []
line.append(name)
if diff_results is None:
for k in fields:
line.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
line.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
line.append('')
elif percent:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
lines.append(line)
# total # total
r = next(iter(fold(Result, results, by=[])), None) r = next(iter(fold(Result, results, by=[])), None)
if diff_results is not None: if diff_results is None:
diff_r = None
ratios = None
else:
diff_r = next(iter(fold(Result, diff_results, by=[])), None) diff_r = next(iter(fold(Result, diff_results, by=[])), None)
ratios = [ ratios = [
types[k].ratio( types[k].ratio(
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None)) getattr(diff_r, k, None))
for k in fields] for k in fields]
lines.append(table_entry('TOTAL', r, diff_r, ratios))
line = []
line.append('TOTAL')
if diff_results is None:
for k in fields:
line.append(getattr(r, k).table()
if getattr(r, k, None) is not None
else types[k].none)
elif percent:
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
else:
for k in fields:
line.append(getattr(diff_r, k).diff_table()
if getattr(diff_r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(getattr(r, k).diff_table()
if getattr(r, k, None) is not None
else types[k].diff_none)
for k in fields:
line.append(types[k].diff_diff(
getattr(r, k, None),
getattr(diff_r, k, None)))
if diff_results is None:
line.append('')
elif percent:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios))
else:
line.append(' (%s)' % ', '.join(
'+∞%' if t == +m.inf
else '-∞%' if t == -m.inf
else '%+.1f%%' % (100*t)
for t in ratios
if t)
if any(ratios) else '')
lines.append(line)
# find the best widths, note that column 0 contains the names and column -1 # find the best widths, note that column 0 contains the names and column -1
# the ratios, so those are handled a bit differently # the ratios, so those are handled a bit differently