carlos / todomvc-bakeoff Public
fix: decide sleep from the platform's own events; recollect wake samples
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 files changed,
+78
−55
harness/cmd/bakeoff/main.go+5 −0harness/harness_test.go+16 −0harness/wake.go+23 −3results/go-rastrillo.json+4 −6results/go.json+1 −3results/haskell.json+1 −3results/nim.json+1 −3results/ocaml.json+1 −3results/rust.json+8 −10results/spinel.json+1 −3results/swift.json+5 −7results/zig.json+12 −14
diff --git a/harness/cmd/bakeoff/main.go b/harness/cmd/bakeoff/main.go| index 1e6ad00..354cd70 100644 |
| --- a/harness/cmd/bakeoff/main.go |
| +++ b/harness/cmd/bakeoff/main.go |
| @@ -49,6 +49,7 @@ func main() { |
| fs.StringVar(&rec.Note, "note", "", "record: a note shown in the app's section (added once)") |
| recURL := fs.String("app-url", "", "record: live URL") |
| clearNotes := fs.Bool("clear-notes", false, "record: drop existing notes first") |
| + clearWake := fs.Bool("clear", false, "wake: drop recorded samples") |
| replies := fs.String("replies", "", "readability: comma-separated files, one scorer reply each") |
| fs.Parse(os.Args[2:]) |
| if *slug == "" && cmd != "report" { |
| @@ -66,6 +67,10 @@ func main() { |
| case "ttfb": |
| err = ttfb(*slug, *url) |
| case "wake": |
| + if *clearWake { |
| + err = b.Update(*slug, func(r *b.AppResult) { r.Wake = nil }) |
| + break |
| + } |
| err = wake(*slug, *url) |
| case "load": |
| err = load(*slug, *url, *levels, *dur, *rest) |
diff --git a/harness/harness_test.go b/harness/harness_test.go| index 5ee90d4..08c74d2 100644 |
| --- a/harness/harness_test.go |
| +++ b/harness/harness_test.go |
| @@ -344,3 +344,19 @@ func TestApplyRecordAppendsNoteOnce(t *testing.T) { |
| t.Fatalf("got %q", r.Notes) |
| } |
| } |
| + |
| +func TestAsleepFromLogs(t *testing.T) { |
| + logs := `2026-10-24 09:50:52 [platform] todo-zig-2.bfj.oncarlos.com instance stopping |
| +2026-10-24 09:50:53 [platform] todo-zig-2.bfj.oncarlos.com instance exited (exit 0) |
| +2026-10-24 10:01:00 [platform] todo-zig.bfj.oncarlos.com instance woke in 1.1s (spawn 1s) (version x)` |
| + if !AsleepFromLogs(logs, "todo-zig-2.bfj.oncarlos.com") { |
| + t.Fatal("last event for the host is an exit: asleep") |
| + } |
| + logs += "\n2026-10-24 10:36:41 [platform] todo-zig-2.bfj.oncarlos.com instance woke in 1.156s (spawn 1.037s) (version 73268e0)" |
| + if AsleepFromLogs(logs, "todo-zig-2.bfj.oncarlos.com") { |
| + t.Fatal("last event is a wake: awake") |
| + } |
| + if AsleepFromLogs("", "todo-zig-2.bfj.oncarlos.com") { |
| + t.Fatal("no events: unknown, so not asleep") |
| + } |
| +} |
diff --git a/harness/wake.go b/harness/wake.go| index 5727373..72f70db 100644 |
| --- a/harness/wake.go |
| +++ b/harness/wake.go |
| @@ -5,13 +5,33 @@ import ( |
| "strings" |
| ) |
| -// IsAsleep asks the platform whether the instance at host is hibernating. |
| +// AsleepFromLogs reads the platform's own events for host in `carlos logs` |
| +// output: the instance is asleep when its latest event is "stopping" or |
| +// "exited", and awake after "woke". (The health column in `carlos |
| +// instances list` lags by minutes, so it can't be trusted for this.) |
| +func AsleepFromLogs(logs, host string) bool { |
| + asleep := false |
| + for _, line := range strings.Split(logs, "\n") { |
| + if !strings.Contains(line, "[platform] "+host+" ") { |
| + continue |
| + } |
| + switch { |
| + case strings.Contains(line, "instance woke"): |
| + asleep = false |
| + case strings.Contains(line, "instance stopping"), strings.Contains(line, "instance exited"): |
| + asleep = true |
| + } |
| + } |
| + return asleep |
| +} |
| + |
| +// IsAsleep asks the platform's log whether the instance at host is asleep. |
| func IsAsleep(app, host string) (bool, error) { |
| - out, err := exec.Command("carlos", "instances", "list", "--account", "bfj", "--app", app, "--health", "asleep").CombinedOutput() |
| + out, err := exec.Command("carlos", "logs", "--account", "bfj", "--app", app).CombinedOutput() |
| if err != nil { |
| return false, err |
| } |
| - return strings.Contains(string(out), host), nil |
| + return AsleepFromLogs(string(out), host), nil |
| } |
| // MeasureWake times the first request to a sleeping instance. |
diff --git a/results/go-rastrillo.json b/results/go-rastrillo.json| index fe2baa3..ddb4176 100644 |
| --- a/results/go-rastrillo.json |
| +++ b/results/go-rastrillo.json |
| @@ -32,9 +32,7 @@ |
| "Min": 48.03, |
| "Max": 64.063 |
| }, |
| - "Wake": [ |
| - 1435.117 |
| - ], |
| + "Wake": null, |
| "Load": [ |
| { |
| "Scenario": "read", |
| @@ -301,9 +299,9 @@ |
| "Deps": 3 |
| }, |
| "Build": { |
| - "ColdSeconds": 11.408177673, |
| - "WarmSeconds": 0.28805728, |
| - "EditSeconds": 0.285644601, |
| + "ColdSeconds": 10.390341005, |
| + "WarmSeconds": 0.272297767, |
| + "EditSeconds": 0.139181192, |
| "Bytes": 14090402, |
| "Host": "DOCS-T1", |
| "ColdRuns": 1, |
diff --git a/results/go.json b/results/go.json| index d1b0c14..496e736 100644 |
| --- a/results/go.json |
| +++ b/results/go.json |
| @@ -32,9 +32,7 @@ |
| "Min": 86.404, |
| "Max": 108.968 |
| }, |
| - "Wake": [ |
| - 84.556 |
| - ], |
| + "Wake": null, |
| "Load": [ |
| { |
| "Scenario": "read", |
diff --git a/results/haskell.json b/results/haskell.json| index 739efe7..9e6b385 100644 |
| --- a/results/haskell.json |
| +++ b/results/haskell.json |
| @@ -32,9 +32,7 @@ |
| "Min": 89.266, |
| "Max": 168.219 |
| }, |
| - "Wake": [ |
| - 1373.548 |
| - ], |
| + "Wake": null, |
| "Load": [ |
| { |
| "Scenario": "read", |
diff --git a/results/nim.json b/results/nim.json| index 3a1c0d0..eb3c94e 100644 |
| --- a/results/nim.json |
| +++ b/results/nim.json |
| @@ -32,9 +32,7 @@ |
| "Min": 42.03, |
| "Max": 55.361 |
| }, |
| - "Wake": [ |
| - 1244.605 |
| - ], |
| + "Wake": null, |
| "Load": [ |
| { |
| "Scenario": "read", |
diff --git a/results/ocaml.json b/results/ocaml.json| index dd5915d..ab1d39e 100644 |
| --- a/results/ocaml.json |
| +++ b/results/ocaml.json |
| @@ -32,9 +32,7 @@ |
| "Min": 41.961, |
| "Max": 125.31 |
| }, |
| - "Wake": [ |
| - 1352.486 |
| - ], |
| + "Wake": null, |
| "Load": [ |
| { |
| "Scenario": "read", |
diff --git a/results/rust.json b/results/rust.json| index de783ef..e5f8ce7 100644 |
| --- a/results/rust.json |
| +++ b/results/rust.json |
| @@ -32,9 +32,7 @@ |
| "Min": 79.697, |
| "Max": 102.563 |
| }, |
| - "Wake": [ |
| - 1156.261 |
| - ], |
| + "Wake": null, |
| "Load": [ |
| { |
| "Scenario": "read", |
| @@ -363,13 +361,13 @@ |
| "Deps": 7 |
| }, |
| "Build": { |
| - "ColdSeconds": 0, |
| - "WarmSeconds": 0, |
| - "EditSeconds": 0, |
| - "Bytes": 0, |
| - "Host": "", |
| - "ColdRuns": 0, |
| - "Runs": 0 |
| + "ColdSeconds": 49.623494074, |
| + "WarmSeconds": 8.477199217, |
| + "EditSeconds": 5.752862848, |
| + "Bytes": 3155304, |
| + "Host": "DOCS-T1", |
| + "ColdRuns": 1, |
| + "Runs": 3 |
| }, |
| "TokensBuild": { |
| "Input": 94, |
diff --git a/results/spinel.json b/results/spinel.json| index b507d7b..bc9ad00 100644 |
| --- a/results/spinel.json |
| +++ b/results/spinel.json |
| @@ -32,9 +32,7 @@ |
| "Min": 82.851, |
| "Max": 108.763 |
| }, |
| - "Wake": [ |
| - 1300.982 |
| - ], |
| + "Wake": null, |
| "Load": [ |
| { |
| "Scenario": "read", |
diff --git a/results/swift.json b/results/swift.json| index d64613b..12063c7 100644 |
| --- a/results/swift.json |
| +++ b/results/swift.json |
| @@ -32,9 +32,7 @@ |
| "Min": 47.876, |
| "Max": 63.425 |
| }, |
| - "Wake": [ |
| - 1363.467 |
| - ], |
| + "Wake": null, |
| "Load": [ |
| { |
| "Scenario": "read", |
| @@ -367,11 +365,11 @@ |
| "JSBytes": 3110 |
| }, |
| "Code": { |
| - "Server": 0, |
| + "Server": 1251, |
| "Templates": 0, |
| - "JS": 0, |
| - "Tests": 0, |
| - "Deps": 0 |
| + "JS": 75, |
| + "Tests": 309, |
| + "Deps": 3 |
| }, |
| "Build": { |
| "ColdSeconds": 0, |
diff --git a/results/zig.json b/results/zig.json| index 7cc3819..34fdc4a 100644 |
| --- a/results/zig.json |
| +++ b/results/zig.json |
| @@ -32,9 +32,7 @@ |
| "Min": 48.154, |
| "Max": 65.122 |
| }, |
| - "Wake": [ |
| - 1239.057 |
| - ], |
| + "Wake": null, |
| "Load": [ |
| { |
| "Scenario": "read", |
| @@ -342,20 +340,20 @@ |
| "JSBytes": 3854 |
| }, |
| "Code": { |
| - "Server": 0, |
| + "Server": 964, |
| "Templates": 0, |
| - "JS": 0, |
| - "Tests": 0, |
| - "Deps": 0 |
| + "JS": 94, |
| + "Tests": 59, |
| + "Deps": 1 |
| }, |
| "Build": { |
| - "ColdSeconds": 0, |
| - "WarmSeconds": 0, |
| - "EditSeconds": 0, |
| - "Bytes": 0, |
| - "Host": "", |
| - "ColdRuns": 0, |
| - "Runs": 0 |
| + "ColdSeconds": 151.72579616, |
| + "WarmSeconds": 5.378776474, |
| + "EditSeconds": 5.416322927, |
| + "Bytes": 1918040, |
| + "Host": "DOCS-T1", |
| + "ColdRuns": 1, |
| + "Runs": 3 |
| }, |
| "TokensBuild": { |
| "Input": 172, |