#!/usr/bin/env python3 # build-aux/tent-graph - Take dbg_noncache=True dbg_nstatic=True stack.c on stdin, and produce a tent graph SVG on stdout # # Copyright (C) 2025 Luke T. Shumaker # SPDX-License-Identifier: AGPL-3.0-or-later import ast import re import sys class Block: title: str parent: "Block|None" children: list["Block"] nbytes: int def __init__(self, *, title: str, nbytes: int, parent: "Block|None") -> None: self.title = title self.parent = parent self.children = [] self.nbytes = nbytes @property def rows(self) -> int: if not self.children: return 1 return sum(c.rows for c in self.children) @property def sum_nbytes(self) -> int: if not self.children: return self.nbytes return self.nbytes + max(c.sum_nbytes for c in self.children) def prune(self) -> None: tgt = self.sum_nbytes - self.nbytes self.children = [c for c in self.children if c.sum_nbytes == tgt] re_line = re.compile( r"^//dbg-nstatic:(?P(?: -)*) QName\((?P.*)\)\t(?P[0-9]+)$" ) def parse() -> list[Block]: roots: list[Block] = [] stack: list[Block] = [] for line in sys.stdin: m = re_line.fullmatch(line.strip()) if not m: continue depth = len(m.group("indent")) // 2 func = ast.literal_eval(m.group("func")) size = int(m.group("size"), 10) stack = stack[:depth] block = Block( title=func, nbytes=size, parent=stack[-1] if stack else None, ) if block.parent: block.parent.children.append(block) else: roots.append(block) stack.append(block) return roots def render(roots: list[Block]) -> None: total_nbytes = max(r.sum_nbytes for r in roots) total_rows = sum(r.rows for r in roots) img_w = 1920 img_h = 948 details_h = 16 text_yoff = 12 text_xoff = 3 main_h = img_h - details_h nbyte_h = main_h / total_nbytes row_w = img_w / total_rows print( f""" """ ) min_nbytes = roots[0].nbytes max_nbytes = 0 def visit(b: Block) -> None: nonlocal min_nbytes nonlocal max_nbytes min_nbytes = min(min_nbytes, b.nbytes) max_nbytes = max(max_nbytes, b.nbytes) for c in b.children: visit(c) for r in roots: visit(r) def print_block(block: Block, nbyte: int, row: int) -> None: nonlocal min_nbytes nonlocal max_nbytes if block.nbytes: hue = 100 - int( ((block.nbytes - min_nbytes) / (max_nbytes - min_nbytes)) * 100 ) x = row * row_w y = nbyte * nbyte_h w = max(1, block.rows * row_w - 1) h = block.nbytes * nbyte_h title = f"{block.title} = {block.nbytes} / {block.sum_nbytes} bytes" nonlocal main_h print(f'') print(f"\t{title}") print( f'\t' ) short_title = title.rsplit(":", 1)[-1] if h > details_h and w > len(short_title) * 10: print( f'\t{short_title}' ) print("") def sort_key(c: Block) -> int: return c.sum_nbytes for c in sorted(block.children, key=sort_key, reverse=True): print_block(c, nbyte + block.nbytes, row) row += c.rows row = 0 for r in roots: print_block(r, 0, row) row += r.rows print("") def main() -> None: roots = parse() # tgt = max(r.sum_nbytes for r in roots) # roots = [r for r in roots if r.sum_nbytes == tgt] render(roots) if __name__ == "__main__": main()