detached a57483f

fix(perf): validate proxy measurements

commit
a57483f66d5e9cbd5323650782d1880912ad20aa
author
brice · · 2026-08-01 05:30 UTC
parent
9a66c88
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
9 files · +262 -26
M cmd/vev-perf-harness/evidence_test.go +10 -1
@@ -254,7 +254,13 @@ func TestHarnessWritesRequiredEvidenceWithSufficientSpans(t *testing.T) {
254254 h := defaultHarness()
255255 h.clock = &fakeClock{}
256256 h.launcher = l
257- h.gitSHA = func() string { return "test-sha" }
257+ h.gitSHA = func() string { return "harness-sha" }
258+ h.vevGitSHA = func(path string) string {
259+ if !filepath.IsAbs(path) {
260+ t.Fatalf("measured binary path is not resolved: %q", path)
261+ }
262+ return "measured-vev-sha"
263+ }
258264 if err := run([]string{"--vev-bin", "ignored", "--manifest", manifestPath, "--out", filepath.Join(dir, "out"), "--warmup", "1s", "--duration", "30s", "--repetitions", "10"}, h); err != nil {
259265 t.Fatal(err)
260266 }
@@ -271,6 +277,9 @@ func TestHarnessWritesRequiredEvidenceWithSufficientSpans(t *testing.T) {
271277 if err := json.Unmarshal(b, &got); err != nil {
272278 t.Fatal(err)
273279 }
280+ if got.GitSHA != "harness-sha" || got.VevGitSHA != "measured-vev-sha" {
281+ t.Fatalf("summary provenance = harness %q measured vev %q", got.GitSHA, got.VevGitSHA)
282+ }
274283 if got.EndToEnd.Count < 10*minimumInIntervalEventSamples || got.Repetitions != 10 || got.RunP50Dispersion.Count != 10 || got.Cadence.Count == 0 || got.MaxGap == 0 || len(got.Spans) == 0 {
275284 t.Fatalf("summary=%+v", got)
276285 }
@@ -254,7 +254,13 @@ func TestHarnessWritesRequiredEvidenceWithSufficientSpans(t *testing.T) {
254 h := defaultHarness()254 h := defaultHarness()
255 h.clock = &fakeClock{}255 h.clock = &fakeClock{}
256 h.launcher = l256 h.launcher = l
257- h.gitSHA = func() string { return "test-sha" }257+ h.gitSHA = func() string { return "harness-sha" }
258+ h.vevGitSHA = func(path string) string {
259+ if !filepath.IsAbs(path) {
260+ t.Fatalf("measured binary path is not resolved: %q", path)
261+ }
262+ return "measured-vev-sha"
263+ }
258 if err := run([]string{"--vev-bin", "ignored", "--manifest", manifestPath, "--out", filepath.Join(dir, "out"), "--warmup", "1s", "--duration", "30s", "--repetitions", "10"}, h); err != nil {264 if err := run([]string{"--vev-bin", "ignored", "--manifest", manifestPath, "--out", filepath.Join(dir, "out"), "--warmup", "1s", "--duration", "30s", "--repetitions", "10"}, h); err != nil {
259 t.Fatal(err)265 t.Fatal(err)
260 }266 }
@@ -271,6 +277,9 @@ func TestHarnessWritesRequiredEvidenceWithSufficientSpans(t *testing.T) {
271 if err := json.Unmarshal(b, &got); err != nil {277 if err := json.Unmarshal(b, &got); err != nil {
272 t.Fatal(err)278 t.Fatal(err)
273 }279 }
280+ if got.GitSHA != "harness-sha" || got.VevGitSHA != "measured-vev-sha" {
281+ t.Fatalf("summary provenance = harness %q measured vev %q", got.GitSHA, got.VevGitSHA)
282+ }
274 if got.EndToEnd.Count < 10*minimumInIntervalEventSamples || got.Repetitions != 10 || got.RunP50Dispersion.Count != 10 || got.Cadence.Count == 0 || got.MaxGap == 0 || len(got.Spans) == 0 {283 if got.EndToEnd.Count < 10*minimumInIntervalEventSamples || got.Repetitions != 10 || got.RunP50Dispersion.Count != 10 || got.Cadence.Count == 0 || got.MaxGap == 0 || len(got.Spans) == 0 {
275 t.Fatalf("summary=%+v", got)284 t.Fatalf("summary=%+v", got)
276 }285 }
M cmd/vev-perf-harness/launcher.go +5 -0
@@ -198,6 +198,11 @@ func (l *cliLauncher) Launch(m processMapping, role roleCommand) (launchedProces
198198 if err != nil {
199199 return nil, err
200200 }
201+ if err := setPTYWinsize(int(master.Fd()), 120, 40); err != nil {
202+ _ = master.Close()
203+ _ = slave.Close()
204+ return nil, fmt.Errorf("set canonical client PTY geometry: %w", err)
205+ }
201206 output, err := os.OpenFile(filepath.Join(filepath.Dir(m.TracePath), "terminal-output"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600)
202207 if err != nil {
203208 _ = master.Close()
@@ -198,6 +198,11 @@ func (l *cliLauncher) Launch(m processMapping, role roleCommand) (launchedProces
198 if err != nil {198 if err != nil {
199 return nil, err199 return nil, err
200 }200 }
201+ if err := setPTYWinsize(int(master.Fd()), 120, 40); err != nil {
202+ _ = master.Close()
203+ _ = slave.Close()
204+ return nil, fmt.Errorf("set canonical client PTY geometry: %w", err)
205+ }
201 output, err := os.OpenFile(filepath.Join(filepath.Dir(m.TracePath), "terminal-output"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600)206 output, err := os.OpenFile(filepath.Join(filepath.Dir(m.TracePath), "terminal-output"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600)
202 if err != nil {207 if err != nil {
203 _ = master.Close()208 _ = master.Close()
M cmd/vev-perf-harness/launcher_test.go +84 -0
@@ -5,6 +5,7 @@ package main
55 import (
66 "bytes"
77 "context"
8+ "errors"
89 "fmt"
910 "net"
1011 "os"
@@ -12,6 +13,7 @@ import (
1213 "path/filepath"
1314 "strings"
1415 "sync"
16+ "syscall"
1517 "testing"
1618 "time"
1719
@@ -31,6 +33,88 @@ type fakeFDPTY struct {
3133
3234 func (p *fakeFDPTY) Fd() uintptr { return p.fd }
3335
36+func TestCLILauncherSetsCanonicalClientGeometryBeforeStart(t *testing.T) {
37+ original := setPTYWinsize
38+ t.Cleanup(func() { setPTYWinsize = original })
39+
40+ for _, tt := range []struct {
41+ name string
42+ setSize func(started string, fd int, cols, rows uint16) error
43+ wantErr bool
44+ }{
45+ {
46+ name: "starts after canonical geometry",
47+ setSize: func(started string, _ int, cols, rows uint16) error {
48+ if _, err := os.Stat(started); !errors.Is(err, os.ErrNotExist) {
49+ return fmt.Errorf("client started before geometry was set: %v", err)
50+ }
51+ if cols != 120 || rows != 40 {
52+ return fmt.Errorf("geometry = %dx%d, want 120x40", cols, rows)
53+ }
54+ return nil
55+ },
56+ },
57+ {
58+ name: "winsize failure closes PTY before returning",
59+ setSize: func(started string, fd int, _, _ uint16) error {
60+ if _, err := os.Stat(started); !errors.Is(err, os.ErrNotExist) {
61+ return fmt.Errorf("client started after winsize failure: %v", err)
62+ }
63+ return errTestWinsize
64+ },
65+ wantErr: true,
66+ },
67+ } {
68+ t.Run(tt.name, func(t *testing.T) {
69+ started := filepath.Join(t.TempDir(), "started")
70+ t.Setenv("STARTED", started)
71+ bin := filepath.Join(t.TempDir(), "client")
72+ if err := os.WriteFile(bin, []byte("#!/bin/sh\ntouch \"$STARTED\"\n"), 0o700); err != nil {
73+ t.Fatal(err)
74+ }
75+ runDir := filepath.Join(t.TempDir(), "run")
76+ if err := safedir.EnsurePrivate(runDir); err != nil {
77+ t.Fatal(err)
78+ }
79+ setFD := -1
80+ setPTYWinsize = func(fd int, cols, rows uint16) error {
81+ setFD = fd
82+ return tt.setSize(started, fd, cols, rows)
83+ }
84+ launcher := &cliLauncher{bin: bin}
85+ t.Cleanup(func() {
86+ if err := launcher.releaseRuntime(runDir); err != nil {
87+ t.Error(err)
88+ }
89+ })
90+ p, err := launcher.Launch(processMapping{Role: "client", TracePath: filepath.Join(runDir, "client.jsonl")}, roleCommand{Args: []string{"test"}})
91+ if tt.wantErr {
92+ if !errors.Is(err, errTestWinsize) || p != nil {
93+ t.Fatalf("Launch() = (%T, %v), want winsize error", p, err)
94+ }
95+ if setFD < 0 {
96+ t.Fatal("winsize setter was not called")
97+ }
98+ if closeErr := syscall.Close(setFD); !errors.Is(closeErr, syscall.EBADF) {
99+ t.Fatalf("winsize failure left PTY fd %d open: close error %v", setFD, closeErr)
100+ }
101+ return
102+ }
103+ if err != nil {
104+ t.Fatal(err)
105+ }
106+ if setFD < 0 {
107+ t.Fatal("winsize setter was not called before successful launch")
108+ }
109+ if err := p.Close(); err != nil {
110+ t.Fatal(err)
111+ }
112+ })
113+ }
114+}
115+
116+var errTestWinsize = errors.New("test winsize failure")
117+
34118 func TestCLIProcessResize(t *testing.T) {
35119 original := setPTYWinsize
36120 t.Cleanup(func() { setPTYWinsize = original })
@@ -5,6 +5,7 @@ package main
5 import (5 import (
6 "bytes"6 "bytes"
7 "context"7 "context"
8+ "errors"
8 "fmt"9 "fmt"
9 "net"10 "net"
10 "os"11 "os"
@@ -12,6 +13,7 @@ import (
12 "path/filepath"13 "path/filepath"
13 "strings"14 "strings"
14 "sync"15 "sync"
16+ "syscall"
15 "testing"17 "testing"
16 "time"18 "time"
17 19
@@ -31,6 +33,88 @@ type fakeFDPTY struct {
31 33
32 func (p *fakeFDPTY) Fd() uintptr { return p.fd }34 func (p *fakeFDPTY) Fd() uintptr { return p.fd }
33 35
36+func TestCLILauncherSetsCanonicalClientGeometryBeforeStart(t *testing.T) {
37+ original := setPTYWinsize
38+ t.Cleanup(func() { setPTYWinsize = original })
39+
40+ for _, tt := range []struct {
41+ name string
42+ setSize func(started string, fd int, cols, rows uint16) error
43+ wantErr bool
44+ }{
45+ {
46+ name: "starts after canonical geometry",
47+ setSize: func(started string, _ int, cols, rows uint16) error {
48+ if _, err := os.Stat(started); !errors.Is(err, os.ErrNotExist) {
49+ return fmt.Errorf("client started before geometry was set: %v", err)
50+ }
51+ if cols != 120 || rows != 40 {
52+ return fmt.Errorf("geometry = %dx%d, want 120x40", cols, rows)
53+ }
54+ return nil
55+ },
56+ },
57+ {
58+ name: "winsize failure closes PTY before returning",
59+ setSize: func(started string, fd int, _, _ uint16) error {
60+ if _, err := os.Stat(started); !errors.Is(err, os.ErrNotExist) {
61+ return fmt.Errorf("client started after winsize failure: %v", err)
62+ }
63+ return errTestWinsize
64+ },
65+ wantErr: true,
66+ },
67+ } {
68+ t.Run(tt.name, func(t *testing.T) {
69+ started := filepath.Join(t.TempDir(), "started")
70+ t.Setenv("STARTED", started)
71+ bin := filepath.Join(t.TempDir(), "client")
72+ if err := os.WriteFile(bin, []byte("#!/bin/sh\ntouch \"$STARTED\"\n"), 0o700); err != nil {
73+ t.Fatal(err)
74+ }
75+ runDir := filepath.Join(t.TempDir(), "run")
76+ if err := safedir.EnsurePrivate(runDir); err != nil {
77+ t.Fatal(err)
78+ }
79+ setFD := -1
80+ setPTYWinsize = func(fd int, cols, rows uint16) error {
81+ setFD = fd
82+ return tt.setSize(started, fd, cols, rows)
83+ }
84+ launcher := &cliLauncher{bin: bin}
85+ t.Cleanup(func() {
86+ if err := launcher.releaseRuntime(runDir); err != nil {
87+ t.Error(err)
88+ }
89+ })
90+ p, err := launcher.Launch(processMapping{Role: "client", TracePath: filepath.Join(runDir, "client.jsonl")}, roleCommand{Args: []string{"test"}})
91+ if tt.wantErr {
92+ if !errors.Is(err, errTestWinsize) || p != nil {
93+ t.Fatalf("Launch() = (%T, %v), want winsize error", p, err)
94+ }
95+ if setFD < 0 {
96+ t.Fatal("winsize setter was not called")
97+ }
98+ if closeErr := syscall.Close(setFD); !errors.Is(closeErr, syscall.EBADF) {
99+ t.Fatalf("winsize failure left PTY fd %d open: close error %v", setFD, closeErr)
100+ }
101+ return
102+ }
103+ if err != nil {
104+ t.Fatal(err)
105+ }
106+ if setFD < 0 {
107+ t.Fatal("winsize setter was not called before successful launch")
108+ }
109+ if err := p.Close(); err != nil {
110+ t.Fatal(err)
111+ }
112+ })
113+ }
114+}
115+
116+var errTestWinsize = errors.New("test winsize failure")
117+
34 func TestCLIProcessResize(t *testing.T) {118 func TestCLIProcessResize(t *testing.T) {
35 original := setPTYWinsize119 original := setPTYWinsize
36 t.Cleanup(func() { setPTYWinsize = original })120 t.Cleanup(func() { setPTYWinsize = original })
M cmd/vev-perf-harness/main.go +1 -1
@@ -18,5 +18,5 @@ func main() {
1818 }
1919
2020 func defaultHarness() *harness {
21- return &harness{clock: systemClock{time.Now()}, mkdir: safedir.EnsurePrivate, createRunDir: createExclusiveRunDir, create: func(p string) (*os.File, error) { return os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600) }, removeAll: os.RemoveAll, gitSHA: recordedGitSHA}
21+ return &harness{clock: systemClock{time.Now()}, mkdir: safedir.EnsurePrivate, createRunDir: createExclusiveRunDir, create: func(p string) (*os.File, error) { return os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600) }, removeAll: os.RemoveAll, gitSHA: recordedGitSHA, vevGitSHA: recordedBinaryGitSHA}
2222 }
@@ -18,5 +18,5 @@ func main() {
18 }18 }
19 19
20 func defaultHarness() *harness {20 func defaultHarness() *harness {
21- return &harness{clock: systemClock{time.Now()}, mkdir: safedir.EnsurePrivate, createRunDir: createExclusiveRunDir, create: func(p string) (*os.File, error) { return os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600) }, removeAll: os.RemoveAll, gitSHA: recordedGitSHA}21+ return &harness{clock: systemClock{time.Now()}, mkdir: safedir.EnsurePrivate, createRunDir: createExclusiveRunDir, create: func(p string) (*os.File, error) { return os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600) }, removeAll: os.RemoveAll, gitSHA: recordedGitSHA, vevGitSHA: recordedBinaryGitSHA}
22 }22 }
M cmd/vev-perf-harness/model.go +1 -0
@@ -99,6 +99,7 @@ type runResult struct {
9999 type summary struct {
100100 Schema uint16 `json:"schema"`
101101 GitSHA string `json:"git_sha"`
102+ VevGitSHA string `json:"vev_git_sha"`
102103 Warmup string `json:"warmup"`
103104 Duration string `json:"duration"`
104105 Repetitions int `json:"repetitions"`
@@ -99,6 +99,7 @@ type runResult struct {
99 type summary struct {99 type summary struct {
100 Schema uint16 `json:"schema"`100 Schema uint16 `json:"schema"`
101 GitSHA string `json:"git_sha"`101 GitSHA string `json:"git_sha"`
102+ VevGitSHA string `json:"vev_git_sha"`
102 Warmup string `json:"warmup"`103 Warmup string `json:"warmup"`
103 Duration string `json:"duration"`104 Duration string `json:"duration"`
104 Repetitions int `json:"repetitions"`105 Repetitions int `json:"repetitions"`
M cmd/vev-perf-harness/runner.go +49 -9
@@ -4,6 +4,7 @@ package main
44
55 import (
66 "context"
7+ "debug/buildinfo"
78 "encoding/json"
89 "errors"
910 "fmt"
@@ -13,6 +14,7 @@ import (
1314 "path/filepath"
1415 "runtime/debug"
1516 "sort"
17+ "strconv"
1618 "strings"
1719 "time"
1820
@@ -51,6 +53,7 @@ type harness struct {
5153 create func(string) (*os.File, error)
5254 removeAll func(string) error
5355 gitSHA func() string
56+ vevGitSHA func(string) string
5457 selectScenarios func(manifest) []scenario // test-only bounded fixture seam
5558 }
5659
@@ -71,6 +74,19 @@ func createExclusiveRunDir(path string) error {
7174 // go run evidence attributable as well.
7275 const gitSHACommandTimeout = time.Second
7376
77+func recordedBinaryGitSHA(path string) string {
78+ info, err := buildinfo.ReadFile(path)
79+ if err != nil {
80+ return "unknown"
81+ }
82+ for _, setting := range info.Settings {
83+ if setting.Key == "vcs.revision" && setting.Value != "" {
84+ return setting.Value
85+ }
86+ }
87+ return "unknown"
88+}
89+
7490 func recordedGitSHA() string {
7591 if info, ok := debug.ReadBuildInfo(); ok {
7692 for _, setting := range info.Settings {
@@ -148,7 +164,7 @@ func run(args []string, h *harness) (err error) {
148164 if err := writeJSON(filepath.Join(opt.out, "runs.json"), results); err != nil {
149165 return err
150166 }
151- return writeJSON(filepath.Join(opt.out, "summary.json"), summary{Schema: 1, GitSHA: h.gitSHA(), Warmup: opt.warmup.String(), Duration: opt.duration.String(), Repetitions: opt.repetitions, EndToEnd: percentiles(all), Cadence: percentiles(cadence), MaxGap: maxGap, RunP50Dispersion: percentiles(runP50s), Spans: summarizeSpans(localSpans), Runs: len(results)})
167+ return writeJSON(filepath.Join(opt.out, "summary.json"), summary{Schema: 1, GitSHA: h.gitSHA(), VevGitSHA: h.vevGitSHA(opt.vevBin), Warmup: opt.warmup.String(), Duration: opt.duration.String(), Repetitions: opt.repetitions, EndToEnd: percentiles(all), Cadence: percentiles(cadence), MaxGap: maxGap, RunP50Dispersion: percentiles(runP50s), Spans: summarizeSpans(localSpans), Runs: len(results)})
152168 }
153169
154170 // aggregateEventResults deliberately preserves raw event latencies for
@@ -307,16 +323,22 @@ func (h *harness) runOne(o options, mat manifest, s scenario, run int, raw io.Wr
307323 flushedRecorded = true
308324 return nil
309325 }
326+ measureInjected := injected
310327 if cols, rows, resize := workloadResize(s, sequence); resize {
311328 resizer, ok := p.process.(processResizer)
312329 if !ok {
313330 return res, errors.New("client process does not support resize workload")
314331 }
332+ if err := injected(); err != nil {
333+ return res, err
334+ }
335+ measureInjected = func() error { return nil }
315336 if resizeErr := resizer.Resize(cols, rows); resizeErr != nil {
316- return res, fmt.Errorf("resize client PTY: %w", resizeErr)
337+ diagnosticErr := recordMark(harnessMark{Scenario: s.ID, Run: run, Sequence: sequence, Kind: "terminal_flushed", Tick: h.clock.Now(), Valid: false})
338+ return res, errors.Join(fmt.Errorf("resize client PTY: %w", resizeErr), diagnosticErr)
317339 }
318340 }
319- if measureErr := p.process.Measure(workloadInput(s, run, fmt.Sprintf("measured-%d", sequence)), injected, flushed); measureErr != nil {
341+ if measureErr := p.process.Measure(workloadInput(s, run, fmt.Sprintf("measured-%d", sequence)), measureInjected, flushed); measureErr != nil {
320342 if injectedRecorded && !flushedRecorded {
321343 diagnosticErr := recordMark(harnessMark{Scenario: s.ID, Run: run, Sequence: sequence, Kind: "terminal_flushed", Tick: h.clock.Now(), Valid: false})
322344 return res, errors.Join(measureErr, diagnosticErr)
@@ -448,9 +470,14 @@ func workloadResize(s scenario, sequence uint64) (uint16, uint16, bool) {
448470 func workloadInput(s scenario, run int, phase string) []byte {
449471 marker := fmt.Sprintf("__VEV_HARNESS_%s_r%d_%s__", safeName(s.ID), run, safeName(phase))
450472 body := "printf 'vev perf %s\\n'"
473+ markerSeparator := "; "
451474 switch s.Workload {
452475 case "active_output":
453- body = `printf '\033[10;20HX'; : '%s'`
476+ cell := byte('X')
477+ if sequence, err := strconv.ParseUint(strings.TrimPrefix(phase, "measured-"), 10, 64); err == nil && strings.HasPrefix(phase, "measured-") {
478+ cell = '0' + byte(sequence%2)
479+ }
480+ body = fmt.Sprintf(`printf '\033[10;20H%c'; : '%%s'`, cell)
454481 case "all_output":
455482 body = `printf '%%-120s' 'vev perf line %s'`
456483 case "inactive_output":
@@ -458,16 +485,29 @@ func workloadInput(s scenario, run int, phase string) []byte {
458485 case "interactive_flood":
459486 body = "i=0; while [ $i -lt 128 ]; do printf 'vev perf output %s\\n'; i=$((i+1)); done"
460487 case "resize_sweep":
461- body = "printf 'vev perf resize-sweep %s\\n'"
488+ cols, _ := resizeWorkloadGeometry(s, phase)
489+ body = fmt.Sprintf(`i=0; while [ "$i" -lt 100 ]; do set -- $(stty size); [ "$2" = "%d" ] && break; i=$((i+1)); sleep 0.01; done; set -- $(stty size); [ "$2" = "%d" ] && printf 'vev perf resize-sweep %%s\n'`, cols, cols)
490+ markerSeparator = " && "
462491 case "copy_search":
463492 body = "printf 'vev perf copy-search %s\\n'"
464493 case "snapshot_output_resize":
465- body = `printf '\033[2J\033[Hvev perf snapshot-output-resize %s\n'`
494+ cols, _ := resizeWorkloadGeometry(s, phase)
495+ body = fmt.Sprintf(`i=0; while [ "$i" -lt 100 ]; do set -- $(stty size); [ "$2" = "%d" ] && break; i=$((i+1)); sleep 0.01; done; set -- $(stty size); [ "$2" = "%d" ] && printf '\033[2J\033[Hvev perf snapshot-output-resize %%s\n'`, cols, cols)
496+ markerSeparator = " && "
466497 case "attach_restore_tab_switch":
467- body = `"$VEV_PERF_BIN" cmd next-tab; "$VEV_PERF_BIN" cmd previous-tab; printf 'vev perf attach-restore-tab-switch %s\n'`
498+ body = `"$VEV_PERF_BIN" cmd next-tab && "$VEV_PERF_BIN" cmd previous-tab && printf 'vev perf attach-restore-tab-switch %s\n'`
468499 if phase == "warmup" {
469- body = `"$VEV_PERF_BIN" cmd new-tab; "$VEV_PERF_BIN" cmd previous-tab; printf 'vev perf attach-restore-tab-switch %s\n'`
500+ body = `"$VEV_PERF_BIN" cmd new-tab && "$VEV_PERF_BIN" cmd previous-tab && printf 'vev perf attach-restore-tab-switch %s\n'`
470501 }
502+ markerSeparator = " && "
503+ }
504+ return []byte(fmt.Sprintf(body+markerSeparator+"printf '%s\\n'\n", s.ID, marker))
505+}
506+
507+func resizeWorkloadGeometry(s scenario, phase string) (uint16, uint16) {
508+ if sequence, err := strconv.ParseUint(strings.TrimPrefix(phase, "measured-"), 10, 64); err == nil && strings.HasPrefix(phase, "measured-") {
509+ cols, rows, _ := workloadResize(s, sequence)
510+ return cols, rows
471511 }
472- return []byte(fmt.Sprintf(body+"; printf '%s\\n'\n", s.ID, marker))
512+ return 120, 40
473513 }
@@ -4,6 +4,7 @@ package main
4 4
5 import (5 import (
6 "context"6 "context"
7+ "debug/buildinfo"
7 "encoding/json"8 "encoding/json"
8 "errors"9 "errors"
9 "fmt"10 "fmt"
@@ -13,6 +14,7 @@ import (
13 "path/filepath"14 "path/filepath"
14 "runtime/debug"15 "runtime/debug"
15 "sort"16 "sort"
17+ "strconv"
16 "strings"18 "strings"
17 "time"19 "time"
18 20
@@ -51,6 +53,7 @@ type harness struct {
51 create func(string) (*os.File, error)53 create func(string) (*os.File, error)
52 removeAll func(string) error54 removeAll func(string) error
53 gitSHA func() string55 gitSHA func() string
56+ vevGitSHA func(string) string
54 selectScenarios func(manifest) []scenario // test-only bounded fixture seam57 selectScenarios func(manifest) []scenario // test-only bounded fixture seam
55 }58 }
56 59
@@ -71,6 +74,19 @@ func createExclusiveRunDir(path string) error {
71 // go run evidence attributable as well.74 // go run evidence attributable as well.
72 const gitSHACommandTimeout = time.Second75 const gitSHACommandTimeout = time.Second
73 76
77+func recordedBinaryGitSHA(path string) string {
78+ info, err := buildinfo.ReadFile(path)
79+ if err != nil {
80+ return "unknown"
81+ }
82+ for _, setting := range info.Settings {
83+ if setting.Key == "vcs.revision" && setting.Value != "" {
84+ return setting.Value
85+ }
86+ }
87+ return "unknown"
88+}
89+
74 func recordedGitSHA() string {90 func recordedGitSHA() string {
75 if info, ok := debug.ReadBuildInfo(); ok {91 if info, ok := debug.ReadBuildInfo(); ok {
76 for _, setting := range info.Settings {92 for _, setting := range info.Settings {
@@ -148,7 +164,7 @@ func run(args []string, h *harness) (err error) {
148 if err := writeJSON(filepath.Join(opt.out, "runs.json"), results); err != nil {164 if err := writeJSON(filepath.Join(opt.out, "runs.json"), results); err != nil {
149 return err165 return err
150 }166 }
151- return writeJSON(filepath.Join(opt.out, "summary.json"), summary{Schema: 1, GitSHA: h.gitSHA(), Warmup: opt.warmup.String(), Duration: opt.duration.String(), Repetitions: opt.repetitions, EndToEnd: percentiles(all), Cadence: percentiles(cadence), MaxGap: maxGap, RunP50Dispersion: percentiles(runP50s), Spans: summarizeSpans(localSpans), Runs: len(results)})167+ return writeJSON(filepath.Join(opt.out, "summary.json"), summary{Schema: 1, GitSHA: h.gitSHA(), VevGitSHA: h.vevGitSHA(opt.vevBin), Warmup: opt.warmup.String(), Duration: opt.duration.String(), Repetitions: opt.repetitions, EndToEnd: percentiles(all), Cadence: percentiles(cadence), MaxGap: maxGap, RunP50Dispersion: percentiles(runP50s), Spans: summarizeSpans(localSpans), Runs: len(results)})
152 }168 }
153 169
154 // aggregateEventResults deliberately preserves raw event latencies for170 // aggregateEventResults deliberately preserves raw event latencies for
@@ -307,16 +323,22 @@ func (h *harness) runOne(o options, mat manifest, s scenario, run int, raw io.Wr
307 flushedRecorded = true323 flushedRecorded = true
308 return nil324 return nil
309 }325 }
326+ measureInjected := injected
310 if cols, rows, resize := workloadResize(s, sequence); resize {327 if cols, rows, resize := workloadResize(s, sequence); resize {
311 resizer, ok := p.process.(processResizer)328 resizer, ok := p.process.(processResizer)
312 if !ok {329 if !ok {
313 return res, errors.New("client process does not support resize workload")330 return res, errors.New("client process does not support resize workload")
314 }331 }
332+ if err := injected(); err != nil {
333+ return res, err
334+ }
335+ measureInjected = func() error { return nil }
315 if resizeErr := resizer.Resize(cols, rows); resizeErr != nil {336 if resizeErr := resizer.Resize(cols, rows); resizeErr != nil {
316- return res, fmt.Errorf("resize client PTY: %w", resizeErr)337+ diagnosticErr := recordMark(harnessMark{Scenario: s.ID, Run: run, Sequence: sequence, Kind: "terminal_flushed", Tick: h.clock.Now(), Valid: false})
338+ return res, errors.Join(fmt.Errorf("resize client PTY: %w", resizeErr), diagnosticErr)
317 }339 }
318 }340 }
319- if measureErr := p.process.Measure(workloadInput(s, run, fmt.Sprintf("measured-%d", sequence)), injected, flushed); measureErr != nil {341+ if measureErr := p.process.Measure(workloadInput(s, run, fmt.Sprintf("measured-%d", sequence)), measureInjected, flushed); measureErr != nil {
320 if injectedRecorded && !flushedRecorded {342 if injectedRecorded && !flushedRecorded {
321 diagnosticErr := recordMark(harnessMark{Scenario: s.ID, Run: run, Sequence: sequence, Kind: "terminal_flushed", Tick: h.clock.Now(), Valid: false})343 diagnosticErr := recordMark(harnessMark{Scenario: s.ID, Run: run, Sequence: sequence, Kind: "terminal_flushed", Tick: h.clock.Now(), Valid: false})
322 return res, errors.Join(measureErr, diagnosticErr)344 return res, errors.Join(measureErr, diagnosticErr)
@@ -448,9 +470,14 @@ func workloadResize(s scenario, sequence uint64) (uint16, uint16, bool) {
448 func workloadInput(s scenario, run int, phase string) []byte {470 func workloadInput(s scenario, run int, phase string) []byte {
449 marker := fmt.Sprintf("__VEV_HARNESS_%s_r%d_%s__", safeName(s.ID), run, safeName(phase))471 marker := fmt.Sprintf("__VEV_HARNESS_%s_r%d_%s__", safeName(s.ID), run, safeName(phase))
450 body := "printf 'vev perf %s\\n'"472 body := "printf 'vev perf %s\\n'"
473+ markerSeparator := "; "
451 switch s.Workload {474 switch s.Workload {
452 case "active_output":475 case "active_output":
453- body = `printf '\033[10;20HX'; : '%s'`476+ cell := byte('X')
477+ if sequence, err := strconv.ParseUint(strings.TrimPrefix(phase, "measured-"), 10, 64); err == nil && strings.HasPrefix(phase, "measured-") {
478+ cell = '0' + byte(sequence%2)
479+ }
480+ body = fmt.Sprintf(`printf '\033[10;20H%c'; : '%%s'`, cell)
454 case "all_output":481 case "all_output":
455 body = `printf '%%-120s' 'vev perf line %s'`482 body = `printf '%%-120s' 'vev perf line %s'`
456 case "inactive_output":483 case "inactive_output":
@@ -458,16 +485,29 @@ func workloadInput(s scenario, run int, phase string) []byte {
458 case "interactive_flood":485 case "interactive_flood":
459 body = "i=0; while [ $i -lt 128 ]; do printf 'vev perf output %s\\n'; i=$((i+1)); done"486 body = "i=0; while [ $i -lt 128 ]; do printf 'vev perf output %s\\n'; i=$((i+1)); done"
460 case "resize_sweep":487 case "resize_sweep":
461- body = "printf 'vev perf resize-sweep %s\\n'"488+ cols, _ := resizeWorkloadGeometry(s, phase)
489+ body = fmt.Sprintf(`i=0; while [ "$i" -lt 100 ]; do set -- $(stty size); [ "$2" = "%d" ] && break; i=$((i+1)); sleep 0.01; done; set -- $(stty size); [ "$2" = "%d" ] && printf 'vev perf resize-sweep %%s\n'`, cols, cols)
490+ markerSeparator = " && "
462 case "copy_search":491 case "copy_search":
463 body = "printf 'vev perf copy-search %s\\n'"492 body = "printf 'vev perf copy-search %s\\n'"
464 case "snapshot_output_resize":493 case "snapshot_output_resize":
465- body = `printf '\033[2J\033[Hvev perf snapshot-output-resize %s\n'`494+ cols, _ := resizeWorkloadGeometry(s, phase)
495+ body = fmt.Sprintf(`i=0; while [ "$i" -lt 100 ]; do set -- $(stty size); [ "$2" = "%d" ] && break; i=$((i+1)); sleep 0.01; done; set -- $(stty size); [ "$2" = "%d" ] && printf '\033[2J\033[Hvev perf snapshot-output-resize %%s\n'`, cols, cols)
496+ markerSeparator = " && "
466 case "attach_restore_tab_switch":497 case "attach_restore_tab_switch":
467- body = `"$VEV_PERF_BIN" cmd next-tab; "$VEV_PERF_BIN" cmd previous-tab; printf 'vev perf attach-restore-tab-switch %s\n'`498+ body = `"$VEV_PERF_BIN" cmd next-tab && "$VEV_PERF_BIN" cmd previous-tab && printf 'vev perf attach-restore-tab-switch %s\n'`
468 if phase == "warmup" {499 if phase == "warmup" {
469- body = `"$VEV_PERF_BIN" cmd new-tab; "$VEV_PERF_BIN" cmd previous-tab; printf 'vev perf attach-restore-tab-switch %s\n'`500+ body = `"$VEV_PERF_BIN" cmd new-tab && "$VEV_PERF_BIN" cmd previous-tab && printf 'vev perf attach-restore-tab-switch %s\n'`
470 }501 }
502+ markerSeparator = " && "
503+ }
504+ return []byte(fmt.Sprintf(body+markerSeparator+"printf '%s\\n'\n", s.ID, marker))
505+}
506+
507+func resizeWorkloadGeometry(s scenario, phase string) (uint16, uint16) {
508+ if sequence, err := strconv.ParseUint(strings.TrimPrefix(phase, "measured-"), 10, 64); err == nil && strings.HasPrefix(phase, "measured-") {
509+ cols, rows, _ := workloadResize(s, sequence)
510+ return cols, rows
471 }511 }
472- return []byte(fmt.Sprintf(body+"; printf '%s\\n'\n", s.ID, marker))512+ return 120, 40
473 }513 }
M cmd/vev-perf-harness/runner_test.go +109 -8
@@ -314,7 +314,7 @@ func TestWorkloadInputShapes(t *testing.T) {
314314 workload string
315315 contains []string
316316 }{
317- {"active_output", []string{`printf '\033[10;20HX'`}},
317+ {"active_output", []string{`printf '\033[10;20H1'`}},
318318 {"all_output", []string{"120", "vev perf line"}},
319319 {"inactive_output", []string{
320320 `printf '\033[38;2;20;120;220mred'`,
@@ -339,9 +339,19 @@ func TestWorkloadInputShapes(t *testing.T) {
339339 })
340340 }
341341
342- active := string(workloadInput(scenario{ID: "s", Workload: "active_output"}, 1, "measured-1"))
343- if strings.Contains(active, `\r`) || strings.Contains(active, "vev perf") {
344- t.Fatalf("active output is not an absolute-cursor one-cell write: %q", active)
342+ activeWarmup := string(workloadInput(scenario{ID: "s", Workload: "active_output"}, 1, "warmup"))
343+ activeOne := string(workloadInput(scenario{ID: "s", Workload: "active_output"}, 1, "measured-1"))
344+ activeTwo := string(workloadInput(scenario{ID: "s", Workload: "active_output"}, 1, "measured-2"))
345+ for phase, got := range map[string]string{"warmup": activeWarmup, "measured-1": activeOne, "measured-2": activeTwo} {
346+ marker := fmt.Sprintf("__VEV_HARNESS_s_r1_%s__", phase)
347+ wantByte := map[string]byte{"warmup": 'X', "measured-1": '1', "measured-2": '0'}[phase]
348+ want := fmt.Sprintf("printf '\\033[10;20H%c'; : 's'; printf '%s\\n'\n", wantByte, marker)
349+ if got != want {
350+ t.Errorf("active %s input = %q, want exactly one changing cell write %q", phase, got, want)
351+ }
352+ }
353+ if activeWarmup == activeOne || activeOne == activeTwo {
354+ t.Fatalf("active one-cell payload did not change by sequence: %q %q %q", activeWarmup, activeOne, activeTwo)
345355 }
346356 inactive := string(workloadInput(scenario{ID: "s", Workload: "inactive_output"}, 1, "measured-1"))
347357 if count := strings.Count(inactive, `\033[38;2;`); count != 3 {
@@ -352,12 +362,14 @@ func TestWorkloadInputShapes(t *testing.T) {
352362 if count := strings.Count(warmup, `"$VEV_PERF_BIN" cmd new-tab`); count != 1 {
353363 t.Fatalf("attach warmup new-tab commands = %d, want 1: %q", count, warmup)
354364 }
355- if strings.Contains(warmup, "cmd next-tab") || !strings.Contains(warmup, `cmd previous-tab; printf 'vev perf attach`) {
356- t.Fatalf("attach warmup does not return to the marker-producing tab: %q", warmup)
365+ wantWarmup := `"$VEV_PERF_BIN" cmd new-tab && "$VEV_PERF_BIN" cmd previous-tab && printf 'vev perf attach-restore-tab-switch s\n' && printf '__VEV_HARNESS_s_r1_warmup__\n'` + "\n"
366+ if warmup != wantWarmup {
367+ t.Fatalf("attach warmup success marker is not gated by every command:\n got %q\nwant %q", warmup, wantWarmup)
357368 }
358369 measured := string(workloadInput(scenario{ID: "s", Workload: "attach_restore_tab_switch"}, 1, "measured-1"))
359- if strings.Contains(measured, "cmd new-tab") {
360- t.Fatalf("measured attach workload creates another tab: %q", measured)
370+ wantMeasured := `"$VEV_PERF_BIN" cmd next-tab && "$VEV_PERF_BIN" cmd previous-tab && printf 'vev perf attach-restore-tab-switch s\n' && printf '__VEV_HARNESS_s_r1_measured-1__\n'` + "\n"
371+ if measured != wantMeasured {
372+ t.Fatalf("attach measured success marker is not gated by every command:\n got %q\nwant %q", measured, wantMeasured)
361373 }
362374 }
363375
@@ -385,6 +397,95 @@ func TestWorkloadResize(t *testing.T) {
385397 }
386398 }
387399
400+type resizeOrderingWriter struct {
401+ bytes.Buffer
402+ order *[]string
403+}
404+
405+func (w *resizeOrderingWriter) Write(p []byte) (int, error) {
406+ if bytes.Contains(p, []byte(`"sequence":2`)) {
407+ switch {
408+ case bytes.Contains(p, []byte(`"kind":"input_injected"`)):
409+ *w.order = append(*w.order, "injected")
410+ case bytes.Contains(p, []byte(`"kind":"terminal_flushed"`)):
411+ *w.order = append(*w.order, "terminal_flushed")
412+ }
413+ }
414+ return w.Buffer.Write(p)
415+}
416+
417+type resizeOrderingProcess struct {
418+ order *[]string
419+ inputs [][]byte
420+ resizes int
421+ measures int
422+}
423+
424+func (*resizeOrderingProcess) Warmup([]byte) error { return nil }
425+
426+func (p *resizeOrderingProcess) Resize(cols, rows uint16) error {
427+ p.resizes++
428+ if p.resizes == 1 {
429+ *p.order = append(*p.order, fmt.Sprintf("resize:%dx%d", cols, rows))
430+ }
431+ return nil
432+}
433+
434+func (p *resizeOrderingProcess) Measure(input []byte, injected, flushed func() error) error {
435+ p.measures++
436+ p.inputs = append(p.inputs, append([]byte(nil), input...))
437+ if p.measures == 1 {
438+ *p.order = append(*p.order, "workload_write")
439+ }
440+ if err := injected(); err != nil {
441+ return err
442+ }
443+ if p.measures == 1 {
444+ *p.order = append(*p.order, "workload_flush")
445+ }
446+ return flushed()
447+}
448+
449+func (*resizeOrderingProcess) Close() error { return nil }
450+
451+type resizeOrderingLauncher struct{ client *resizeOrderingProcess }
452+
453+func (l resizeOrderingLauncher) Launch(m processMapping, _ roleCommand) (launchedProcess, error) {
454+ if m.Role == "client" {
455+ return l.client, nil
456+ }
457+ return &fakeProcess{}, nil
458+}
459+
460+func TestResizeBoundaryPrecedesResizeAndMarkerWaitsForShellGeometry(t *testing.T) {
461+ dir := t.TempDir()
462+ order := []string{}
463+ client := &resizeOrderingProcess{order: &order}
464+ h := defaultHarness()
465+ h.clock, h.launcher = &fakeClock{}, resizeOrderingLauncher{client: client}
466+ raw := &resizeOrderingWriter{order: &order}
467+ _, err := h.runOne(options{out: dir, warmup: time.Second, duration: minimumDuration}, manifest{}, scenario{ID: "resize", Workload: "resize_sweep", Roles: []string{"daemon", "client"}}, 1, raw)
468+ if err != nil {
469+ t.Fatal(err)
470+ }
471+ wantOrder := []string{"injected", "resize:120x40", "workload_write", "workload_flush", "terminal_flushed"}
472+ if len(order) < len(wantOrder) {
473+ t.Fatalf("resize event order = %q, want prefix %q", order, wantOrder)
474+ }
475+ if !equalStrings(order[:len(wantOrder)], wantOrder) {
476+ t.Fatalf("first resize event order = %q, want %q", order[:len(wantOrder)], wantOrder)
477+ }
478+ first := string(client.inputs[0])
479+ for _, want := range []string{`set -- $(stty size)`, `[ "$2" = "120" ]`, `sleep 0.01`, `&& printf '__VEV_HARNESS_resize_r1_measured-2__\n'`} {
480+ if !strings.Contains(first, want) {
481+ t.Errorf("resize input has no bounded expected-column gate %q: %q", want, first)
482+ }
483+ }
484+ if strings.Contains(first, `"40 120"`) {
485+ t.Fatalf("resize input incorrectly gates on client rows that shell chrome does not expose: %q", first)
486+ }
487+}
488+
388489 func TestHarnessAggregatesRawEventsSeparatelyFromRunDispersion(t *testing.T) {
389490 first := append([]int64(nil), make([]int64, minimumInIntervalEventSamples+5)...)
390491 for i := range first {
@@ -314,7 +314,7 @@ func TestWorkloadInputShapes(t *testing.T) {
314 workload string314 workload string
315 contains []string315 contains []string
316 }{316 }{
317- {"active_output", []string{`printf '\033[10;20HX'`}},317+ {"active_output", []string{`printf '\033[10;20H1'`}},
318 {"all_output", []string{"120", "vev perf line"}},318 {"all_output", []string{"120", "vev perf line"}},
319 {"inactive_output", []string{319 {"inactive_output", []string{
320 `printf '\033[38;2;20;120;220mred'`,320 `printf '\033[38;2;20;120;220mred'`,
@@ -339,9 +339,19 @@ func TestWorkloadInputShapes(t *testing.T) {
339 })339 })
340 }340 }
341 341
342- active := string(workloadInput(scenario{ID: "s", Workload: "active_output"}, 1, "measured-1"))342+ activeWarmup := string(workloadInput(scenario{ID: "s", Workload: "active_output"}, 1, "warmup"))
343- if strings.Contains(active, `\r`) || strings.Contains(active, "vev perf") {343+ activeOne := string(workloadInput(scenario{ID: "s", Workload: "active_output"}, 1, "measured-1"))
344- t.Fatalf("active output is not an absolute-cursor one-cell write: %q", active)344+ activeTwo := string(workloadInput(scenario{ID: "s", Workload: "active_output"}, 1, "measured-2"))
345+ for phase, got := range map[string]string{"warmup": activeWarmup, "measured-1": activeOne, "measured-2": activeTwo} {
346+ marker := fmt.Sprintf("__VEV_HARNESS_s_r1_%s__", phase)
347+ wantByte := map[string]byte{"warmup": 'X', "measured-1": '1', "measured-2": '0'}[phase]
348+ want := fmt.Sprintf("printf '\\033[10;20H%c'; : 's'; printf '%s\\n'\n", wantByte, marker)
349+ if got != want {
350+ t.Errorf("active %s input = %q, want exactly one changing cell write %q", phase, got, want)
351+ }
352+ }
353+ if activeWarmup == activeOne || activeOne == activeTwo {
354+ t.Fatalf("active one-cell payload did not change by sequence: %q %q %q", activeWarmup, activeOne, activeTwo)
345 }355 }
346 inactive := string(workloadInput(scenario{ID: "s", Workload: "inactive_output"}, 1, "measured-1"))356 inactive := string(workloadInput(scenario{ID: "s", Workload: "inactive_output"}, 1, "measured-1"))
347 if count := strings.Count(inactive, `\033[38;2;`); count != 3 {357 if count := strings.Count(inactive, `\033[38;2;`); count != 3 {
@@ -352,12 +362,14 @@ func TestWorkloadInputShapes(t *testing.T) {
352 if count := strings.Count(warmup, `"$VEV_PERF_BIN" cmd new-tab`); count != 1 {362 if count := strings.Count(warmup, `"$VEV_PERF_BIN" cmd new-tab`); count != 1 {
353 t.Fatalf("attach warmup new-tab commands = %d, want 1: %q", count, warmup)363 t.Fatalf("attach warmup new-tab commands = %d, want 1: %q", count, warmup)
354 }364 }
355- if strings.Contains(warmup, "cmd next-tab") || !strings.Contains(warmup, `cmd previous-tab; printf 'vev perf attach`) {365+ wantWarmup := `"$VEV_PERF_BIN" cmd new-tab && "$VEV_PERF_BIN" cmd previous-tab && printf 'vev perf attach-restore-tab-switch s\n' && printf '__VEV_HARNESS_s_r1_warmup__\n'` + "\n"
356- t.Fatalf("attach warmup does not return to the marker-producing tab: %q", warmup)366+ if warmup != wantWarmup {
367+ t.Fatalf("attach warmup success marker is not gated by every command:\n got %q\nwant %q", warmup, wantWarmup)
357 }368 }
358 measured := string(workloadInput(scenario{ID: "s", Workload: "attach_restore_tab_switch"}, 1, "measured-1"))369 measured := string(workloadInput(scenario{ID: "s", Workload: "attach_restore_tab_switch"}, 1, "measured-1"))
359- if strings.Contains(measured, "cmd new-tab") {370+ wantMeasured := `"$VEV_PERF_BIN" cmd next-tab && "$VEV_PERF_BIN" cmd previous-tab && printf 'vev perf attach-restore-tab-switch s\n' && printf '__VEV_HARNESS_s_r1_measured-1__\n'` + "\n"
360- t.Fatalf("measured attach workload creates another tab: %q", measured)371+ if measured != wantMeasured {
372+ t.Fatalf("attach measured success marker is not gated by every command:\n got %q\nwant %q", measured, wantMeasured)
361 }373 }
362 }374 }
363 375
@@ -385,6 +397,95 @@ func TestWorkloadResize(t *testing.T) {
385 }397 }
386 }398 }
387 399
400+type resizeOrderingWriter struct {
401+ bytes.Buffer
402+ order *[]string
403+}
404+
405+func (w *resizeOrderingWriter) Write(p []byte) (int, error) {
406+ if bytes.Contains(p, []byte(`"sequence":2`)) {
407+ switch {
408+ case bytes.Contains(p, []byte(`"kind":"input_injected"`)):
409+ *w.order = append(*w.order, "injected")
410+ case bytes.Contains(p, []byte(`"kind":"terminal_flushed"`)):
411+ *w.order = append(*w.order, "terminal_flushed")
412+ }
413+ }
414+ return w.Buffer.Write(p)
415+}
416+
417+type resizeOrderingProcess struct {
418+ order *[]string
419+ inputs [][]byte
420+ resizes int
421+ measures int
422+}
423+
424+func (*resizeOrderingProcess) Warmup([]byte) error { return nil }
425+
426+func (p *resizeOrderingProcess) Resize(cols, rows uint16) error {
427+ p.resizes++
428+ if p.resizes == 1 {
429+ *p.order = append(*p.order, fmt.Sprintf("resize:%dx%d", cols, rows))
430+ }
431+ return nil
432+}
433+
434+func (p *resizeOrderingProcess) Measure(input []byte, injected, flushed func() error) error {
435+ p.measures++
436+ p.inputs = append(p.inputs, append([]byte(nil), input...))
437+ if p.measures == 1 {
438+ *p.order = append(*p.order, "workload_write")
439+ }
440+ if err := injected(); err != nil {
441+ return err
442+ }
443+ if p.measures == 1 {
444+ *p.order = append(*p.order, "workload_flush")
445+ }
446+ return flushed()
447+}
448+
449+func (*resizeOrderingProcess) Close() error { return nil }
450+
451+type resizeOrderingLauncher struct{ client *resizeOrderingProcess }
452+
453+func (l resizeOrderingLauncher) Launch(m processMapping, _ roleCommand) (launchedProcess, error) {
454+ if m.Role == "client" {
455+ return l.client, nil
456+ }
457+ return &fakeProcess{}, nil
458+}
459+
460+func TestResizeBoundaryPrecedesResizeAndMarkerWaitsForShellGeometry(t *testing.T) {
461+ dir := t.TempDir()
462+ order := []string{}
463+ client := &resizeOrderingProcess{order: &order}
464+ h := defaultHarness()
465+ h.clock, h.launcher = &fakeClock{}, resizeOrderingLauncher{client: client}
466+ raw := &resizeOrderingWriter{order: &order}
467+ _, err := h.runOne(options{out: dir, warmup: time.Second, duration: minimumDuration}, manifest{}, scenario{ID: "resize", Workload: "resize_sweep", Roles: []string{"daemon", "client"}}, 1, raw)
468+ if err != nil {
469+ t.Fatal(err)
470+ }
471+ wantOrder := []string{"injected", "resize:120x40", "workload_write", "workload_flush", "terminal_flushed"}
472+ if len(order) < len(wantOrder) {
473+ t.Fatalf("resize event order = %q, want prefix %q", order, wantOrder)
474+ }
475+ if !equalStrings(order[:len(wantOrder)], wantOrder) {
476+ t.Fatalf("first resize event order = %q, want %q", order[:len(wantOrder)], wantOrder)
477+ }
478+ first := string(client.inputs[0])
479+ for _, want := range []string{`set -- $(stty size)`, `[ "$2" = "120" ]`, `sleep 0.01`, `&& printf '__VEV_HARNESS_resize_r1_measured-2__\n'`} {
480+ if !strings.Contains(first, want) {
481+ t.Errorf("resize input has no bounded expected-column gate %q: %q", want, first)
482+ }
483+ }
484+ if strings.Contains(first, `"40 120"`) {
485+ t.Fatalf("resize input incorrectly gates on client rows that shell chrome does not expose: %q", first)
486+ }
487+}
488+
388 func TestHarnessAggregatesRawEventsSeparatelyFromRunDispersion(t *testing.T) {489 func TestHarnessAggregatesRawEventsSeparatelyFromRunDispersion(t *testing.T) {
389 first := append([]int64(nil), make([]int64, minimumInIntervalEventSamples+5)...)490 first := append([]int64(nil), make([]int64, minimumInIntervalEventSamples+5)...)
390 for i := range first {491 for i := range first {
M docs/performance.md +2 -1
@@ -123,7 +123,8 @@ Straddling or failed events remain raw diagnostics, not samples. Each run must
123123 contain `manifest.json`, `raw-harness.jsonl`, per-process JSONL, `runs.json`, and
124124 `summary.json`.
125125
126-`summary.json` records the source `git_sha` and measurement parameters;
126+`summary.json` records the harness source as `git_sha`, the measured vev binary
127+source as `vev_git_sha`, and the measurement parameters;
127128 `runs.json`, run manifests, and JSONL identify scenario, run, process, component,
128129 and correlation IDs. Preserve those files with the benchmark output and record
129130 `go version -m /tmp/vev-proxy-baseline`; do not commit result directories. CPU
@@ -123,7 +123,8 @@ Straddling or failed events remain raw diagnostics, not samples. Each run must
123 contain `manifest.json`, `raw-harness.jsonl`, per-process JSONL, `runs.json`, and123 contain `manifest.json`, `raw-harness.jsonl`, per-process JSONL, `runs.json`, and
124 `summary.json`.124 `summary.json`.
125 125
126-`summary.json` records the source `git_sha` and measurement parameters;126+`summary.json` records the harness source as `git_sha`, the measured vev binary
127+source as `vev_git_sha`, and the measurement parameters;
127 `runs.json`, run manifests, and JSONL identify scenario, run, process, component,128 `runs.json`, run manifests, and JSONL identify scenario, run, process, component,
128 and correlation IDs. Preserve those files with the benchmark output and record129 and correlation IDs. Preserve those files with the benchmark output and record
129 `go version -m /tmp/vev-proxy-baseline`; do not commit result directories. CPU130 `go version -m /tmp/vev-proxy-baseline`; do not commit result directories. CPU
M internal/usecase/daemon/performance_benchmark_test.go +1 -6
@@ -214,8 +214,7 @@ func TestProxyANSIBenchmarkFixtures(t *testing.T) {
214214
215215 for _, fixture := range fixtures {
216216 t.Run(fixture.name, func(t *testing.T) {
217- proxy, err := newProxySession(domain.RemoteSessionKey{Host: "benchmark", Name: "ansi"}, domain.Size{Cols: 120, Rows: 40 + tabChromeRows})
218- require.NoError(t, err)
217+ proxy := newProxyANSIBenchmarkSession(t)
219218
220219 ack, reset, changed := proxy.applyOutputForGeneration(proxy.linkGeneration, ports.Output{
221220 BaseStateNum: 0,
@@ -233,10 +232,6 @@ func TestProxyANSIBenchmarkFixtures(t *testing.T) {
233232 }
234233 }
235234
236-func TestProxyANSIBenchmarkGeometry(t *testing.T) {
237- newProxyANSIBenchmarkSession(t)
238-}
239-
240235 func newProxyANSIBenchmarkSession(t testing.TB) *proxySession {
241236 t.Helper()
242237 proxy, err := newProxySession(domain.RemoteSessionKey{Host: "benchmark", Name: "ansi"}, domain.Size{Cols: 120, Rows: 40 + tabChromeRows})
@@ -214,8 +214,7 @@ func TestProxyANSIBenchmarkFixtures(t *testing.T) {
214 214
215 for _, fixture := range fixtures {215 for _, fixture := range fixtures {
216 t.Run(fixture.name, func(t *testing.T) {216 t.Run(fixture.name, func(t *testing.T) {
217- proxy, err := newProxySession(domain.RemoteSessionKey{Host: "benchmark", Name: "ansi"}, domain.Size{Cols: 120, Rows: 40 + tabChromeRows})217+ proxy := newProxyANSIBenchmarkSession(t)
218- require.NoError(t, err)
219 218
220 ack, reset, changed := proxy.applyOutputForGeneration(proxy.linkGeneration, ports.Output{219 ack, reset, changed := proxy.applyOutputForGeneration(proxy.linkGeneration, ports.Output{
221 BaseStateNum: 0,220 BaseStateNum: 0,
@@ -233,10 +232,6 @@ func TestProxyANSIBenchmarkFixtures(t *testing.T) {
233 }232 }
234 }233 }
235 234
236-func TestProxyANSIBenchmarkGeometry(t *testing.T) {
237- newProxyANSIBenchmarkSession(t)
238-}
239-
240 func newProxyANSIBenchmarkSession(t testing.TB) *proxySession {235 func newProxyANSIBenchmarkSession(t testing.TB) *proxySession {
241 t.Helper()236 t.Helper()
242 proxy, err := newProxySession(domain.RemoteSessionKey{Host: "benchmark", Name: "ansi"}, domain.Size{Cols: 120, Rows: 40 + tabChromeRows})237 proxy, err := newProxySession(domain.RemoteSessionKey{Host: "benchmark", Name: "ansi"}, domain.Size{Cols: 120, Rows: 40 + tabChromeRows})

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close