From 183ede1b83b61b08aa056042ea64cd773ceb94ab Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 2 Dec 2024 15:53:32 -0600 Subject: [PATCH] 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. --- scripts/code.py | 83 +++++++++++++++++++++++---------- scripts/cov.py | 83 +++++++++++++++++++++++---------- scripts/csv.py | 83 +++++++++++++++++++++++---------- scripts/ctx.py | 111 +++++++++++++++++++++++++++++++-------------- scripts/data.py | 83 +++++++++++++++++++++++---------- scripts/perf.py | 83 +++++++++++++++++++++++---------- scripts/perfbd.py | 83 +++++++++++++++++++++++---------- scripts/stack.py | 85 ++++++++++++++++++++++++---------- scripts/structs.py | 101 +++++++++++++++++++++++++++++------------ 9 files changed, 564 insertions(+), 231 deletions(-) diff --git a/scripts/code.py b/scripts/code.py index 23fe2945..43ae73de 100755 --- a/scripts/code.py +++ b/scripts/code.py @@ -506,34 +506,69 @@ def table(Result, results, diff_results=None, *, if diff_results is not None: 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: - def rec_hot(results_, seen=set()): - if not results_: - return [] + # subclass to reintroduce __dict__ + class HotResult(Result): + 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_, - 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]))) + def __add__(self, other): + return HotResult( + Result.__add__(self, other), + 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.notes + other.notes) - # found a cycle? - if (detect_cycles - and tuple(getattr(r, k) for k in Result._by) in seen): - return [] + def hot_(results_, depth_): + hot_ = [] + def recurse(results_, depth_, seen=set()): + nonlocal hot_ + if not results_: + return - return [r._replace(children=[])] + rec_hot( - r.children, - seen | {tuple(getattr(r, k) for k in Result._by)}) + # find the hottest result + r = max(results_, + 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 table = { @@ -690,7 +725,7 @@ def table(Result, results, diff_results=None, *, names_ = list(table_.keys()) # sort the children layer - names_.sort() + names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n)) if sort: for k, reverse in reversed(sort): names_.sort( diff --git a/scripts/cov.py b/scripts/cov.py index 11c4e59d..fb62ba97 100755 --- a/scripts/cov.py +++ b/scripts/cov.py @@ -410,34 +410,69 @@ def table(Result, results, diff_results=None, *, if diff_results is not None: 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: - def rec_hot(results_, seen=set()): - if not results_: - return [] + # subclass to reintroduce __dict__ + class HotResult(Result): + 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_, - 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]))) + def __add__(self, other): + return HotResult( + Result.__add__(self, other), + 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.notes + other.notes) - # found a cycle? - if (detect_cycles - and tuple(getattr(r, k) for k in Result._by) in seen): - return [] + def hot_(results_, depth_): + hot_ = [] + def recurse(results_, depth_, seen=set()): + nonlocal hot_ + if not results_: + return - return [r._replace(children=[])] + rec_hot( - r.children, - seen | {tuple(getattr(r, k) for k in Result._by)}) + # find the hottest result + r = max(results_, + 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 table = { @@ -594,7 +629,7 @@ def table(Result, results, diff_results=None, *, names_ = list(table_.keys()) # sort the children layer - names_.sort() + names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n)) if sort: for k, reverse in reversed(sort): names_.sort( diff --git a/scripts/csv.py b/scripts/csv.py index 07374c56..c45d0042 100755 --- a/scripts/csv.py +++ b/scripts/csv.py @@ -1422,34 +1422,69 @@ def table(Result, results, diff_results=None, *, if diff_results is not None: 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: - def rec_hot(results_, seen=set()): - if not results_: - return [] + # subclass to reintroduce __dict__ + class HotResult(Result): + 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_, - 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]))) + def __add__(self, other): + return HotResult( + Result.__add__(self, other), + 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.notes + other.notes) - # found a cycle? - if (detect_cycles - and tuple(getattr(r, k) for k in Result._by) in seen): - return [] + def hot_(results_, depth_): + hot_ = [] + def recurse(results_, depth_, seen=set()): + nonlocal hot_ + if not results_: + return - return [r._replace(children=[])] + rec_hot( - r.children, - seen | {tuple(getattr(r, k) for k in Result._by)}) + # find the hottest result + r = max(results_, + 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 table = { @@ -1606,7 +1641,7 @@ def table(Result, results, diff_results=None, *, names_ = list(table_.keys()) # sort the children layer - names_.sort() + names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n)) if sort: for k, reverse in reversed(sort): names_.sort( diff --git a/scripts/ctx.py b/scripts/ctx.py index e77c8c62..80fb0866 100755 --- a/scripts/ctx.py +++ b/scripts/ctx.py @@ -129,7 +129,7 @@ class RInt(co.namedtuple('RInt', 'x')): class CtxResult(co.namedtuple('CtxResult', [ 'file', 'function', 'size', - 'children', 'notes'])): + 'i', 'children', 'notes'])): _by = ['file', 'function'] _fields = ['size'] _sort = ['size'] @@ -137,15 +137,19 @@ class CtxResult(co.namedtuple('CtxResult', [ __slots__ = () def __new__(cls, file='', function='', size=0, - children=None, notes=None): + i=None, children=None, notes=None): return super().__new__(cls, file, function, RInt(size), + i, children if children is not None else [], notes if notes is not None else []) def __add__(self, other): return CtxResult(self.file, self.function, 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.notes + other.notes) @@ -575,8 +579,9 @@ def collect(obj_paths, *, size_ = sizeof(type, seen | {entry.off}) children_, notes_ = childrenof( type, seen | {entry.off}) - children.append(CtxResult( - file, name_, size_, children_, notes_)) + children.append(CtxResult(file, name_, size_, + children=children_, + notes=notes_)) # struct? union? elif entry.tag in { 'DW_TAG_structure_type', @@ -589,8 +594,10 @@ def collect(obj_paths, *, size_ = sizeof(child, seen | {entry.off}) children_, notes_ = childrenof( child, seen | {entry.off}) - children.append(CtxResult( - file, name_, size_, children_, notes_)) + children.append(CtxResult(file, name_, size_, + i=child.off, + children=children_, + notes=notes_)) # base type? function pointer? elif entry.tag in { 'DW_TAG_base_type', @@ -656,13 +663,16 @@ def collect(obj_paths, *, # find children, recursing if necessary children_, notes_ = childrenof(param) - params.append(CtxResult( - file, name_, size_, children_, notes_)) + params.append(CtxResult(file, name_, size_, + i=param.off, + children=children_, + notes=notes_)) # context = sum of params name = entry.name 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 @@ -726,34 +736,69 @@ def table(Result, results, diff_results=None, *, if diff_results is not None: 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: - def rec_hot(results_, seen=set()): - if not results_: - return [] + # subclass to reintroduce __dict__ + class HotResult(Result): + 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_, - 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]))) + def __add__(self, other): + return HotResult( + Result.__add__(self, other), + 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.notes + other.notes) - # found a cycle? - if (detect_cycles - and tuple(getattr(r, k) for k in Result._by) in seen): - return [] + def hot_(results_, depth_): + hot_ = [] + def recurse(results_, depth_, seen=set()): + nonlocal hot_ + if not results_: + return - return [r._replace(children=[])] + rec_hot( - r.children, - seen | {tuple(getattr(r, k) for k in Result._by)}) + # find the hottest result + r = max(results_, + 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 table = { @@ -910,7 +955,7 @@ def table(Result, results, diff_results=None, *, names_ = list(table_.keys()) # sort the children layer - names_.sort() + names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n)) if sort: for k, reverse in reversed(sort): names_.sort( diff --git a/scripts/data.py b/scripts/data.py index 69bf9a07..7b73be09 100755 --- a/scripts/data.py +++ b/scripts/data.py @@ -506,34 +506,69 @@ def table(Result, results, diff_results=None, *, if diff_results is not None: 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: - def rec_hot(results_, seen=set()): - if not results_: - return [] + # subclass to reintroduce __dict__ + class HotResult(Result): + 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_, - 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]))) + def __add__(self, other): + return HotResult( + Result.__add__(self, other), + 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.notes + other.notes) - # found a cycle? - if (detect_cycles - and tuple(getattr(r, k) for k in Result._by) in seen): - return [] + def hot_(results_, depth_): + hot_ = [] + def recurse(results_, depth_, seen=set()): + nonlocal hot_ + if not results_: + return - return [r._replace(children=[])] + rec_hot( - r.children, - seen | {tuple(getattr(r, k) for k in Result._by)}) + # find the hottest result + r = max(results_, + 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 table = { @@ -690,7 +725,7 @@ def table(Result, results, diff_results=None, *, names_ = list(table_.keys()) # sort the children layer - names_.sort() + names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n)) if sort: for k, reverse in reversed(sort): names_.sort( diff --git a/scripts/perf.py b/scripts/perf.py index 9e3f19a8..8b20a1f0 100755 --- a/scripts/perf.py +++ b/scripts/perf.py @@ -812,34 +812,69 @@ def table(Result, results, diff_results=None, *, if diff_results is not None: 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: - def rec_hot(results_, seen=set()): - if not results_: - return [] + # subclass to reintroduce __dict__ + class HotResult(Result): + 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_, - 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]))) + def __add__(self, other): + return HotResult( + Result.__add__(self, other), + 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.notes + other.notes) - # found a cycle? - if (detect_cycles - and tuple(getattr(r, k) for k in Result._by) in seen): - return [] + def hot_(results_, depth_): + hot_ = [] + def recurse(results_, depth_, seen=set()): + nonlocal hot_ + if not results_: + return - return [r._replace(children=[])] + rec_hot( - r.children, - seen | {tuple(getattr(r, k) for k in Result._by)}) + # find the hottest result + r = max(results_, + 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 table = { @@ -996,7 +1031,7 @@ def table(Result, results, diff_results=None, *, names_ = list(table_.keys()) # sort the children layer - names_.sort() + names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n)) if sort: for k, reverse in reversed(sort): names_.sort( diff --git a/scripts/perfbd.py b/scripts/perfbd.py index 5c0d0a6b..18d7817d 100755 --- a/scripts/perfbd.py +++ b/scripts/perfbd.py @@ -775,34 +775,69 @@ def table(Result, results, diff_results=None, *, if diff_results is not None: 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: - def rec_hot(results_, seen=set()): - if not results_: - return [] + # subclass to reintroduce __dict__ + class HotResult(Result): + 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_, - 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]))) + def __add__(self, other): + return HotResult( + Result.__add__(self, other), + 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.notes + other.notes) - # found a cycle? - if (detect_cycles - and tuple(getattr(r, k) for k in Result._by) in seen): - return [] + def hot_(results_, depth_): + hot_ = [] + def recurse(results_, depth_, seen=set()): + nonlocal hot_ + if not results_: + return - return [r._replace(children=[])] + rec_hot( - r.children, - seen | {tuple(getattr(r, k) for k in Result._by)}) + # find the hottest result + r = max(results_, + 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 table = { @@ -959,7 +994,7 @@ def table(Result, results, diff_results=None, *, names_ = list(table_.keys()) # sort the children layer - names_.sort() + names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n)) if sort: for k, reverse in reversed(sort): names_.sort( diff --git a/scripts/stack.py b/scripts/stack.py index 3bd11c74..d336bd18 100755 --- a/scripts/stack.py +++ b/scripts/stack.py @@ -286,7 +286,7 @@ def collect(ci_paths, *, results = {} for source, (s_file, s_function, frame, _) in callgraph.items(): 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 # in the case of recursion @@ -358,34 +358,69 @@ def table(Result, results, diff_results=None, *, if diff_results is not None: 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: - def rec_hot(results_, seen=set()): - if not results_: - return [] + # subclass to reintroduce __dict__ + class HotResult(Result): + 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_, - 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]))) + def __add__(self, other): + return HotResult( + Result.__add__(self, other), + 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.notes + other.notes) - # found a cycle? - if (detect_cycles - and tuple(getattr(r, k) for k in Result._by) in seen): - return [] + def hot_(results_, depth_): + hot_ = [] + def recurse(results_, depth_, seen=set()): + nonlocal hot_ + if not results_: + return - return [r._replace(children=[])] + rec_hot( - r.children, - seen | {tuple(getattr(r, k) for k in Result._by)}) + # find the hottest result + r = max(results_, + 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 table = { @@ -542,7 +577,7 @@ def table(Result, results, diff_results=None, *, names_ = list(table_.keys()) # sort the children layer - names_.sort() + names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n)) if sort: for k, reverse in reversed(sort): names_.sort( diff --git a/scripts/structs.py b/scripts/structs.py index f1b6266e..ffc8b17e 100755 --- a/scripts/structs.py +++ b/scripts/structs.py @@ -129,7 +129,7 @@ class RInt(co.namedtuple('RInt', 'x')): class StructResult(co.namedtuple('StructResult', [ 'file', 'struct', 'size', 'align', - 'children'])): + 'i', 'children'])): _by = ['file', 'struct'] _fields = ['size', 'align'] _sort = ['size', 'align'] @@ -137,15 +137,19 @@ class StructResult(co.namedtuple('StructResult', [ __slots__ = () def __new__(cls, file='', struct='', size=0, align=0, - children=None): + i=None, children=None): return super().__new__(cls, file, struct, RInt(size), RInt(align), + i, children if children is not None else []) def __add__(self, other): return StructResult(self.file, self.struct, self.size + other.size, 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) @@ -456,7 +460,9 @@ def collect(obj_paths, *, align_ = alignof(child) children_ = childrenof(child) children.append(StructResult( - file, name_, size_, align_, children_)) + file, name_, size_, align_, + i=child.off, + children=children_)) # indirect type? elif 'DW_AT_type' in entry: type = int(entry['DW_AT_type'].strip('<>'), 0) @@ -519,11 +525,13 @@ def collect(obj_paths, *, # these separately if entry.tag == 'DW_TAG_typedef': typedefs[entry.off] = StructResult( - file, name, size, align, children) + file, name, size, align, + children=children) typedefed.add(int(entry['DW_AT_type'].strip('<>'), 0)) else: types[entry.off] = StructResult( - file, name, size, align, children) + file, name, size, align, + children=children) # let typedefs take priority results.extend(typedefs.values()) @@ -593,34 +601,69 @@ def table(Result, results, diff_results=None, *, if diff_results is not None: 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: - def rec_hot(results_, seen=set()): - if not results_: - return [] + # subclass to reintroduce __dict__ + class HotResult(Result): + 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_, - 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]))) + def __add__(self, other): + return HotResult( + Result.__add__(self, other), + 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.notes + other.notes) - # found a cycle? - if (detect_cycles - and tuple(getattr(r, k) for k in Result._by) in seen): - return [] + def hot_(results_, depth_): + hot_ = [] + def recurse(results_, depth_, seen=set()): + nonlocal hot_ + if not results_: + return - return [r._replace(children=[])] + rec_hot( - r.children, - seen | {tuple(getattr(r, k) for k in Result._by)}) + # find the hottest result + r = max(results_, + 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 table = { @@ -777,7 +820,7 @@ def table(Result, results, diff_results=None, *, names_ = list(table_.keys()) # sort the children layer - names_.sort() + names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n)) if sort: for k, reverse in reversed(sort): names_.sort(