Skip to content

Commit 16ea3bc

Browse files
author
merge-queue-bot
committed
Merge PR #611: docs(bench): link performance page to the assets-branch benchmark write-up
2 parents 957998a + 55eabdd commit 16ea3bc

10 files changed

Lines changed: 466 additions & 16 deletions

File tree

.github/workflows/release.yml

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,10 +1154,12 @@ jobs:
11541154
run: go run ./cmd/mdsmith-release bench /tmp/mdsmith-bench
11551155
- name: Normalize the freshly measured fragments
11561156
# Normalize the gen_fragments.py tables to the gate's canonical
1157-
# form (the same step benchmark.yml runs). No host page is
1158-
# re-spliced here: the pages job bakes performance.md from the
1159-
# artifact at deploy time, and the README links to
1160-
# results.fragment.md directly.
1157+
# form (the same step benchmark.yml runs). Only the fragments
1158+
# are touched here — those feed the artifact. The benchmark
1159+
# README is spliced and link-rewritten later, in the publish
1160+
# step below, solely to produce the assets-branch prose copy;
1161+
# the site's own performance.md is baked from the artifact at
1162+
# deploy time.
11611163
run: |
11621164
go run ./cmd/mdsmith fix \
11631165
docs/research/benchmarks/results.fragment.md \
@@ -1182,10 +1184,11 @@ jobs:
11821184
if-no-files-found: error
11831185
- name: Publish the numbers to the assets branch
11841186
# GitHub Actions cannot open PRs here, but it can push to the
1185-
# orphan `assets` branch. Push this release's data + fragments
1186-
# there, so the README's [bench-live] link target
1187-
# (assets/benchmarks/results.fragment.md) shows the release's
1188-
# numbers. This job OWNS assets/benchmarks/; demo.yml owns
1187+
# orphan `assets` branch. Push this release's data, fragments,
1188+
# and the rendered prose page there, so the performance page's
1189+
# link to assets/benchmarks/pages/benchmark.md shows the
1190+
# release's freshly measured numbers. This job OWNS
1191+
# assets/benchmarks/; demo.yml owns
11891192
# assets/demo.gif and benchmark.yml owns assets/benchmarks-drift/,
11901193
# so the three subtrees are disjoint. Subtree-safe and
11911194
# best-effort: it rewrites only assets/benchmarks/, commits only
@@ -1200,17 +1203,30 @@ jobs:
12001203
git config user.name "github-actions[bot]"
12011204
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
12021205
1203-
# Stage the subtree before switching branches: the raw data and
1204-
# the two fragments. results.fragment.md is the README's link
1205-
# target; both render as clean tables with no inner links to
1206-
# 404 on the assets branch.
1206+
# Stage the subtree before switching branches: the raw data,
1207+
# the two fragments, and the rendered prose page. The fragments
1208+
# render as clean tables with no inner links to 404 on the
1209+
# assets branch.
12071210
rm -rf /tmp/bench-assets
1208-
mkdir -p /tmp/bench-assets/data
1211+
mkdir -p /tmp/bench-assets/data /tmp/bench-assets/pages
12091212
cp docs/research/benchmarks/results.fragment.md \
12101213
docs/research/benchmarks/headline.fragment.md \
12111214
/tmp/bench-assets/
12121215
cp docs/research/benchmarks/data/*.json /tmp/bench-assets/data/
12131216
1217+
# Publish the prose research write-up too. Splice this release's
1218+
# fresh fragments into the README, then rewrite its repo-relative
1219+
# links to absolute GitHub URLs so none 404 on the assets branch
1220+
# (the sibling files do not exist there). Lands at
1221+
# assets/benchmarks/pages/benchmark.md — the rendered, fresh-
1222+
# numbers copy the performance page links to. Both `go run`s
1223+
# execute here, on the full source tree, before the branch
1224+
# switch below; the working-tree README edit is then discarded
1225+
# by `git checkout -f`, so main's committed snapshot is untouched.
1226+
go run ./cmd/mdsmith fix docs/research/benchmarks/README.md
1227+
go run ./cmd/mdsmith-release render-bench-page \
1228+
/tmp/bench-assets/pages/benchmark.md
1229+
12141230
# The push authenticates via a masked http.extraheader (checkout
12151231
# ran with persist-credentials: false).
12161232
auth_header=$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 -w0)

cmd/mdsmith-release/main.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
// mdsmith-release merge-coverage -o <out> <profile>...
2727
// mdsmith-release test-summary
2828
// mdsmith-release bench [workdir]
29+
// mdsmith-release render-bench-page <out-path>
2930
// mdsmith-release pgo [workdir]
3031
// mdsmith-release pull-site-assets
3132
// mdsmith-release sync-messaging [--check]
@@ -75,6 +76,7 @@ Commands:
7576
test-summary Tally unit/integration/e2e tests from a go test -json stream on stdin.
7677
bench [workdir] Run the pinned cross-tool benchmark; promote JSON + fragments.
7778
bench-check <base> <fresh> Fail if mdsmith regressed vs mado between two benchmark snapshots.
79+
render-bench-page <out-path> Render the benchmark README (links → GitHub) for the assets branch.
7880
pgo [workdir] Generate a PGO profile over the bench corpora into cmd/mdsmith/default.pgo.
7981
pull-site-assets Fetch the published demo GIF for the site build.
8082
sync-messaging [--check] Propagate docs/brand/messaging.md into every tracked surface (or check drift).
@@ -187,6 +189,8 @@ func dispatchGenerators(cmd, root string, rest []string) int {
187189
return runBench(root, rest)
188190
case "bench-check":
189191
return runBenchCheck(root, rest)
192+
case "render-bench-page":
193+
return runRenderBenchPage(root, rest)
190194
case "pgo":
191195
return runPGO(root, rest)
192196
default:
@@ -682,6 +686,31 @@ func runBenchCheck(_ string, args []string) int {
682686
release.BenchCheckConfig{Tolerance: tolerance}))
683687
}
684688

689+
func runRenderBenchPage(root string, args []string) int {
690+
fs := flag.NewFlagSet("render-bench-page", flag.ContinueOnError)
691+
fs.Usage = func() {
692+
fmt.Fprintf(os.Stderr, "Usage: mdsmith-release render-bench-page <out-path>\n\n"+
693+
"Read docs/research/benchmarks/README.md (run `mdsmith fix`\n"+
694+
"on it first so its <?include?> tables carry the freshly\n"+
695+
"measured numbers), rewrite every repo-relative link to an\n"+
696+
"absolute GitHub URL on main, and write the result to\n"+
697+
"<out-path>. release.yml's benchmark-publish job publishes\n"+
698+
"that file to assets/benchmarks/pages/benchmark.md on the\n"+
699+
"orphan assets branch, the rendered fresh-numbers copy the\n"+
700+
"performance page links to and whose inner links resolve.\n")
701+
}
702+
if err := fs.Parse(args); err != nil {
703+
if code := reportFlagParseErr(err, os.Stderr, "mdsmith-release: render-bench-page"); code >= 0 {
704+
return code
705+
}
706+
}
707+
if fs.NArg() != 1 {
708+
fs.Usage()
709+
return 2
710+
}
711+
return reportError(release.RenderBenchPage(root, fs.Arg(0)))
712+
}
713+
685714
func runPullSiteAssets(root string, args []string) int {
686715
fs := flag.NewFlagSet("pull-site-assets", flag.ContinueOnError)
687716
fs.Usage = func() {

cmd/mdsmith-release/main_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ func TestRunRejectsBadArity(t *testing.T) {
6565
{"package-obsidian without args", []string{"package-obsidian"}},
6666
{"package-obsidian with one arg", []string{"package-obsidian", "dist"}},
6767
{"build-website with three positionals", []string{"build-website", "a", "b", "c"}},
68+
{"render-bench-page without out-path", []string{"render-bench-page"}},
69+
{"render-bench-page with extra arg", []string{"render-bench-page", "a", "b"}},
6870
{"pgo with extra args", []string{"pgo", "workdir", "extra"}},
6971
}
7072
for _, c := range cases {
@@ -121,6 +123,7 @@ func TestSubcommandHelpExitsZero(t *testing.T) {
121123
"test-summary",
122124
"bench",
123125
"bench-check",
126+
"render-bench-page",
124127
"pgo",
125128
} {
126129
assert.Equal(t, 0, run([]string{sub, "--help"}), "%s --help", sub)
@@ -149,6 +152,7 @@ func TestSubcommandRejectsUnknownFlag(t *testing.T) {
149152
"test-summary",
150153
"bench",
151154
"bench-check",
155+
"render-bench-page",
152156
"pgo",
153157
} {
154158
assert.Equal(t, 2, run([]string{sub, "--bogus"}), "%s --bogus", sub)
@@ -820,6 +824,45 @@ func TestPrintCheckResult(t *testing.T) {
820824
}
821825
}
822826

827+
// TestRunRenderBenchPageEndToEnd dispatches through `run
828+
// render-bench-page` against a staged benchmark README so the
829+
// subcommand wiring (arity check, default-toolkit handoff,
830+
// reportError) runs end-to-end and the output carries a GitHub-
831+
// rewritten link.
832+
func TestRunRenderBenchPageEndToEnd(t *testing.T) {
833+
root := t.TempDir()
834+
readme := filepath.Join(root, "docs", "research", "benchmarks", "README.md")
835+
require.NoError(t, os.MkdirAll(filepath.Dir(readme), 0o755))
836+
require.NoError(t, os.WriteFile(readme,
837+
[]byte("# Bench\n\nReproduce with [`run.sh`](run.sh).\n"), 0o644))
838+
839+
wd, err := os.Getwd()
840+
require.NoError(t, err)
841+
t.Cleanup(func() { _ = os.Chdir(wd) })
842+
require.NoError(t, os.Chdir(root))
843+
844+
out := filepath.Join(root, "pages", "benchmark.md")
845+
assert.Equal(t, 0, run([]string{"render-bench-page", out}))
846+
847+
got, err := os.ReadFile(out)
848+
require.NoError(t, err)
849+
assert.Contains(t, string(got),
850+
"https://github.com/jeduden/mdsmith/blob/main/docs/research/benchmarks/run.sh")
851+
}
852+
853+
// TestRunRenderBenchPageReportsError covers reportError's non-nil
854+
// branch: no benchmark README in cwd, so RenderBenchPage's ReadFile
855+
// fails and the subcommand exits 1.
856+
func TestRunRenderBenchPageReportsError(t *testing.T) {
857+
root := t.TempDir()
858+
wd, err := os.Getwd()
859+
require.NoError(t, err)
860+
t.Cleanup(func() { _ = os.Chdir(wd) })
861+
require.NoError(t, os.Chdir(root))
862+
863+
assert.Equal(t, 1, run([]string{"render-bench-page", filepath.Join(root, "out.md")}))
864+
}
865+
823866
// writeBenchExport writes a minimal hyperfine export with mdsmith and
824867
// mado medians (passed as JSON number literals) into path.
825868
func writeBenchExport(t *testing.T, path, mdsmith, mado string) {

docs/development/release-tooling.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ setup step.
6969
| `test-summary` | `ci.yml` test job |
7070
| `bench [workdir]` | `benchmark.yml` record; `release.yml` benchmark-publish; `run.sh` |
7171
| `bench-check <base> <fresh>` | `release.yml` benchmark-publish + bench-regression-gate |
72+
| `render-bench-page <out>` | `release.yml` benchmark-publish |
7273
| `pull-site-assets` | `pages.yml` deploy job |
7374
| `sync-messaging [--check]` | `ci.yml` messaging-drift; local sync |
7475
| `sync-channels [--check]` | `ci.yml` channels-drift; local sync |

docs/features/performance.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ gate (`check-bench`, modelled on the LSP latency gate) fails the
4848
build if a 60- or 600-file synthetic check regresses past its
4949
budget. See the
5050
[benchmark research doc](../research/benchmarks/README.md) for the
51-
full cross-tool comparison.
51+
full cross-tool comparison — on the published site this link
52+
resolves to the rendered copy on the `assets` branch, which
53+
carries each release's freshly measured numbers.
5254

5355
See the [`check`](../reference/cli/check.md) reference for flags
5456
and exit codes.

docs/research/benchmarks/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -455,8 +455,9 @@ Four gates in CI:
455455
`benchmark-numbers` artifact (which the website deploy
456456
bakes in, so each release's site shows its own
457457
freshly-measured figures) and pushes the numbers plus the
458-
rendered benchmark page to the orphan `assets` branch the
459-
README links to. GitHub Actions cannot open a PR here, so
458+
rendered benchmark page (assets/benchmarks/pages/benchmark.md)
459+
to the orphan `assets` branch the performance page links to.
460+
GitHub Actions cannot open a PR here, so
460461
it never touches the committed snapshot. The separate
461462
`bench-regression-gate` job runs
462463
`mdsmith-release bench-check`, which compares mdsmith's

internal/release/benchpage.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package release
2+
3+
import (
4+
"fmt"
5+
"path"
6+
"path/filepath"
7+
"regexp"
8+
"strings"
9+
)
10+
11+
// benchReadmeRel is the repo-relative path of the benchmark research
12+
// README whose rendered, link-rewritten copy release.yml publishes to
13+
// the orphan assets branch (assets/benchmarks/pages/benchmark.md). It
14+
// is the prose write-up the performance page links to: a copy carrying
15+
// this release's freshly measured numbers, where main's committed
16+
// snapshot lags between deliberate run.sh refreshes.
17+
const benchReadmeRel = benchDirRel + "/README.md"
18+
19+
// linkScheme matches a leading URI scheme (https:, mailto:, …) so an
20+
// already-absolute link target is left untouched by the rewrite.
21+
var linkScheme = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9+.\-]*:`)
22+
23+
// benchInlineLink matches an inline Markdown link's `](target)` tail,
24+
// capturing the target (group 1, up to the first whitespace or `)`)
25+
// and an optional double-quoted title (group 2). Image embeds share
26+
// the same tail and are rewritten identically — both are real link
27+
// targets once the page is lifted off the repo tree.
28+
var benchInlineLink = regexp.MustCompile(`\]\(([^)\s]+)((?:\s+"[^"]*")?)\)`)
29+
30+
// benchRefDef matches a reference-style link definition line,
31+
// capturing the `[label]: ` prefix (group 1), the target (group 2),
32+
// and an optional title (group 3). Multiline so `^`/`$` anchor at each
33+
// line within the non-code segments applyOutsideCode hands it.
34+
var benchRefDef = regexp.MustCompile(
35+
`(?m)^(\[[^\]]+\]:[ \t]+)(\S+)((?:[ \t]+"[^"]*")?)[ \t]*$`)
36+
37+
// rewriteRelativeLinksToGitHub rewrites every repo-relative Markdown
38+
// link in data — resolved against srcDirRel, a repo-root-relative
39+
// directory — to an absolute GitHub URL on main, so a page lifted out
40+
// of the repo tree has no link that 404s. The benchmark README is the
41+
// caller: published to the orphan assets branch, none of its sibling
42+
// files (run.sh, the coverage matrix, the rule READMEs it cites) exist
43+
// there, so each relative link must point back at github.com/main.
44+
//
45+
// Targets that already resolve as-is are left untouched: anchor-only
46+
// (`#sec`), site-absolute (`/x`), and scheme-qualified (`https://…`,
47+
// `mailto:…`) links, plus anything inside a fenced block or inline
48+
// code span — those are documentation examples, not real targets, and
49+
// applyOutsideCode keeps the rewrite away from them.
50+
func rewriteRelativeLinksToGitHub(data []byte, srcDirRel string) []byte {
51+
return applyOutsideCode(data, func(seg []byte) []byte {
52+
seg = benchInlineLink.ReplaceAllFunc(seg, func(m []byte) []byte {
53+
sub := benchInlineLink.FindSubmatch(m)
54+
url, ok := githubURLForRelativeTarget(string(sub[1]), srcDirRel)
55+
if !ok {
56+
return m
57+
}
58+
return []byte("](" + url + string(sub[2]) + ")")
59+
})
60+
return benchRefDef.ReplaceAllFunc(seg, func(m []byte) []byte {
61+
sub := benchRefDef.FindSubmatch(m)
62+
url, ok := githubURLForRelativeTarget(string(sub[2]), srcDirRel)
63+
if !ok {
64+
return m
65+
}
66+
return []byte(string(sub[1]) + url + string(sub[3]))
67+
})
68+
})
69+
}
70+
71+
// githubURLForRelativeTarget resolves a single Markdown link target
72+
// against srcDirRel and returns its absolute GitHub URL on main plus
73+
// true, or ("", false) when the target must be left as-is (empty,
74+
// anchor-only, site-absolute, or already scheme-qualified). A trailing
75+
// `#fragment` is preserved, and a trailing slash routes to /tree/
76+
// (GitHub's directory view) rather than /blob/ via githubURLForPath.
77+
func githubURLForRelativeTarget(target, srcDirRel string) (string, bool) {
78+
if target == "" || target[0] == '#' || target[0] == '/' ||
79+
linkScheme.MatchString(target) {
80+
return "", false
81+
}
82+
rel, frag := target, ""
83+
if i := strings.IndexByte(target, '#'); i >= 0 {
84+
rel, frag = target[:i], target[i:]
85+
}
86+
// rel is always non-empty here: the guard above rejects a
87+
// leading '#', so any '#' found sits at index >= 1.
88+
resolved := path.Join(srcDirRel, rel)
89+
if strings.HasSuffix(rel, "/") {
90+
resolved += "/"
91+
}
92+
return githubURLForPath([]byte(resolved)) + frag, true
93+
}
94+
95+
// RenderBenchPage reads the benchmark README (already `mdsmith fix`ed
96+
// upstream so its <?include?> tables carry this release's freshly
97+
// measured numbers), rewrites every repo-relative link to an absolute
98+
// GitHub URL on main, and writes the result to outPath. release.yml's
99+
// benchmark-publish job publishes that file to
100+
// assets/benchmarks/pages/benchmark.md so the performance page links a
101+
// rendered, fresh-numbers copy whose inner links all resolve.
102+
func (t *Toolkit) RenderBenchPage(root, outPath string) error {
103+
src := filepath.Join(root, filepath.FromSlash(benchReadmeRel))
104+
data, err := t.fs.ReadFile(src)
105+
if err != nil {
106+
return fmt.Errorf("read benchmark README %s: %w", src, err)
107+
}
108+
page := rewriteRelativeLinksToGitHub(data, benchDirRel)
109+
dir := filepath.Dir(outPath)
110+
if err := t.fs.MkdirAll(dir, 0o755); err != nil {
111+
return fmt.Errorf("mkdir %s: %w", dir, err)
112+
}
113+
if err := t.fs.WriteFile(outPath, page, 0o644); err != nil {
114+
return fmt.Errorf("write benchmark page %s: %w", outPath, err)
115+
}
116+
return nil
117+
}
118+
119+
// RenderBenchPage delegates to a default-OS Toolkit (see Stamp).
120+
func RenderBenchPage(root, outPath string) error {
121+
return New().RenderBenchPage(root, outPath)
122+
}

0 commit comments

Comments
 (0)