import argparse import io import json import subprocess import tarfile import tempfile from datetime import date from html import escape from pathlib import Path import progress SERIES = (("libraries", "System library functions", "#2f81f7"), ("shaders", "GPU shader instructions", "#2ea043")) WIDTH, LEFT, RIGHT, TITLE, PANEL, GAP, AXIS = 900, 56, 190, 34, 150, 16, 32 MAX_DOTS = 45 BACKGROUND, GRID, TEXT, MUTED = "#0d1117", "#30363d", "#c9d1d9", "#8b949e" def git(*args): return subprocess.run(["git", "-C", str(progress.ROOT), *args], check=True, capture_output=True).stdout def daily_commits(ref): seen = {} for line in git("log", "--first-parent", "--format=%H %cs", ref).decode().splitlines(): sha, day = line.split() seen.setdefault(day, sha) return sorted(seen.items()) def measure(sha): paths = [progress.PRX.relative_to(progress.ROOT).as_posix(), progress.OPCODES.relative_to(progress.ROOT).as_posix()] with tempfile.TemporaryDirectory() as directory: root = Path(directory) try: archive = tarfile.open(fileobj=io.BytesIO(git("archive", sha, "--", *paths))) if hasattr(tarfile, "data_filter"): archive.extractall(root, filter="data") else: archive.extractall(root) except subprocess.CalledProcessError: return None saved = progress.PRX, progress.OPCODES progress.PRX, progress.OPCODES = root / paths[0], root / paths[1] try: libraries, shaders = progress.collect_libraries(), progress.collect_shaders() except (OSError, AttributeError): return None finally: progress.PRX, progress.OPCODES = saved return {name: {key: data[key] for key in ("done", "total", "percent")} for name, data in (("libraries", libraries), ("shaders", shaders))} def collect(ref): points = [] for day, sha in daily_commits(ref): counts = measure(sha) if counts: points.append({"date": day, "commit": sha, **counts}) return points def labelled(day, span): if span <= 14: return f"{day:%b} {day.day}" if span <= 150: return f"{day:%b} {day.day}" if day.weekday() == 0 else "" return f"{day:%b %Y}" if day.day <= 7 and day.weekday() == 0 else "" def scale(peak): step = next(s for s in (5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000) if peak <= s * 4) return step, max(step, -(-peak // step) * step) def spread(ys, gap=15): out = list(ys) order = sorted(range(len(ys)), key=ys.__getitem__) for above, below in zip(order, order[1:]): out[below] = max(out[below], out[above] + gap) return out def chart(points): days = [date.fromisoformat(p["date"]).toordinal() for p in points] first, span = days[0], max(days[-1] - days[0], 1) px = lambda d: LEFT + (d - first) / span * (WIDTH - LEFT - RIGHT) xs = [px(d) for d in days] edges = [LEFT] + [(a + b) / 2 for a, b in zip(xs, xs[1:])] + [WIDTH - RIGHT] labels = [(px(d), labelled(date.fromordinal(d), span)) for d in range(first, days[-1] + 1)] height = len(SERIES) * (TITLE + PANEL + GAP) - GAP + AXIS out = [f'") return "\n".join(out) def page(points): rows = "\n".join(f'
Each panel has its own scale: the solid line is what is implemented, the dashed line what is declared. Library '
'functions are declared in the prx sources; the shader total is the AMD RDNA 1 + RDNA 2 instruction list '
f'(tools/rdna_isa.txt). '
'One point per day with commits on main, taken at the last commit of the day. Every point is measured '
f'with the current tools/progress.py rules and instruction list, '
'so the series is comparable across days. Hover a day for the exact numbers. Raw numbers: history.json. '
'Back to the current progress.
| Date | ' + "".join(f'{escape(label)} | % | ' for _, label, _ in SERIES) + '
|---|