scripts: Option for result scripts to force children ordering

This extends the recursive part of the table renderer to sort children
by the optional "i" field, if available.

Note this only affects children entries. The top-level entries are
strictly ordered by the relevant "by" fields. I just haven't seen a use
case for this yet, and not sorting "i" at the top-level reduces that
number of things that can go wrong for scripts without children.

---

This also rewrites -t/--hot to take advantage of children ordering by
injecting a totally-no-hacky HotResult subclass.

Now -t/--hot should be strictly ordered by the call depth! Though note
entries that share "by" fields are still merged...

This also gives us a way to introduce the "cycle detected" note and
respect -z/--depth, so overall a big improvement for -t/--hot.
This commit is contained in:
Christopher Haster
2024-12-02 15:53:32 -06:00
parent e6ed785a27
commit 183ede1b83
9 changed files with 564 additions and 231 deletions
+59 -24
View File
@@ -506,34 +506,69 @@ def table(Result, results, diff_results=None, *,
if diff_results is not None: if diff_results is not None:
diff_results = fold(Result, diff_results, by=by) diff_results = fold(Result, diff_results, by=by)
# reduce children to hot paths? # reduce children to hot paths? only used by some scripts
if hot: if hot:
def rec_hot(results_, seen=set()): # subclass to reintroduce __dict__
if not results_: class HotResult(Result):
return [] i = None
children = None
notes = None
def __new__(cls, r, i=None, children=None, notes=None):
self = HotResult._make(r)
self.i = i
self.children = children if children is not None else []
self.notes = notes if notes is not None else []
if hasattr(r, 'notes'):
self.notes.extend(r.notes)
return self
r = max(results_, def __add__(self, other):
key=lambda r: tuple( return HotResult(
tuple((getattr(r, k),) Result.__add__(self, other),
if getattr(r, k, None) is not None self.i if other.i is None
else () else other.i if self.i is None
for k in ( else min(self.i, other.i),
[k] if k else [ self.children + other.children,
k for k in Result._sort self.notes + other.notes)
if k in fields])
if k in fields)
for k in it.chain(hot, [None])))
# found a cycle? def hot_(results_, depth_):
if (detect_cycles hot_ = []
and tuple(getattr(r, k) for k in Result._by) in seen): def recurse(results_, depth_, seen=set()):
return [] nonlocal hot_
if not results_:
return
return [r._replace(children=[])] + rec_hot( # find the hottest result
r.children, r = max(results_,
seen | {tuple(getattr(r, k) for k in Result._by)}) key=lambda r: tuple(
tuple((getattr(r, k),)
if getattr(r, k, None) is not None
else ()
for k in (
[k] if k else [
k for k in Result._sort
if k in fields])
if k in fields)
for k in it.chain(hot, [None])))
hot_.append(HotResult(r, i=len(hot_)))
results = [r._replace(children=rec_hot(r.children)) for r in results] # found a cycle?
if (detect_cycles
and tuple(getattr(r, k) for k in Result._by) in seen):
hot_[-1].notes.append('cycle detected')
return
# recurse?
if depth_ > 1:
recurse(r.children,
depth_-1,
seen | {tuple(getattr(r, k) for k in Result._by)})
recurse(results_, depth_)
return hot_
results = [r._replace(children=hot_(r.children, depth-1))
for r in results]
# organize by name # organize by name
table = { table = {
@@ -690,7 +725,7 @@ def table(Result, results, diff_results=None, *,
names_ = list(table_.keys()) names_ = list(table_.keys())
# sort the children layer # sort the children layer
names_.sort() names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n))
if sort: if sort:
for k, reverse in reversed(sort): for k, reverse in reversed(sort):
names_.sort( names_.sort(
+59 -24
View File
@@ -410,34 +410,69 @@ def table(Result, results, diff_results=None, *,
if diff_results is not None: if diff_results is not None:
diff_results = fold(Result, diff_results, by=by) diff_results = fold(Result, diff_results, by=by)
# reduce children to hot paths? # reduce children to hot paths? only used by some scripts
if hot: if hot:
def rec_hot(results_, seen=set()): # subclass to reintroduce __dict__
if not results_: class HotResult(Result):
return [] i = None
children = None
notes = None
def __new__(cls, r, i=None, children=None, notes=None):
self = HotResult._make(r)
self.i = i
self.children = children if children is not None else []
self.notes = notes if notes is not None else []
if hasattr(r, 'notes'):
self.notes.extend(r.notes)
return self
r = max(results_, def __add__(self, other):
key=lambda r: tuple( return HotResult(
tuple((getattr(r, k),) Result.__add__(self, other),
if getattr(r, k, None) is not None self.i if other.i is None
else () else other.i if self.i is None
for k in ( else min(self.i, other.i),
[k] if k else [ self.children + other.children,
k for k in Result._sort self.notes + other.notes)
if k in fields])
if k in fields)
for k in it.chain(hot, [None])))
# found a cycle? def hot_(results_, depth_):
if (detect_cycles hot_ = []
and tuple(getattr(r, k) for k in Result._by) in seen): def recurse(results_, depth_, seen=set()):
return [] nonlocal hot_
if not results_:
return
return [r._replace(children=[])] + rec_hot( # find the hottest result
r.children, r = max(results_,
seen | {tuple(getattr(r, k) for k in Result._by)}) key=lambda r: tuple(
tuple((getattr(r, k),)
if getattr(r, k, None) is not None
else ()
for k in (
[k] if k else [
k for k in Result._sort
if k in fields])
if k in fields)
for k in it.chain(hot, [None])))
hot_.append(HotResult(r, i=len(hot_)))
results = [r._replace(children=rec_hot(r.children)) for r in results] # found a cycle?
if (detect_cycles
and tuple(getattr(r, k) for k in Result._by) in seen):
hot_[-1].notes.append('cycle detected')
return
# recurse?
if depth_ > 1:
recurse(r.children,
depth_-1,
seen | {tuple(getattr(r, k) for k in Result._by)})
recurse(results_, depth_)
return hot_
results = [r._replace(children=hot_(r.children, depth-1))
for r in results]
# organize by name # organize by name
table = { table = {
@@ -594,7 +629,7 @@ def table(Result, results, diff_results=None, *,
names_ = list(table_.keys()) names_ = list(table_.keys())
# sort the children layer # sort the children layer
names_.sort() names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n))
if sort: if sort:
for k, reverse in reversed(sort): for k, reverse in reversed(sort):
names_.sort( names_.sort(
+59 -24
View File
@@ -1422,34 +1422,69 @@ def table(Result, results, diff_results=None, *,
if diff_results is not None: if diff_results is not None:
diff_results = fold(Result, diff_results, by=by) diff_results = fold(Result, diff_results, by=by)
# reduce children to hot paths? # reduce children to hot paths? only used by some scripts
if hot: if hot:
def rec_hot(results_, seen=set()): # subclass to reintroduce __dict__
if not results_: class HotResult(Result):
return [] i = None
children = None
notes = None
def __new__(cls, r, i=None, children=None, notes=None):
self = HotResult._make(r)
self.i = i
self.children = children if children is not None else []
self.notes = notes if notes is not None else []
if hasattr(r, 'notes'):
self.notes.extend(r.notes)
return self
r = max(results_, def __add__(self, other):
key=lambda r: tuple( return HotResult(
tuple((getattr(r, k),) Result.__add__(self, other),
if getattr(r, k, None) is not None self.i if other.i is None
else () else other.i if self.i is None
for k in ( else min(self.i, other.i),
[k] if k else [ self.children + other.children,
k for k in Result._sort self.notes + other.notes)
if k in fields])
if k in fields)
for k in it.chain(hot, [None])))
# found a cycle? def hot_(results_, depth_):
if (detect_cycles hot_ = []
and tuple(getattr(r, k) for k in Result._by) in seen): def recurse(results_, depth_, seen=set()):
return [] nonlocal hot_
if not results_:
return
return [r._replace(children=[])] + rec_hot( # find the hottest result
r.children, r = max(results_,
seen | {tuple(getattr(r, k) for k in Result._by)}) key=lambda r: tuple(
tuple((getattr(r, k),)
if getattr(r, k, None) is not None
else ()
for k in (
[k] if k else [
k for k in Result._sort
if k in fields])
if k in fields)
for k in it.chain(hot, [None])))
hot_.append(HotResult(r, i=len(hot_)))
results = [r._replace(children=rec_hot(r.children)) for r in results] # found a cycle?
if (detect_cycles
and tuple(getattr(r, k) for k in Result._by) in seen):
hot_[-1].notes.append('cycle detected')
return
# recurse?
if depth_ > 1:
recurse(r.children,
depth_-1,
seen | {tuple(getattr(r, k) for k in Result._by)})
recurse(results_, depth_)
return hot_
results = [r._replace(children=hot_(r.children, depth-1))
for r in results]
# organize by name # organize by name
table = { table = {
@@ -1606,7 +1641,7 @@ def table(Result, results, diff_results=None, *,
names_ = list(table_.keys()) names_ = list(table_.keys())
# sort the children layer # sort the children layer
names_.sort() names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n))
if sort: if sort:
for k, reverse in reversed(sort): for k, reverse in reversed(sort):
names_.sort( names_.sort(
+78 -33
View File
@@ -129,7 +129,7 @@ class RInt(co.namedtuple('RInt', 'x')):
class CtxResult(co.namedtuple('CtxResult', [ class CtxResult(co.namedtuple('CtxResult', [
'file', 'function', 'file', 'function',
'size', 'size',
'children', 'notes'])): 'i', 'children', 'notes'])):
_by = ['file', 'function'] _by = ['file', 'function']
_fields = ['size'] _fields = ['size']
_sort = ['size'] _sort = ['size']
@@ -137,15 +137,19 @@ class CtxResult(co.namedtuple('CtxResult', [
__slots__ = () __slots__ = ()
def __new__(cls, file='', function='', size=0, def __new__(cls, file='', function='', size=0,
children=None, notes=None): i=None, children=None, notes=None):
return super().__new__(cls, file, function, return super().__new__(cls, file, function,
RInt(size), RInt(size),
i,
children if children is not None else [], children if children is not None else [],
notes if notes is not None else []) notes if notes is not None else [])
def __add__(self, other): def __add__(self, other):
return CtxResult(self.file, self.function, return CtxResult(self.file, self.function,
max(self.size, other.size), max(self.size, other.size),
self.i if other.i is None
else other.i if self.i is None
else min(self.i, other.i),
self.children + other.children, self.children + other.children,
self.notes + other.notes) self.notes + other.notes)
@@ -575,8 +579,9 @@ def collect(obj_paths, *,
size_ = sizeof(type, seen | {entry.off}) size_ = sizeof(type, seen | {entry.off})
children_, notes_ = childrenof( children_, notes_ = childrenof(
type, seen | {entry.off}) type, seen | {entry.off})
children.append(CtxResult( children.append(CtxResult(file, name_, size_,
file, name_, size_, children_, notes_)) children=children_,
notes=notes_))
# struct? union? # struct? union?
elif entry.tag in { elif entry.tag in {
'DW_TAG_structure_type', 'DW_TAG_structure_type',
@@ -589,8 +594,10 @@ def collect(obj_paths, *,
size_ = sizeof(child, seen | {entry.off}) size_ = sizeof(child, seen | {entry.off})
children_, notes_ = childrenof( children_, notes_ = childrenof(
child, seen | {entry.off}) child, seen | {entry.off})
children.append(CtxResult( children.append(CtxResult(file, name_, size_,
file, name_, size_, children_, notes_)) i=child.off,
children=children_,
notes=notes_))
# base type? function pointer? # base type? function pointer?
elif entry.tag in { elif entry.tag in {
'DW_TAG_base_type', 'DW_TAG_base_type',
@@ -656,13 +663,16 @@ def collect(obj_paths, *,
# find children, recursing if necessary # find children, recursing if necessary
children_, notes_ = childrenof(param) children_, notes_ = childrenof(param)
params.append(CtxResult( params.append(CtxResult(file, name_, size_,
file, name_, size_, children_, notes_)) i=param.off,
children=children_,
notes=notes_))
# context = sum of params # context = sum of params
name = entry.name name = entry.name
size = sum((param.size for param in params), start=RInt(0)) size = sum((param.size for param in params), start=RInt(0))
results.append(CtxResult(file, name, size, params)) results.append(CtxResult(file, name, size,
children=params))
return results return results
@@ -726,34 +736,69 @@ def table(Result, results, diff_results=None, *,
if diff_results is not None: if diff_results is not None:
diff_results = fold(Result, diff_results, by=by) diff_results = fold(Result, diff_results, by=by)
# reduce children to hot paths? # reduce children to hot paths? only used by some scripts
if hot: if hot:
def rec_hot(results_, seen=set()): # subclass to reintroduce __dict__
if not results_: class HotResult(Result):
return [] i = None
children = None
notes = None
def __new__(cls, r, i=None, children=None, notes=None):
self = HotResult._make(r)
self.i = i
self.children = children if children is not None else []
self.notes = notes if notes is not None else []
if hasattr(r, 'notes'):
self.notes.extend(r.notes)
return self
r = max(results_, def __add__(self, other):
key=lambda r: tuple( return HotResult(
tuple((getattr(r, k),) Result.__add__(self, other),
if getattr(r, k, None) is not None self.i if other.i is None
else () else other.i if self.i is None
for k in ( else min(self.i, other.i),
[k] if k else [ self.children + other.children,
k for k in Result._sort self.notes + other.notes)
if k in fields])
if k in fields)
for k in it.chain(hot, [None])))
# found a cycle? def hot_(results_, depth_):
if (detect_cycles hot_ = []
and tuple(getattr(r, k) for k in Result._by) in seen): def recurse(results_, depth_, seen=set()):
return [] nonlocal hot_
if not results_:
return
return [r._replace(children=[])] + rec_hot( # find the hottest result
r.children, r = max(results_,
seen | {tuple(getattr(r, k) for k in Result._by)}) key=lambda r: tuple(
tuple((getattr(r, k),)
if getattr(r, k, None) is not None
else ()
for k in (
[k] if k else [
k for k in Result._sort
if k in fields])
if k in fields)
for k in it.chain(hot, [None])))
hot_.append(HotResult(r, i=len(hot_)))
results = [r._replace(children=rec_hot(r.children)) for r in results] # found a cycle?
if (detect_cycles
and tuple(getattr(r, k) for k in Result._by) in seen):
hot_[-1].notes.append('cycle detected')
return
# recurse?
if depth_ > 1:
recurse(r.children,
depth_-1,
seen | {tuple(getattr(r, k) for k in Result._by)})
recurse(results_, depth_)
return hot_
results = [r._replace(children=hot_(r.children, depth-1))
for r in results]
# organize by name # organize by name
table = { table = {
@@ -910,7 +955,7 @@ def table(Result, results, diff_results=None, *,
names_ = list(table_.keys()) names_ = list(table_.keys())
# sort the children layer # sort the children layer
names_.sort() names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n))
if sort: if sort:
for k, reverse in reversed(sort): for k, reverse in reversed(sort):
names_.sort( names_.sort(
+59 -24
View File
@@ -506,34 +506,69 @@ def table(Result, results, diff_results=None, *,
if diff_results is not None: if diff_results is not None:
diff_results = fold(Result, diff_results, by=by) diff_results = fold(Result, diff_results, by=by)
# reduce children to hot paths? # reduce children to hot paths? only used by some scripts
if hot: if hot:
def rec_hot(results_, seen=set()): # subclass to reintroduce __dict__
if not results_: class HotResult(Result):
return [] i = None
children = None
notes = None
def __new__(cls, r, i=None, children=None, notes=None):
self = HotResult._make(r)
self.i = i
self.children = children if children is not None else []
self.notes = notes if notes is not None else []
if hasattr(r, 'notes'):
self.notes.extend(r.notes)
return self
r = max(results_, def __add__(self, other):
key=lambda r: tuple( return HotResult(
tuple((getattr(r, k),) Result.__add__(self, other),
if getattr(r, k, None) is not None self.i if other.i is None
else () else other.i if self.i is None
for k in ( else min(self.i, other.i),
[k] if k else [ self.children + other.children,
k for k in Result._sort self.notes + other.notes)
if k in fields])
if k in fields)
for k in it.chain(hot, [None])))
# found a cycle? def hot_(results_, depth_):
if (detect_cycles hot_ = []
and tuple(getattr(r, k) for k in Result._by) in seen): def recurse(results_, depth_, seen=set()):
return [] nonlocal hot_
if not results_:
return
return [r._replace(children=[])] + rec_hot( # find the hottest result
r.children, r = max(results_,
seen | {tuple(getattr(r, k) for k in Result._by)}) key=lambda r: tuple(
tuple((getattr(r, k),)
if getattr(r, k, None) is not None
else ()
for k in (
[k] if k else [
k for k in Result._sort
if k in fields])
if k in fields)
for k in it.chain(hot, [None])))
hot_.append(HotResult(r, i=len(hot_)))
results = [r._replace(children=rec_hot(r.children)) for r in results] # found a cycle?
if (detect_cycles
and tuple(getattr(r, k) for k in Result._by) in seen):
hot_[-1].notes.append('cycle detected')
return
# recurse?
if depth_ > 1:
recurse(r.children,
depth_-1,
seen | {tuple(getattr(r, k) for k in Result._by)})
recurse(results_, depth_)
return hot_
results = [r._replace(children=hot_(r.children, depth-1))
for r in results]
# organize by name # organize by name
table = { table = {
@@ -690,7 +725,7 @@ def table(Result, results, diff_results=None, *,
names_ = list(table_.keys()) names_ = list(table_.keys())
# sort the children layer # sort the children layer
names_.sort() names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n))
if sort: if sort:
for k, reverse in reversed(sort): for k, reverse in reversed(sort):
names_.sort( names_.sort(
+59 -24
View File
@@ -812,34 +812,69 @@ def table(Result, results, diff_results=None, *,
if diff_results is not None: if diff_results is not None:
diff_results = fold(Result, diff_results, by=by) diff_results = fold(Result, diff_results, by=by)
# reduce children to hot paths? # reduce children to hot paths? only used by some scripts
if hot: if hot:
def rec_hot(results_, seen=set()): # subclass to reintroduce __dict__
if not results_: class HotResult(Result):
return [] i = None
children = None
notes = None
def __new__(cls, r, i=None, children=None, notes=None):
self = HotResult._make(r)
self.i = i
self.children = children if children is not None else []
self.notes = notes if notes is not None else []
if hasattr(r, 'notes'):
self.notes.extend(r.notes)
return self
r = max(results_, def __add__(self, other):
key=lambda r: tuple( return HotResult(
tuple((getattr(r, k),) Result.__add__(self, other),
if getattr(r, k, None) is not None self.i if other.i is None
else () else other.i if self.i is None
for k in ( else min(self.i, other.i),
[k] if k else [ self.children + other.children,
k for k in Result._sort self.notes + other.notes)
if k in fields])
if k in fields)
for k in it.chain(hot, [None])))
# found a cycle? def hot_(results_, depth_):
if (detect_cycles hot_ = []
and tuple(getattr(r, k) for k in Result._by) in seen): def recurse(results_, depth_, seen=set()):
return [] nonlocal hot_
if not results_:
return
return [r._replace(children=[])] + rec_hot( # find the hottest result
r.children, r = max(results_,
seen | {tuple(getattr(r, k) for k in Result._by)}) key=lambda r: tuple(
tuple((getattr(r, k),)
if getattr(r, k, None) is not None
else ()
for k in (
[k] if k else [
k for k in Result._sort
if k in fields])
if k in fields)
for k in it.chain(hot, [None])))
hot_.append(HotResult(r, i=len(hot_)))
results = [r._replace(children=rec_hot(r.children)) for r in results] # found a cycle?
if (detect_cycles
and tuple(getattr(r, k) for k in Result._by) in seen):
hot_[-1].notes.append('cycle detected')
return
# recurse?
if depth_ > 1:
recurse(r.children,
depth_-1,
seen | {tuple(getattr(r, k) for k in Result._by)})
recurse(results_, depth_)
return hot_
results = [r._replace(children=hot_(r.children, depth-1))
for r in results]
# organize by name # organize by name
table = { table = {
@@ -996,7 +1031,7 @@ def table(Result, results, diff_results=None, *,
names_ = list(table_.keys()) names_ = list(table_.keys())
# sort the children layer # sort the children layer
names_.sort() names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n))
if sort: if sort:
for k, reverse in reversed(sort): for k, reverse in reversed(sort):
names_.sort( names_.sort(
+59 -24
View File
@@ -775,34 +775,69 @@ def table(Result, results, diff_results=None, *,
if diff_results is not None: if diff_results is not None:
diff_results = fold(Result, diff_results, by=by) diff_results = fold(Result, diff_results, by=by)
# reduce children to hot paths? # reduce children to hot paths? only used by some scripts
if hot: if hot:
def rec_hot(results_, seen=set()): # subclass to reintroduce __dict__
if not results_: class HotResult(Result):
return [] i = None
children = None
notes = None
def __new__(cls, r, i=None, children=None, notes=None):
self = HotResult._make(r)
self.i = i
self.children = children if children is not None else []
self.notes = notes if notes is not None else []
if hasattr(r, 'notes'):
self.notes.extend(r.notes)
return self
r = max(results_, def __add__(self, other):
key=lambda r: tuple( return HotResult(
tuple((getattr(r, k),) Result.__add__(self, other),
if getattr(r, k, None) is not None self.i if other.i is None
else () else other.i if self.i is None
for k in ( else min(self.i, other.i),
[k] if k else [ self.children + other.children,
k for k in Result._sort self.notes + other.notes)
if k in fields])
if k in fields)
for k in it.chain(hot, [None])))
# found a cycle? def hot_(results_, depth_):
if (detect_cycles hot_ = []
and tuple(getattr(r, k) for k in Result._by) in seen): def recurse(results_, depth_, seen=set()):
return [] nonlocal hot_
if not results_:
return
return [r._replace(children=[])] + rec_hot( # find the hottest result
r.children, r = max(results_,
seen | {tuple(getattr(r, k) for k in Result._by)}) key=lambda r: tuple(
tuple((getattr(r, k),)
if getattr(r, k, None) is not None
else ()
for k in (
[k] if k else [
k for k in Result._sort
if k in fields])
if k in fields)
for k in it.chain(hot, [None])))
hot_.append(HotResult(r, i=len(hot_)))
results = [r._replace(children=rec_hot(r.children)) for r in results] # found a cycle?
if (detect_cycles
and tuple(getattr(r, k) for k in Result._by) in seen):
hot_[-1].notes.append('cycle detected')
return
# recurse?
if depth_ > 1:
recurse(r.children,
depth_-1,
seen | {tuple(getattr(r, k) for k in Result._by)})
recurse(results_, depth_)
return hot_
results = [r._replace(children=hot_(r.children, depth-1))
for r in results]
# organize by name # organize by name
table = { table = {
@@ -959,7 +994,7 @@ def table(Result, results, diff_results=None, *,
names_ = list(table_.keys()) names_ = list(table_.keys())
# sort the children layer # sort the children layer
names_.sort() names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n))
if sort: if sort:
for k, reverse in reversed(sort): for k, reverse in reversed(sort):
names_.sort( names_.sort(
+60 -25
View File
@@ -286,7 +286,7 @@ def collect(ci_paths, *,
results = {} results = {}
for source, (s_file, s_function, frame, _) in callgraph.items(): for source, (s_file, s_function, frame, _) in callgraph.items():
limit = find_limit(source) limit = find_limit(source)
results[source] = StackResult(s_file, s_function, frame, limit, []) results[source] = StackResult(s_file, s_function, frame, limit)
# connect parents to their children, this may create a fully cyclic graph # connect parents to their children, this may create a fully cyclic graph
# in the case of recursion # in the case of recursion
@@ -358,34 +358,69 @@ def table(Result, results, diff_results=None, *,
if diff_results is not None: if diff_results is not None:
diff_results = fold(Result, diff_results, by=by) diff_results = fold(Result, diff_results, by=by)
# reduce children to hot paths? # reduce children to hot paths? only used by some scripts
if hot: if hot:
def rec_hot(results_, seen=set()): # subclass to reintroduce __dict__
if not results_: class HotResult(Result):
return [] i = None
children = None
notes = None
def __new__(cls, r, i=None, children=None, notes=None):
self = HotResult._make(r)
self.i = i
self.children = children if children is not None else []
self.notes = notes if notes is not None else []
if hasattr(r, 'notes'):
self.notes.extend(r.notes)
return self
r = max(results_, def __add__(self, other):
key=lambda r: tuple( return HotResult(
tuple((getattr(r, k),) Result.__add__(self, other),
if getattr(r, k, None) is not None self.i if other.i is None
else () else other.i if self.i is None
for k in ( else min(self.i, other.i),
[k] if k else [ self.children + other.children,
k for k in Result._sort self.notes + other.notes)
if k in fields])
if k in fields)
for k in it.chain(hot, [None])))
# found a cycle? def hot_(results_, depth_):
if (detect_cycles hot_ = []
and tuple(getattr(r, k) for k in Result._by) in seen): def recurse(results_, depth_, seen=set()):
return [] nonlocal hot_
if not results_:
return
return [r._replace(children=[])] + rec_hot( # find the hottest result
r.children, r = max(results_,
seen | {tuple(getattr(r, k) for k in Result._by)}) key=lambda r: tuple(
tuple((getattr(r, k),)
if getattr(r, k, None) is not None
else ()
for k in (
[k] if k else [
k for k in Result._sort
if k in fields])
if k in fields)
for k in it.chain(hot, [None])))
hot_.append(HotResult(r, i=len(hot_)))
results = [r._replace(children=rec_hot(r.children)) for r in results] # found a cycle?
if (detect_cycles
and tuple(getattr(r, k) for k in Result._by) in seen):
hot_[-1].notes.append('cycle detected')
return
# recurse?
if depth_ > 1:
recurse(r.children,
depth_-1,
seen | {tuple(getattr(r, k) for k in Result._by)})
recurse(results_, depth_)
return hot_
results = [r._replace(children=hot_(r.children, depth-1))
for r in results]
# organize by name # organize by name
table = { table = {
@@ -542,7 +577,7 @@ def table(Result, results, diff_results=None, *,
names_ = list(table_.keys()) names_ = list(table_.keys())
# sort the children layer # sort the children layer
names_.sort() names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n))
if sort: if sort:
for k, reverse in reversed(sort): for k, reverse in reversed(sort):
names_.sort( names_.sort(
+72 -29
View File
@@ -129,7 +129,7 @@ class RInt(co.namedtuple('RInt', 'x')):
class StructResult(co.namedtuple('StructResult', [ class StructResult(co.namedtuple('StructResult', [
'file', 'struct', 'file', 'struct',
'size', 'align', 'size', 'align',
'children'])): 'i', 'children'])):
_by = ['file', 'struct'] _by = ['file', 'struct']
_fields = ['size', 'align'] _fields = ['size', 'align']
_sort = ['size', 'align'] _sort = ['size', 'align']
@@ -137,15 +137,19 @@ class StructResult(co.namedtuple('StructResult', [
__slots__ = () __slots__ = ()
def __new__(cls, file='', struct='', size=0, align=0, def __new__(cls, file='', struct='', size=0, align=0,
children=None): i=None, children=None):
return super().__new__(cls, file, struct, return super().__new__(cls, file, struct,
RInt(size), RInt(align), RInt(size), RInt(align),
i,
children if children is not None else []) children if children is not None else [])
def __add__(self, other): def __add__(self, other):
return StructResult(self.file, self.struct, return StructResult(self.file, self.struct,
self.size + other.size, self.size + other.size,
max(self.align, other.align), max(self.align, other.align),
self.i if other.i is None
else other.i if self.i is None
else min(self.i, other.i),
self.children + other.children) self.children + other.children)
@@ -456,7 +460,9 @@ def collect(obj_paths, *,
align_ = alignof(child) align_ = alignof(child)
children_ = childrenof(child) children_ = childrenof(child)
children.append(StructResult( children.append(StructResult(
file, name_, size_, align_, children_)) file, name_, size_, align_,
i=child.off,
children=children_))
# indirect type? # indirect type?
elif 'DW_AT_type' in entry: elif 'DW_AT_type' in entry:
type = int(entry['DW_AT_type'].strip('<>'), 0) type = int(entry['DW_AT_type'].strip('<>'), 0)
@@ -519,11 +525,13 @@ def collect(obj_paths, *,
# these separately # these separately
if entry.tag == 'DW_TAG_typedef': if entry.tag == 'DW_TAG_typedef':
typedefs[entry.off] = StructResult( typedefs[entry.off] = StructResult(
file, name, size, align, children) file, name, size, align,
children=children)
typedefed.add(int(entry['DW_AT_type'].strip('<>'), 0)) typedefed.add(int(entry['DW_AT_type'].strip('<>'), 0))
else: else:
types[entry.off] = StructResult( types[entry.off] = StructResult(
file, name, size, align, children) file, name, size, align,
children=children)
# let typedefs take priority # let typedefs take priority
results.extend(typedefs.values()) results.extend(typedefs.values())
@@ -593,34 +601,69 @@ def table(Result, results, diff_results=None, *,
if diff_results is not None: if diff_results is not None:
diff_results = fold(Result, diff_results, by=by) diff_results = fold(Result, diff_results, by=by)
# reduce children to hot paths? # reduce children to hot paths? only used by some scripts
if hot: if hot:
def rec_hot(results_, seen=set()): # subclass to reintroduce __dict__
if not results_: class HotResult(Result):
return [] i = None
children = None
notes = None
def __new__(cls, r, i=None, children=None, notes=None):
self = HotResult._make(r)
self.i = i
self.children = children if children is not None else []
self.notes = notes if notes is not None else []
if hasattr(r, 'notes'):
self.notes.extend(r.notes)
return self
r = max(results_, def __add__(self, other):
key=lambda r: tuple( return HotResult(
tuple((getattr(r, k),) Result.__add__(self, other),
if getattr(r, k, None) is not None self.i if other.i is None
else () else other.i if self.i is None
for k in ( else min(self.i, other.i),
[k] if k else [ self.children + other.children,
k for k in Result._sort self.notes + other.notes)
if k in fields])
if k in fields)
for k in it.chain(hot, [None])))
# found a cycle? def hot_(results_, depth_):
if (detect_cycles hot_ = []
and tuple(getattr(r, k) for k in Result._by) in seen): def recurse(results_, depth_, seen=set()):
return [] nonlocal hot_
if not results_:
return
return [r._replace(children=[])] + rec_hot( # find the hottest result
r.children, r = max(results_,
seen | {tuple(getattr(r, k) for k in Result._by)}) key=lambda r: tuple(
tuple((getattr(r, k),)
if getattr(r, k, None) is not None
else ()
for k in (
[k] if k else [
k for k in Result._sort
if k in fields])
if k in fields)
for k in it.chain(hot, [None])))
hot_.append(HotResult(r, i=len(hot_)))
results = [r._replace(children=rec_hot(r.children)) for r in results] # found a cycle?
if (detect_cycles
and tuple(getattr(r, k) for k in Result._by) in seen):
hot_[-1].notes.append('cycle detected')
return
# recurse?
if depth_ > 1:
recurse(r.children,
depth_-1,
seen | {tuple(getattr(r, k) for k in Result._by)})
recurse(results_, depth_)
return hot_
results = [r._replace(children=hot_(r.children, depth-1))
for r in results]
# organize by name # organize by name
table = { table = {
@@ -777,7 +820,7 @@ def table(Result, results, diff_results=None, *,
names_ = list(table_.keys()) names_ = list(table_.keys())
# sort the children layer # sort the children layer
names_.sort() names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n))
if sort: if sort:
for k, reverse in reversed(sort): for k, reverse in reversed(sort):
names_.sort( names_.sort(