scripts: Prevented i/children/notes result field collisions

Without this, naming a column i/children/notes in csv.py could cause
things to break. Unlikely for children/notes, but very likely for i,
especially when benchmarking.

Unfortunately namedtuple makes this tricky. I _want_ to just rename
these to _i/_children/_notes and call the problem solved, but namedtuple
reserves all underscore-prefixed fields for its own use.

As a workaround, the table renderer now looks for _i/_children/_notes at
the _class_ level, as an optional name of which namedtuple field to use.
This way Result types can stay lightweight namedtuples while including
extra table rendering info without risk of conflicts.

This also makes the HotResult type a bit more funky, but that's not a
big deal.
This commit is contained in:
Christopher Haster
2024-12-02 19:23:42 -06:00
parent 183ede1b83
commit 8526cd9cf1
9 changed files with 296 additions and 243 deletions

View File

@@ -127,6 +127,7 @@ class StackResult(co.namedtuple('StackResult', [
_fields = ['frame', 'limit']
_sort = ['limit', 'frame']
_types = {'frame': RInt, 'limit': RInt}
_children = 'children'
__slots__ = ()
def __new__(cls, file='', function='', frame=0, limit=0,
@@ -361,29 +362,32 @@ def table(Result, results, diff_results=None, *,
# reduce children to hot paths? only used by some scripts
if hot:
# subclass to reintroduce __dict__
class HotResult(Result):
i = None
children = None
notes = None
Result_ = Result
class HotResult(Result_):
_i = '_hot_i'
_children = '_hot_children'
_notes = '_hot_notes'
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)
self._hot_i = i
self._hot_children = children if children is not None else []
self._hot_notes = notes if notes is not None else []
if hasattr(Result_, '_notes'):
self._hot_notes.extend(getattr(r, r._notes))
return self
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)
Result_.__add__(self, other),
self._hot_i if other._hot_i is None
else other._hot_i if self._hot_i is None
else min(self._hot_i, other._hot_i),
self._hot_children + other._hot_children,
self._hot_notes + other._hot_notes)
def hot_(results_, depth_):
results_ = []
for r in results:
hot_ = []
def recurse(results_, depth_, seen=set()):
nonlocal hot_
@@ -407,20 +411,20 @@ def table(Result, results, diff_results=None, *,
# found a cycle?
if (detect_cycles
and tuple(getattr(r, k) for k in Result._by) in seen):
hot_[-1].notes.append('cycle detected')
hot_[-1]._hot_notes.append('cycle detected')
return
# recurse?
if depth_ > 1:
recurse(r.children,
recurse(getattr(r, Result._children),
depth_-1,
seen | {tuple(getattr(r, k) for k in Result._by)})
recurse(results_, depth_)
return hot_
recurse(getattr(r, Result._children), depth-1)
results_.append(HotResult(r, children=hot_))
results = [r._replace(children=hot_(r.children, depth-1))
for r in results]
Result = HotResult
results = results_
# organize by name
table = {
@@ -562,8 +566,8 @@ def table(Result, results, diff_results=None, *,
getattr(r, k, None),
getattr(diff_r, k, None)))))
# append any notes
if hasattr(r, 'notes'):
entry[-1][1].extend(r.notes)
if hasattr(Result, '_notes'):
entry[-1][1].extend(getattr(r, Result._notes))
return entry
# recursive entry helper, only used by some scripts
@@ -577,7 +581,9 @@ def table(Result, results, diff_results=None, *,
names_ = list(table_.keys())
# sort the children layer
names_.sort(key=lambda n: (getattr(table_[n], 'i', None), n))
names_.sort()
if hasattr(Result, '_i'):
names_.sort(key=lambda n: getattr(table_[n], Result._i))
if sort:
for k, reverse in reversed(sort):
names_.sort(
@@ -611,7 +617,7 @@ def table(Result, results, diff_results=None, *,
# recurse?
if depth_ > 1:
recurse(r.children,
recurse(getattr(r, Result._children),
depth_-1,
seen | {name},
(prefixes[2+is_last] + "|-> ",
@@ -631,7 +637,7 @@ def table(Result, results, diff_results=None, *,
# recursive entries
if name in table and depth > 1:
recurse(table[name].children,
recurse(getattr(table[name], Result._children),
depth-1,
{name},
("|-> ",