Skip to content

Commit 457f36e

Browse files
[temp] probe: mcpp#874 re-measured in the reporter's workspace
Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
1 parent 669dc20 commit 457f36e

27 files changed

Lines changed: 286 additions & 8587 deletions

‎.github/probe/874-measure.py‎

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
#!/usr/bin/env python3
2+
# mcpp#874 re-measure: the areas of the issue's tables, read from a workspace's
3+
# target/ and the global build cache after `mcpp test --workspace`. It prints
4+
# and never asserts.
5+
#
6+
# 874-measure.py WORKSPACE SINCE_EPOCH [LABEL]
7+
# 874-measure.py --snapshot WORKSPACE > lines.json
8+
#
9+
# With NINJA_BASE=lines.json, the edges of a run are the lines its .ninja_log
10+
# files gained since the snapshot (an edge whose output restat kept counts too).
11+
import collections, json, os, subprocess, sys, time
12+
13+
def ninja_logs(target):
14+
return [os.path.join(r, ".ninja_log") for r, _, fs in os.walk(target) if ".ninja_log" in fs]
15+
16+
if sys.argv[1] == "--snapshot":
17+
t = os.path.join(sys.argv[2], "target")
18+
print(json.dumps({os.path.relpath(l, t): sum(1 for _ in open(l, errors="replace")) for l in ninja_logs(t)}))
19+
sys.exit(0)
20+
21+
ws, since = sys.argv[1], float(sys.argv[2])
22+
base = json.load(open(os.environ["NINJA_BASE"])) if os.environ.get("NINJA_BASE") else None
23+
label = sys.argv[3] if len(sys.argv) > 3 else "run"
24+
target = os.path.join(ws, "target")
25+
home = os.environ.get("MCPP_HOME") or os.path.expanduser("~/.mcpp")
26+
out = []
27+
def say(s=""): out.append(s); print(s)
28+
29+
def du(p):
30+
total = 0
31+
seen = set()
32+
for root, _, files in os.walk(p):
33+
for f in files:
34+
try: st = os.lstat(os.path.join(root, f))
35+
except OSError: continue
36+
if (st.st_dev, st.st_ino) in seen: continue
37+
seen.add((st.st_dev, st.st_ino)); total += st.st_blocks * 512
38+
return total
39+
def gb(n): return f"{n / 2**30:.2f} GiB" if n >= 2**30 else f"{n / 2**20:.0f} MiB"
40+
41+
def sections(p):
42+
"""(name, size, flags) of each section; flags hold C when compressed."""
43+
rows = []
44+
for l in subprocess.run(["readelf", "-S", "-W", p], capture_output=True, text=True).stdout.splitlines():
45+
if "]" not in l or l.lstrip().startswith("[Nr]"): continue
46+
t = l.split("]", 1)[1].split()
47+
if len(t) >= 9: rows.append((t[0], int(t[4], 16), t[6] if len(t) >= 10 else ""))
48+
return rows
49+
50+
def is_elf(p):
51+
try:
52+
with open(p, "rb") as f: return f.read(4) == b"\x7fELF"
53+
except OSError: return False
54+
55+
say(f"## {label}")
56+
say()
57+
say("### 1. Areas of target/ (allocated bytes, hard links counted once)")
58+
say()
59+
say("| Where | Size |")
60+
say("|---|---:|")
61+
fps = []
62+
for triple in sorted(os.listdir(target)) if os.path.isdir(target) else []:
63+
tdir = os.path.join(target, triple)
64+
if not os.path.isdir(tdir) or triple.startswith("."): continue
65+
for fp in sorted(os.listdir(tdir)):
66+
d = os.path.join(tdir, fp)
67+
if not os.path.isdir(d): continue
68+
fps.append(d)
69+
for area in ("obj", "gcm.cache", "pcm.cache", "bin", "lib"):
70+
if os.path.isdir(os.path.join(d, area)):
71+
say(f"| `{triple}/{fp[:8]}…/{area}/` | {gb(du(os.path.join(d, area)))} |")
72+
for area in ("host", "out"):
73+
p = os.path.join(target, ".build-mcpp", area)
74+
if os.path.isdir(p): say(f"| `target/.build-mcpp/{area}/` | {gb(du(p))} |")
75+
say(f"| **`target/` in all** | **{gb(du(target))}** |")
76+
say()
77+
78+
say("### 2. bin/")
79+
say()
80+
for d in fps:
81+
b = os.path.join(d, "bin")
82+
if not os.path.isdir(b): continue
83+
tests, archives, shared, inodes = [], [], [], {}
84+
for root, _, files in os.walk(b):
85+
for f in files:
86+
p = os.path.join(root, f)
87+
try: st = os.lstat(p)
88+
except OSError: continue
89+
if f.endswith(".a"): archives.append((p, st))
90+
elif ".so" in f and not f.endswith(".json"): shared.append((p, st))
91+
elif os.access(p, os.X_OK) and is_elf(p) and ("test" in f or "/tests/" in p): tests.append((p, st))
92+
say(f"`{os.path.relpath(b, ws)}`")
93+
say()
94+
if tests:
95+
sizes = sorted(st.st_size for _, st in tests)
96+
compressed = 0
97+
sample = max(tests, key=lambda t: t[1].st_size)[0]
98+
for p, _ in tests[:400]:
99+
if any(n == ".debug_info" and "C" in fl or n.startswith(".zdebug") for n, _, fl in sections(p)):
100+
compressed += 1
101+
say(f"- test programs: {len(tests)}, {gb(sum(sizes))}; median {gb(sizes[len(sizes)//2])}, largest {gb(sizes[-1])} (`{os.path.basename(sample)}`); compressed debug sections in {compressed} of {min(len(tests), 400)} read")
102+
dbg = [(n, z, fl) for n, z, fl in sections(sample) if n.startswith((".debug_", ".zdebug"))]
103+
say(f" - `{os.path.basename(sample)}`: debug sections {gb(sum(z for _, z, _ in dbg))} of {gb(os.path.getsize(sample))}" + (", compressed" if any("C" in fl for _, _, fl in dbg) else ""))
104+
if archives:
105+
members, distinct, thin = 0, set(), 0
106+
for p, st in archives:
107+
with open(p, "rb") as f: thin += f.read(8) == b"!<thin>\n"
108+
names = subprocess.run(["ar", "t", p], capture_output=True, text=True).stdout.split()
109+
members += len(names); distinct.update(names)
110+
big = sorted(archives, key=lambda a: -a[1].st_size)[:4]
111+
say(f"- member archives: {len(archives)}, {gb(sum(st.st_blocks * 512 for _, st in archives))} allocated; {members} members for {len(distinct)} distinct names; thin: {thin}")
112+
say(" - largest: " + ", ".join(f"`{os.path.basename(p)}` {gb(st.st_size)}" for p, st in big))
113+
if shared:
114+
uniq = {(st.st_dev, st.st_ino): st for _, st in shared}
115+
dirs = {os.path.dirname(p) for p, _ in shared}
116+
say(f"- staged shared objects: {len(shared)} files in {len(dirs)} directories, {len(uniq)} distinct inodes, {gb(sum(st.st_size for st in uniq.values()))} distinct, {gb(sum(st.st_size for _, st in shared))} as named")
117+
say()
118+
119+
say("### 3. Build programs")
120+
say()
121+
host = os.path.join(target, ".build-mcpp", "host")
122+
if os.path.isdir(host):
123+
nested = [r for r, ds, _ in os.walk(host) if r.endswith(os.path.join("target", ".build-mcpp", "host"))]
124+
progs = []
125+
for root, _, files in os.walk(host):
126+
for f in files:
127+
if f in ("build-program", "build.mcpp.bin") or f.startswith("build-program"):
128+
p = os.path.join(root, f)
129+
if is_elf(p): progs.append((os.path.relpath(p, host), os.path.getsize(p)))
130+
say(f"- groups under `target/.build-mcpp/host/`: {len([x for x in os.listdir(host) if os.path.isdir(os.path.join(host, x))])}")
131+
say(f"- nested `target/.build-mcpp/host` inside a group: {len(nested)}" + ("".join(f"\n - `{os.path.relpath(n, target)}`" for n in nested[:5])))
132+
say(f"- build-program executables: {len(progs)}, {gb(sum(s for _, s in progs))}")
133+
outd = os.path.join(target, ".build-mcpp", "out")
134+
if os.path.isdir(outd):
135+
say(f"- generated-source trees under `target/.build-mcpp/out/`: {len(os.listdir(outd))}")
136+
say()
137+
138+
say("### 4. Edges of this run (.ninja_log)")
139+
say()
140+
count, ms, hashes = collections.Counter(), collections.Counter(), collections.defaultdict(set)
141+
logs = ninja_logs(target)
142+
for log in logs:
143+
lines = open(log, errors="replace").read().split("\n")
144+
skip = base.get(os.path.relpath(log, target), 0) if base is not None else 1
145+
for line in lines[max(skip, 1):]:
146+
if line.count("\t") != 4: continue
147+
start, end, mtime, o, h = line.split("\t")
148+
t = int(mtime) / 1e9 if int(mtime) > 1e12 else int(mtime)
149+
if base is not None or t >= since:
150+
key = os.path.join(os.path.dirname(log), o)
151+
count[key] += 1; ms[key] += int(end) - int(start); hashes[key].add(h)
152+
again = [o for o, c in count.items() if c > 1]
153+
say(f"- logs: {len(logs)}; edges ran: {sum(count.values())} for {len(count)} distinct outputs")
154+
say(f"- outputs built more than once: {len(again)}; repeated minutes: {round(sum(ms[o] * (count[o] - 1) / count[o] for o in again) / 60000, 1)} of {round(sum(ms.values()) / 60000, 1)}")
155+
for o in sorted(again, key=lambda o: -count[o])[:8]:
156+
say(f" - `{os.path.relpath(o, target)}`: {count[o]} runs, {len(hashes[o])} command hashes")
157+
say()
158+
159+
say("### 5. Host tools in the global cache")
160+
say()
161+
tool = os.path.join(home, "build-cache", "v1", "tool")
162+
if os.path.isdir(tool):
163+
keys = collections.defaultdict(list)
164+
for root, ds, files in os.walk(tool):
165+
if "entry.json" in files:
166+
rel = os.path.relpath(root, tool).split(os.sep)
167+
keys[os.sep.join(rel[:-1])].append(root)
168+
path_keys = [k for k in keys if "+path." in k]
169+
say(f"- `build-cache/v1/tool/`: {gb(du(tool))}; {len(keys)} keys, {sum(len(v) for v in keys.values())} variants; path-package keys: {len(path_keys)}")
170+
for k, v in sorted(keys.items(), key=lambda kv: -len(kv[1]))[:8]:
171+
say(f" - `{k}`: {len(v)} variant(s)")
172+
std = os.path.join(home, "build-cache", "v1", "std")
173+
if os.path.isdir(std): say(f"- `build-cache/v1/std/`: {len(os.listdir(std))} builds, {gb(du(std))}")
174+
say()
175+
176+
with open(os.environ.get("GITHUB_STEP_SUMMARY", os.devnull), "a") as f: f.write("\n".join(out) + "\n\n")

‎.github/workflows/aur-publish.yml‎

Lines changed: 0 additions & 163 deletions
This file was deleted.

0 commit comments

Comments
 (0)