feat: one cold build, three warm and one-line-change builds

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 9cddb7f20dffca0ae2b02613a3883480f4e6053d parent 89261e7
4 files changed, +24 −14
  • harness/buildstats.go +13 −7
  • harness/cmd/bakeoff/main.go +5 −4
  • harness/harness_test.go +4 −1
  • scripts/measure-build.sh +2 −2
diff --git a/harness/buildstats.go b/harness/buildstats.go
index 96e3735..7f307ba 100644
--- a/harness/buildstats.go
+++ b/harness/buildstats.go
@@ -15,6 +15,7 @@ type Build struct {
ColdSeconds, WarmSeconds, EditSeconds float64
Bytes int64
Host string
+ ColdRuns, Runs int // how many cold builds, and warm and edit builds, each median is of
}
func mk(dir string, targets ...string) error {
@@ -37,15 +38,17 @@ func median(v []float64) float64 {
return v[len(v)/2]
}
-// BuildStats measures cold (clean-deps; COLD=1 for Go, which passes -a),
-// warm (clean) and one-line-change (bump) builds, runs times each.
-func BuildStats(dir string, goCold bool, runs int) (Build, error) {
+// BuildStats measures cold (clean-deps; COLD=1 for Go, which passes -a)
+// builds coldRuns times, and warm (clean) and one-line-change (bump)
+// builds runs times each. Cold builds under emulation take up to an hour,
+// hence the separate count.
+func BuildStats(dir string, goCold bool, coldRuns, runs int) (Build, error) {
coldTarget := []string{"build-arm64"}
if goCold {
coldTarget = append(coldTarget, "COLD=1")
}
var cold, warm, edit []float64
- for i := 0; i < runs; i++ {
+ for i := 0; i < coldRuns; i++ {
if err := mk(dir, "clean", "clean-deps"); err != nil {
return Build{}, err
}
@@ -54,11 +57,13 @@ func BuildStats(dir string, goCold bool, runs int) (Build, error) {
return Build{}, err
}
cold = append(cold, s)
-
+ }
+ for i := 0; i < runs; i++ {
if err := mk(dir, "clean"); err != nil {
return Build{}, err
}
- if s, err = timed(dir, "build-arm64"); err != nil {
+ s, err := timed(dir, "build-arm64")
+ if err != nil {
return Build{}, err
}
warm = append(warm, s)
@@ -83,5 +88,6 @@ func BuildStats(dir string, goCold bool, runs int) (Build, error) {
return Build{}, err
}
host, _ := os.Hostname()
- return Build{ColdSeconds: median(cold), WarmSeconds: median(warm), EditSeconds: median(edit), Bytes: fi.Size(), Host: host}, nil
+ return Build{ColdSeconds: median(cold), WarmSeconds: median(warm), EditSeconds: median(edit), Bytes: fi.Size(), Host: host,
+ ColdRuns: coldRuns, Runs: runs}, nil
}
diff --git a/harness/cmd/bakeoff/main.go b/harness/cmd/bakeoff/main.go
index 6661615..e26a3fe 100644
--- a/harness/cmd/bakeoff/main.go
+++ b/harness/cmd/bakeoff/main.go
@@ -31,7 +31,8 @@ func main() {
url := fs.String("url", "", "app base URL (default https://todo-<slug>.bfj.oncarlos.com)")
results := fs.String("results", "results", "results directory")
dir := fs.String("dir", "", "app directory (default apps/<slug>)")
- runs := fs.Int("runs", 3, "build: runs per mode")
+ runs := fs.Int("runs", 3, "build: warm and one-line-change runs")
+ coldRuns := fs.Int("cold-runs", 1, "build: cold runs")
dur := fs.Duration("duration", 10*time.Second, "load: length of each step")
rest := fs.Duration("rest", 10*time.Second, "load: pause between steps")
levels := fs.String("levels", "1,2,4,8,16,32,64,128,256,512,1024", "load: concurrency levels, doubling")
@@ -72,7 +73,7 @@ func main() {
case "code":
err = code(*slug, *dir)
case "build":
- err = build(*slug, *dir, *runs)
+ err = build(*slug, *dir, *coldRuns, *runs)
case "tokens":
err = tokens(*slug, *logs, *phase, *since)
case "behaviour":
@@ -229,8 +230,8 @@ func code(slug, dir string) error {
return b.Update(slug, func(r *b.AppResult) { r.Code = c })
}
-func build(slug, dir string, runs int) error {
- st, err := b.BuildStats(dir, strings.HasPrefix(slug, "go"), runs)
+func build(slug, dir string, coldRuns, runs int) error {
+ st, err := b.BuildStats(dir, strings.HasPrefix(slug, "go"), coldRuns, runs)
if err != nil {
return err
}
diff --git a/harness/harness_test.go b/harness/harness_test.go
index 3d93025..1c31c61 100644
--- a/harness/harness_test.go
+++ b/harness/harness_test.go
@@ -145,7 +145,7 @@ func TestBuildStats(t *testing.T) {
dir := t.TempDir()
mk, _ := os.ReadFile("testdata/buildtree/Makefile")
os.WriteFile(filepath.Join(dir, "Makefile"), mk, 0o644)
- b, err := BuildStats(dir, false, 1)
+ b, err := BuildStats(dir, false, 1, 2)
if err != nil {
t.Fatal(err)
}
@@ -158,6 +158,9 @@ func TestBuildStats(t *testing.T) {
if _, err := os.Stat(filepath.Join(dir, ".unbumped")); err != nil {
t.Fatal("unbump was not run")
}
+ if b.ColdRuns != 1 || b.Runs != 2 {
+ t.Fatalf("want 1 cold run and 2 warm/edit runs recorded, got %+v", b)
+ }
}
func TestUpdateRoundTrip(t *testing.T) {
diff --git a/scripts/measure-build.sh b/scripts/measure-build.sh
index ad8b6ca..883a0e3 100755
--- a/scripts/measure-build.sh
+++ b/scripts/measure-build.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Build measurements for one app, in its worktree: cold, warm and
-# one-line-change builds of the arm64 binary (median of 3), its size, and
+# one-line-change builds of the arm64 binary (cold once; warm and edit median of 3), its size, and
# the code counts. Run apps one at a time, with nothing else heavy running.
# scripts/measure-build.sh <slug> [runs]
set -euo pipefail
@@ -9,4 +9,4 @@ cd "$(dirname "$0")/.."
source scripts/env.sh
dir=.worktrees/$slug/apps/$slug
bakeoff code --slug "$slug" --dir "$dir"
-bakeoff build --slug "$slug" --dir "$dir" --runs "$runs"
+bakeoff build --slug "$slug" --dir "$dir" --cold-runs 1 --runs "$runs"