From b3e93a1962e4b3842e7d5f413119e73902f37023 Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Mon, 28 Apr 2025 23:02:31 -0600 Subject: measurestack: Shorten debug f-strings --- build-aux/measurestack/analyze.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'build-aux/measurestack/analyze.py') diff --git a/build-aux/measurestack/analyze.py b/build-aux/measurestack/analyze.py index a93874f..2485970 100644 --- a/build-aux/measurestack/analyze.py +++ b/build-aux/measurestack/analyze.py @@ -356,9 +356,12 @@ def analyze( assert funcname in graphdata.graph + def putdbg(msg: str) -> None: + print(f"//dbg: {'- '*len(chain)}{msg}") + node = graphdata.graph[funcname] if dbg: - print(f"//dbg: {'- '*len(chain)}{funcname}\t{node.nstatic}") + putdbg(f"{funcname}\t{node.nstatic}") if node.usage_kind == "dynamic" or node.ndynamic > 0: dynamic.add(funcname) if track_inclusion: @@ -381,14 +384,12 @@ def analyze( skip, _ = skipmodel(chain, call_orig_qname) if skip: if dbg: - print( - f"//dbg: {'- '*len(chain)}{call_orig_qname}\tskip missing" - ) + putdbg(f"{call_orig_qname}\tskip missing") continue if not call_missing_ok: missing.add(call_orig_qname) if dbg: - print(f"//dbg: {'- '*len(chain)}{call_orig_qname}\tmissing") + putdbg(f"{call_orig_qname}\tmissing") continue # 2. Skip @@ -397,7 +398,7 @@ def analyze( max_call_nchain = max(max_call_nchain, skip_nchain) if skip: if dbg: - print(f"//dbg: {'- '*len(chain)}{call_qname}\tskip") + putdbg(f"{call_qname}\tskip") continue # 3. Call -- cgit v1.2.3-2-g168b From a56ba340a7cb0d6ea8a9c25965cb5311978c0e25 Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Tue, 29 Apr 2025 14:21:25 -0600 Subject: measurestack: Promote and enhance `dbg` to a global `dbg_nstatic` --- build-aux/measurestack/analyze.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) (limited to 'build-aux/measurestack/analyze.py') diff --git a/build-aux/measurestack/analyze.py b/build-aux/measurestack/analyze.py index 2485970..3996b3b 100644 --- a/build-aux/measurestack/analyze.py +++ b/build-aux/measurestack/analyze.py @@ -9,6 +9,9 @@ import typing from . import vcg +# Whether to print "//dbg-nstatic:" lines that trace nstatic() execution +dbg_nstatic = False + # pylint: disable=unused-variable __all__ = [ "BaseName", @@ -337,8 +340,6 @@ def analyze( dynamic: set[QName] = set() included_funcs: set[QName] = set() - dbg = False - track_inclusion: bool = True skipmodels = app.skipmodels() @@ -351,16 +352,15 @@ def analyze( _nstatic_cache: dict[QName, int] = {} def _nstatic(chain: list[QName], funcname: QName) -> tuple[int, int]: - nonlocal dbg nonlocal track_inclusion assert funcname in graphdata.graph def putdbg(msg: str) -> None: - print(f"//dbg: {'- '*len(chain)}{msg}") + print(f"//dbg-nstatic: {'- '*len(chain)}{msg}") node = graphdata.graph[funcname] - if dbg: + if dbg_nstatic: putdbg(f"{funcname}\t{node.nstatic}") if node.usage_kind == "dynamic" or node.ndynamic > 0: dynamic.add(funcname) @@ -383,12 +383,12 @@ def analyze( if skipmodel: skip, _ = skipmodel(chain, call_orig_qname) if skip: - if dbg: + if dbg_nstatic: putdbg(f"{call_orig_qname}\tskip missing") continue if not call_missing_ok: missing.add(call_orig_qname) - if dbg: + if dbg_nstatic: putdbg(f"{call_orig_qname}\tmissing") continue @@ -397,19 +397,26 @@ def analyze( skip, skip_nchain = skipmodel(chain, call_qname) max_call_nchain = max(max_call_nchain, skip_nchain) if skip: - if dbg: + if dbg_nstatic: putdbg(f"{call_qname}\tskip") continue # 3. Call if skip_nchain == 0 and call_qname in _nstatic_cache: - max_call_nstatic = max(max_call_nstatic, _nstatic_cache[call_qname]) + call_nstatic = _nstatic_cache[call_qname] + if dbg_nstatic: + putdbg(f"{call_qname}\ttotal={call_nstatic} (cache-read)") + max_call_nstatic = max(max_call_nstatic, call_nstatic) else: call_nstatic, call_nchain = _nstatic(chain, call_qname) max_call_nstatic = max(max_call_nstatic, call_nstatic) max_call_nchain = max(max_call_nchain, call_nchain) if skip_nchain == 0 and call_nchain == 0: + if dbg_nstatic: + putdbg(f"{call_qname}\ttotal={call_nstatic} (cache-write)") _nstatic_cache[call_qname] = call_nstatic + elif dbg_nstatic: + putdbg(f"{call_qname}\ttotal={call_nstatic} (do-not-cache)") chain.pop() return node.nstatic + max_call_nstatic, max(0, max_call_nchain - 1) -- cgit v1.2.3-2-g168b From ce19c341c2fb1d1386d449b2d135b4a86d9a375f Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Mon, 28 Apr 2025 11:51:50 -0600 Subject: measurestack: Add a `dbg_dumpgraph` toggle --- build-aux/measurestack/analyze.py | 70 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) (limited to 'build-aux/measurestack/analyze.py') diff --git a/build-aux/measurestack/analyze.py b/build-aux/measurestack/analyze.py index 3996b3b..86c51f2 100644 --- a/build-aux/measurestack/analyze.py +++ b/build-aux/measurestack/analyze.py @@ -9,6 +9,8 @@ import typing from . import vcg +# Whether to print the graph in a /* comment */ before processing it +dbg_dumpgraph = False # Whether to print "//dbg-nstatic:" lines that trace nstatic() execution dbg_nstatic = False @@ -24,6 +26,72 @@ __all__ = [ "analyze", ] + +def dumps(x: typing.Any, depth: int = 0, compact: bool = False) -> str: + match x: + case int() | str() | None: + return repr(x) + case dict(): + if len(x) == 0: + return "{}" + ret = "{" + if not compact: + ret += "\n" + for k, v in x.items(): + if not compact: + ret += "\t" * (depth + 1) + ret += dumps(k, depth + 1, True) + ret += ":" + if not compact: + ret += " " + ret += dumps(v, depth + 1, compact) + ret += "," + if not compact: + ret += "\n" + if not compact: + ret += "\t" * depth + ret += "}" + return ret + case list(): + if len(x) == 0: + return "[]" + ret = "[" + if not compact: + ret += "\n" + for v in x: + if not compact: + ret += "\t" * (depth + 1) + ret += dumps(v, depth + 1, compact) + ret += "," + if not compact: + ret += "\n" + if not compact: + ret += "\t" * depth + ret += "]" + return ret + case set(): + if len(x) == 0: + return "set()" + ret = "{" + if not compact: + ret += "\n" + for v in x: + if not compact: + ret += "\t" * (depth + 1) + ret += dumps(v, depth + 1, compact) + ret += "," + if not compact: + ret += "\n" + if not compact: + ret += "\t" * depth + ret += "}" + return ret + case _: + if hasattr(x, "__dict__"): + return f"{x.__class__.__name__}(*{dumps(x.__dict__, depth, compact)})" + return f"TODO({x.__class__.__name__})" + + # types ######################################################################## @@ -335,6 +403,8 @@ def analyze( cfg_max_call_depth: int, ) -> AnalyzeResult: graphdata = _make_graph(ci_fnames, app) + if dbg_dumpgraph: + print(f"/* {dumps(graphdata)} */") missing: set[QName] = set() dynamic: set[QName] = set() -- cgit v1.2.3-2-g168b From 3adb478bb5257ab48f366f58f2a2ce3ae1115b97 Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Mon, 28 Apr 2025 15:55:07 -0600 Subject: measurestack: Add a `dbg_sort` toggle --- build-aux/measurestack/analyze.py | 46 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) (limited to 'build-aux/measurestack/analyze.py') diff --git a/build-aux/measurestack/analyze.py b/build-aux/measurestack/analyze.py index 86c51f2..3601707 100644 --- a/build-aux/measurestack/analyze.py +++ b/build-aux/measurestack/analyze.py @@ -3,6 +3,7 @@ # Copyright (C) 2024-2025 Luke T. Shumaker # SPDX-License-Identifier: AGPL-3.0-or-later +import random import re import sys import typing @@ -13,6 +14,8 @@ from . import vcg dbg_dumpgraph = False # Whether to print "//dbg-nstatic:" lines that trace nstatic() execution dbg_nstatic = False +# Whether to sort things for consistently-ordered execution, or shuffle things to detect bugs +dbg_sort: typing.Literal["unsorted", "sorted", "shuffled"] = "unsorted" # pylint: disable=unused-variable __all__ = [ @@ -20,6 +23,7 @@ __all__ = [ "QName", "UsageKind", "Node", + "maybe_sorted", "AnalyzeResultVal", "AnalyzeResultGroup", "AnalyzeResult", @@ -306,6 +310,39 @@ class _Graph: return self._resolve_cache[funcname] +if typing.TYPE_CHECKING: + from _typeshed import SupportsRichComparisonT as _T_sortable + +_T = typing.TypeVar("_T") + + +@typing.overload +def maybe_sorted( + unsorted: typing.Iterable["_T_sortable"], /, *, key: None = None +) -> typing.Iterable["_T_sortable"]: ... +@typing.overload +def maybe_sorted( + unsorted: typing.Iterable[_T], /, *, key: typing.Callable[[_T], "_T_sortable"] +) -> typing.Iterable[_T]: ... + + +def maybe_sorted( + unsorted: typing.Iterable[_T], + /, + *, + key: typing.Callable[[_T], "_T_sortable"] | None = None, +) -> typing.Iterable[_T]: + match dbg_sort: + case "unsorted": + return unsorted + case "sorted": + return sorted(unsorted, key=key) # type: ignore + case "shuffled": + ret = [*unsorted] + random.shuffle(ret) + return ret + + def _make_graph( ci_fnames: typing.Collection[str], app: Application, @@ -368,7 +405,7 @@ def _make_graph( raise ValueError(f"unknown caller: {caller}") if callee == QName("__indirect_call"): callees, missing_ok = app.indirect_callees(elem) - for callee in callees: + for callee in maybe_sorted(callees): if callee not in graph[caller].calls: graph[caller].calls[callee] = missing_ok else: @@ -376,12 +413,15 @@ def _make_graph( case _: raise ValueError(f"unknown elem type {elem.typ!r}") - for ci_fname in ci_fnames: + for ci_fname in maybe_sorted(ci_fnames): with open(ci_fname, "r", encoding="utf-8") as fh: for elem in vcg.parse_vcg(fh): handle_elem(elem) - for node in app.extra_nodes(): + def sort_key(node: Node) -> QName: + return node.funcname + + for node in maybe_sorted(app.extra_nodes(), key=sort_key): if node.funcname in graph: raise ValueError(f"duplicate node {node.funcname}") graph[node.funcname] = node -- cgit v1.2.3-2-g168b From 8e21fcfc3b332e749135e4081dacb5556b30f5be Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Wed, 30 Apr 2025 18:34:37 -0600 Subject: measurestack: Add `dbg_(no)cache` toggles --- build-aux/measurestack/analyze.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'build-aux/measurestack/analyze.py') diff --git a/build-aux/measurestack/analyze.py b/build-aux/measurestack/analyze.py index 3601707..93bf885 100644 --- a/build-aux/measurestack/analyze.py +++ b/build-aux/measurestack/analyze.py @@ -10,10 +10,14 @@ import typing from . import vcg +# Whether to print "//dbg-cache:" on cache writes +dbg_cache = False # Whether to print the graph in a /* comment */ before processing it dbg_dumpgraph = False # Whether to print "//dbg-nstatic:" lines that trace nstatic() execution dbg_nstatic = False +# Whether to disable nstatic() caching (but does NOT disable any cache-related debug logging) +dbg_nocache = False # Whether to sort things for consistently-ordered execution, or shuffle things to detect bugs dbg_sort: typing.Literal["unsorted", "sorted", "shuffled"] = "unsorted" @@ -512,7 +516,11 @@ def analyze( continue # 3. Call - if skip_nchain == 0 and call_qname in _nstatic_cache: + if ( + (not dbg_nocache) + and skip_nchain == 0 + and call_qname in _nstatic_cache + ): call_nstatic = _nstatic_cache[call_qname] if dbg_nstatic: putdbg(f"{call_qname}\ttotal={call_nstatic} (cache-read)") @@ -524,7 +532,13 @@ def analyze( if skip_nchain == 0 and call_nchain == 0: if dbg_nstatic: putdbg(f"{call_qname}\ttotal={call_nstatic} (cache-write)") - _nstatic_cache[call_qname] = call_nstatic + if call_qname not in _nstatic_cache: + if dbg_cache: + print(f"//dbg-cache: {call_qname} = {call_nstatic}") + _nstatic_cache[call_qname] = call_nstatic + else: + assert dbg_nocache + assert _nstatic_cache[call_qname] == call_nstatic elif dbg_nstatic: putdbg(f"{call_qname}\ttotal={call_nstatic} (do-not-cache)") chain.pop() -- cgit v1.2.3-2-g168b From 7259e7ffbebffa74c1c07257076bcb86fdf08689 Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Mon, 28 Apr 2025 23:37:27 -0600 Subject: measurestack: Fix+test skipmodel chain collections --- build-aux/measurestack/analyze.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'build-aux/measurestack/analyze.py') diff --git a/build-aux/measurestack/analyze.py b/build-aux/measurestack/analyze.py index 93bf885..f454b7e 100644 --- a/build-aux/measurestack/analyze.py +++ b/build-aux/measurestack/analyze.py @@ -232,10 +232,10 @@ class AnalyzeResult(typing.NamedTuple): class SkipModel(typing.NamedTuple): """Running the skipmodel calls `.fn(chain, ...)` with the chain consisting of the last `.nchain` items (if .nchain is an int), or - the chain starting with the *last* occurance of `.nchain` (if - .nchain is a collection). If the chain is not that long or does - not contain a member of the collection, then .fn is not called and - the call is *not* skipped. + the chain starting with the *last* occurance of `.nchain` in + chain[:-1] (if .nchain is a collection). If the chain is not that + long or does not contain a member of the collection, then .fn is + not called and the call is *not* skipped. """ @@ -248,9 +248,9 @@ class SkipModel(typing.NamedTuple): _chain = chain[-self.nchain :] return self.fn(_chain, call), len(_chain) else: - for i in reversed(range(len(chain))): + for i in reversed(range(len(chain) - 1)): if chain[i].base() in self.nchain: - _chain = chain[i - 1 :] + _chain = chain[i:] return self.fn(_chain, call), len(_chain) return False, 0 -- cgit v1.2.3-2-g168b From 6c755aadeb3ffff941667c70a93ef0e7cdd41a98 Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Fri, 2 May 2025 01:38:12 -0600 Subject: measurestack: Fix+test printf measurement --- build-aux/measurestack/analyze.py | 55 +++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 23 deletions(-) (limited to 'build-aux/measurestack/analyze.py') diff --git a/build-aux/measurestack/analyze.py b/build-aux/measurestack/analyze.py index f454b7e..67c44ce 100644 --- a/build-aux/measurestack/analyze.py +++ b/build-aux/measurestack/analyze.py @@ -231,28 +231,39 @@ class AnalyzeResult(typing.NamedTuple): class SkipModel(typing.NamedTuple): """Running the skipmodel calls `.fn(chain, ...)` with the chain - consisting of the last `.nchain` items (if .nchain is an int), or - the chain starting with the *last* occurance of `.nchain` in - chain[:-1] (if .nchain is a collection). If the chain is not that - long or does not contain a member of the collection, then .fn is - not called and the call is *not* skipped. + consisting of the last few items of the input chain. + If `.nchain` is an int: + + - the chain is the last `.nchain` items or the input chain. If + the input chain is not that long, then `.fn` is not called and + the call is *not* skipped. + + If `.nchain` is a collection: + + - the chain starts with the *last* occurance of `.nchain` in the + input chain. If the input chain does not contain a member of + the collection, then .fn is called with an empty chain. """ nchain: int | typing.Collection[BaseName] - fn: typing.Callable[[typing.Sequence[QName], QName], bool] - - def __call__(self, chain: typing.Sequence[QName], call: QName) -> tuple[bool, int]: - if isinstance(self.nchain, int): - if len(chain) >= self.nchain: - _chain = chain[-self.nchain :] - return self.fn(_chain, call), len(_chain) - else: - for i in reversed(range(len(chain) - 1)): - if chain[i].base() in self.nchain: - _chain = chain[i:] - return self.fn(_chain, call), len(_chain) - return False, 0 + fn: typing.Callable[[typing.Sequence[QName], Node, QName], bool] + + def __call__( + self, chain: typing.Sequence[QName], node: Node, call: QName + ) -> tuple[bool, int]: + match self.nchain: + case int(): + if len(chain) >= self.nchain: + _chain = chain[-self.nchain :] + return self.fn(_chain, node, call), len(_chain) + 1 + return False, 0 + case _: + for i in reversed(range(len(chain))): + if chain[i].base() in self.nchain: + _chain = chain[i:] + return self.fn(_chain, node, call), len(_chain) + 1 + return self.fn([], node, call), 1 class Application(typing.Protocol): @@ -458,9 +469,7 @@ def analyze( skipmodels = app.skipmodels() for name, model in skipmodels.items(): - if isinstance(model.nchain, int): - assert model.nchain > 1 - else: + if not isinstance(model.nchain, int): assert len(model.nchain) > 0 _nstatic_cache: dict[QName, int] = {} @@ -495,7 +504,7 @@ def analyze( call_qname = graphdata.resolve_funcname(call_orig_qname) if not call_qname: if skipmodel: - skip, _ = skipmodel(chain, call_orig_qname) + skip, _ = skipmodel(chain[:-1], node, call_orig_qname) if skip: if dbg_nstatic: putdbg(f"{call_orig_qname}\tskip missing") @@ -508,7 +517,7 @@ def analyze( # 2. Skip if skipmodel: - skip, skip_nchain = skipmodel(chain, call_qname) + skip, skip_nchain = skipmodel(chain[:-1], node, call_qname) max_call_nchain = max(max_call_nchain, skip_nchain) if skip: if dbg_nstatic: -- cgit v1.2.3-2-g168b