scripts: Added better cycles detection to stack.py

stack.py actually already had a simple cycle detector, since we needed
one to calculate stack limits without getting stuck.

Copying this simple cycle detector into the actual table rendering code
lets us print a nice little "cycle detected" message, instead of just
vomiting to stdout forever:

    $ ./scripts/stack.py lfs.ci lfs_util.ci -z -s
    function                       frame    limit
    lfsr_format                      320        ∞
    |-> lfsr_mountinited             304        ∞
    |   |-> lfsr_mountmroot           80        ∞
    |   |   |-> lfsr_mountmroot       80        ∞ (cycle detected)
    |   |   |-> lfsr_mdir_lookup      48      576
    ... snip ...

The cycle detector is a bit naive, just building a new set each step,
but it gets the job done.

As for perf.py and perfbd.py, it turns out they can't actually create
cycles, so no need for a cycle detector. This is good because I didn't
really want to test these scripts again :)
This commit is contained in:
Christopher Haster
2024-11-02 22:05:23 -05:00
parent bc587e7166
commit fc45af3f6e
3 changed files with 31 additions and 13 deletions

View File

@ -463,26 +463,32 @@ def table(Result, results, diff_results=None, *,
widths[i] = max(widths[i], ((len(x[0])+1+4-1)//4)*4-1)
notes[i] = max(notes[i], 1+2*len(x[1])+sum(len(n) for n in x[1]))
# adjust the name width based on the call depth
if not summary:
# find the actual depth
depth_ = depth
if m.isinf(depth_):
# find the actual depth, this may not terminate! in which
# case it's up to the user to provide an explicit depth
def rec_depth(names_):
def rec_depth(names_, seen=set()):
depth_ = -1
for name in names_:
# found a cycle?
if name in seen:
continue
# recurse?
if name in table:
children = {
','.join(str(getattr(Result(*c), k) or '')
for k in by)
for c in table[name].children}
depth_ = max(depth_,
rec_depth([n for n in names if n in children]))
rec_depth(
[n for n in names if n in children],
seen | {name}))
return depth_ + 1
depth_ = rec_depth(names)
# adjust the name width based on the call depth
widths[0] += 4*max(depth_-1, 0)
# print the tree recursively
@ -496,7 +502,7 @@ def table(Result, results, diff_results=None, *,
if not summary:
line_table = {n: l for n, l in zip(names, lines[1:-1])}
def recurse(names_, depth_, prefixes=('', '', '', '')):
def recurse(names_, depth_, seen=set(), prefixes=('', '', '', '')):
for i, name in enumerate(names_):
if name not in line_table:
continue
@ -508,9 +514,18 @@ def table(Result, results, diff_results=None, *,
widths[0] - len(prefixes[0+is_last]), line[0][0],
' '.join('%*s%-*s' % (
widths[i], x[0],
notes[i], ' (%s)' % ', '.join(x[1]) if x[1] else '')
notes[i],
' (%s)' % ', '.join(it.chain(
x[1], ['cycle detected']))
if i == len(widths)-1 and name in seen
else ' (%s)' % ', '.join(x[1]) if x[1]
else '')
for i, x in enumerate(line[1:], 1))))
# found a cycle?
if name in seen:
continue
# recurse?
if name in table and depth_ > 1:
children = {
@ -520,6 +535,7 @@ def table(Result, results, diff_results=None, *,
# note we're maintaining sort order
[n for n in names if n in children],
depth_-1,
seen | {name},
(prefixes[2+is_last] + "|-> ",
prefixes[2+is_last] + "'-> ",
prefixes[2+is_last] + "| ",