inventory.py counts the functions and call sites that take the whole file as &[u8]; range-trace (standalone crate, x86-64 Linux) records every load clawhdf5 makes from a file by mprotect + single-step, unchanged library code; libhdf5_reads.py counts libhdf5's reads through h5py's fileobj driver and prints a dataset's chunk extents. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
132 lines
5.6 KiB
Python
132 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Inventory of whole-file `&[u8]` parameters in the clawhdf5 workspace.
|
|
|
|
Used by docs/design/range-reads.md. Run from the repository root:
|
|
|
|
python3 docs/design/tools/inventory.py # per-file table
|
|
python3 docs/design/tools/inventory.py --list # every signature
|
|
python3 docs/design/tools/inventory.py --patterns # read patterns per crate
|
|
|
|
A function counts as taking "the whole file" when it has a parameter named
|
|
`file_data: &[u8]` (the repository convention), or a `data` / `file` / `buf` /
|
|
`bytes` / `mmap` parameter of type `&[u8]` *together with* a parameter whose
|
|
name says it is a file address (`*address*`, `*addr*`, `*offset*` of an
|
|
integer type). The second rule is a heuristic; `--list` prints every match so
|
|
it can be checked by eye. Code after the first `#[cfg(test)] mod ... {` in a
|
|
file is excluded (the repository keeps unit tests at the end of each file),
|
|
as are the tests/ and benches/ directories.
|
|
"""
|
|
import os
|
|
import re
|
|
import sys
|
|
from collections import defaultdict
|
|
|
|
ROOT = os.getcwd()
|
|
FN_RE = re.compile(r"\bfn\s+([A-Za-z_][A-Za-z0-9_]*)\s*(<[^()]*?>)?\s*\(", re.S)
|
|
PARAM_RE = re.compile(r"([A-Za-z_][A-Za-z0-9_]*)\s*:\s*&(?:'[a-z_]+\s+)?\[u8\]")
|
|
ADDR_RE = re.compile(r"\b([a-z_]*(?:address|addr|offset)[a-z_]*)\s*:\s*(?:u64|usize|u32)")
|
|
WHOLE_NAMES = {"data", "file", "buf", "bytes", "file_bytes", "mmap"}
|
|
|
|
|
|
def signature(src, start):
|
|
depth, i = 0, start
|
|
while i < len(src):
|
|
c = src[i]
|
|
if c == "(":
|
|
depth += 1
|
|
elif c == ")":
|
|
depth -= 1
|
|
if depth == 0:
|
|
return src[start + 1 : i]
|
|
i += 1
|
|
return ""
|
|
|
|
|
|
def non_test_source(src):
|
|
m = re.search(r"#\[cfg\(test\)\]\s*mod\s+\w+\s*\{", src)
|
|
return src[: m.start()] if m else src
|
|
|
|
|
|
PATTERNS = [
|
|
("`file_data` passed on (call sites)", re.compile(r"[(,]\s*&?file_data\s*[,)]")),
|
|
("`file_data[..]` slicing", re.compile(r"\bfile_data\s*\[")),
|
|
("open-ended `file_data[x..]`", re.compile(r"\bfile_data\s*\[[^\]]*\.\.\s*\]")),
|
|
("`file_data.get(..)`", re.compile(r"\bfile_data\s*\.\s*get\s*\(")),
|
|
("`file_data.len()`", re.compile(r"\bfile_data\s*\.\s*len\s*\(\)")),
|
|
("address/offset `as usize` casts", re.compile(r"\b[a-z_]*(?:addr|address|offset)[a-z_]*\s+as\s+usize")),
|
|
("`ObjectHeader::parse(` calls", re.compile(r"ObjectHeader::parse\s*\(")),
|
|
("`.as_bytes()` on a file/reader", re.compile(r"\b(?:file|reader|data|self\.file|self\.data|self\.file\.data|root\.file)\s*\.\s*as_bytes\s*\(\)")),
|
|
]
|
|
|
|
|
|
def patterns():
|
|
"""Per-crate counts of the read patterns (non-test code only)."""
|
|
per_crate = defaultdict(lambda: [0] * len(PATTERNS))
|
|
for crate in sorted(os.listdir(os.path.join(ROOT, "crates"))):
|
|
srcdir = os.path.join(ROOT, "crates", crate, "src")
|
|
for dp, _, fns in os.walk(srcdir):
|
|
for fn in fns:
|
|
if not fn.endswith(".rs"):
|
|
continue
|
|
with open(os.path.join(dp, fn), encoding="utf-8") as fh:
|
|
src = non_test_source(fh.read())
|
|
for i, (_, rx) in enumerate(PATTERNS):
|
|
per_crate[crate][i] += len(rx.findall(src))
|
|
print("| crate | " + " | ".join(n for n, _ in PATTERNS) + " |")
|
|
print("|---|" + "---:|" * len(PATTERNS))
|
|
tot = [0] * len(PATTERNS)
|
|
for c, v in sorted(per_crate.items()):
|
|
if any(v):
|
|
print("| %s | %s |" % (c, " | ".join(str(x) for x in v)))
|
|
tot = [a + b for a, b in zip(tot, v)]
|
|
print("| **total** | %s |" % " | ".join("**%d**" % x for x in tot))
|
|
|
|
|
|
def main():
|
|
if "--patterns" in sys.argv:
|
|
patterns()
|
|
return
|
|
per_file = defaultdict(lambda: [0, 0, 0]) # [file_data, heuristic, pub]
|
|
rows = []
|
|
for crate in sorted(os.listdir(os.path.join(ROOT, "crates"))):
|
|
srcdir = os.path.join(ROOT, "crates", crate, "src")
|
|
for dp, _, fns in os.walk(srcdir):
|
|
for fn in sorted(fns):
|
|
if not fn.endswith(".rs"):
|
|
continue
|
|
path = os.path.join(dp, fn)
|
|
with open(path, encoding="utf-8") as fh:
|
|
src = non_test_source(fh.read())
|
|
for m in FN_RE.finditer(src):
|
|
sig = signature(src, m.end() - 1)
|
|
params = PARAM_RE.findall(sig)
|
|
kind = None
|
|
if "file_data" in params:
|
|
kind = "file_data"
|
|
elif any(p in WHOLE_NAMES for p in params) and ADDR_RE.search(sig):
|
|
kind = "heuristic"
|
|
if not kind:
|
|
continue
|
|
rel = os.path.relpath(path, ROOT)
|
|
line_start = src.rfind("\n", 0, m.start()) + 1
|
|
is_pub = src[line_start : m.start()].strip().startswith("pub")
|
|
per_file[rel][0 if kind == "file_data" else 1] += 1
|
|
per_file[rel][2] += int(is_pub)
|
|
line = src.count("\n", 0, m.start()) + 1
|
|
rows.append((rel, line, m.group(1), kind, is_pub))
|
|
if "--list" in sys.argv:
|
|
for r in rows:
|
|
print("%s:%d %s [%s%s]" % (r[0], r[1], r[2], r[3], ", pub" if r[4] else ""))
|
|
return
|
|
print("| file | `file_data` fns | other whole-slice fns (heuristic) | of which `pub` |")
|
|
print("|---|---:|---:|---:|")
|
|
tot = [0, 0, 0]
|
|
for f, (a, b, p) in sorted(per_file.items(), key=lambda kv: (-(kv[1][0] + kv[1][1]), kv[0])):
|
|
print("| %s | %d | %d | %d |" % (f, a, b, p))
|
|
tot = [tot[0] + a, tot[1] + b, tot[2] + p]
|
|
print("| **total** | **%d** | **%d** | **%d** |" % tuple(tot))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|