fix(daemon): keep benchmark workloads representative
- commit
- 8aba936c86b12b74795f15d5a2332d5828899d8b
- author
- brice · · 2026-07-11 05:37 UTC
- committer
- brice · · 2026-07-11 06:15 UTC
- parent
- b590931
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +26 -14
M internal/usecase/daemon/performance_benchmark_test.go +26 -8
| @@ -36,6 +36,16 @@ func TestPerformanceFixtureCounters(t *testing.T) { | |||
| 36 | 36 | require.True(t, fixture.resized()) | |
| 37 | 37 | } | |
| 38 | 38 | ||
| 39 | + | func TestPerformanceFixtureResizeAlternatesRealDimensions(t *testing.T) { | |
| 40 | + | fixture := newPerformanceFixture(t, performanceConfig{size: domain.Size{Cols: 120, Rows: 40}}) | |
| 41 | + | ||
| 42 | + | for _, want := range []domain.Size{{Cols: 100, Rows: 30}, {Cols: 120, Rows: 40}} { | |
| 43 | + | fixture.resize() | |
| 44 | + | require.Equal(t, want, fixture.ac.size) | |
| 45 | + | require.True(t, fixture.resized()) | |
| 46 | + | } | |
| 47 | + | } | |
| 48 | + | ||
| 39 | 49 | func TestPerformanceFixtureLargeHistoryTopology(t *testing.T) { | |
| 40 | 50 | fixture := newPerformanceFixture(t, performanceConfig{size: domain.Size{Cols: 120, Rows: 40}, tabs: 4, panes: 4, historyRows: 10_000}) | |
| 41 | 51 | ||
| @@ -95,6 +105,9 @@ func benchmarkDaemonLargeHistory(b *testing.B, workload string, run func(*perfor | |||
| 95 | 105 | if workload == "live-paint" && metrics.outputFrames != uint64(b.N) { | |
| 96 | 106 | b.Fatalf("live paint emitted %d frames for %d operations", metrics.outputFrames, b.N) | |
| 97 | 107 | } | |
| 108 | + | if workload == "snapshot-capture" && metrics.snapshotWrites != uint64(b.N) { | |
| 109 | + | b.Fatalf("snapshot capture wrote %d snapshots for %d operations", metrics.snapshotWrites, b.N) | |
| 110 | + | } | |
| 98 | 111 | benchmarkReportMetrics(b, metrics, b.N, 10_000) | |
| 99 | 112 | }) | |
| 100 | 113 | } | |
| @@ -109,6 +122,7 @@ func benchmarkReportMetrics(b *testing.B, metrics performanceMetrics, operations | |||
| 109 | 122 | b.ReportMetric(float64(metrics.outputFrames)/perOperation, "outputframes/op") | |
| 110 | 123 | b.ReportMetric(float64(metrics.outputBytes)/perOperation, "outputbytes/op") | |
| 111 | 124 | b.ReportMetric(float64(metrics.outputPayloadBytes)/perOperation, "framepayloadbytes/op") | |
| 125 | + | b.ReportMetric(float64(metrics.snapshotWrites)/perOperation, "snapshotwrites/op") | |
| 112 | 126 | b.ReportMetric(float64(metrics.snapshotBytes)/perOperation, "snapshotbytes/op") | |
| 113 | 127 | b.ReportMetric(float64(historyRows), "historyrows/pane") | |
| 114 | 128 | } | |
| @@ -155,6 +169,8 @@ type performanceFixture struct { | |||
| 155 | 169 | activePane *pane | |
| 156 | 170 | liveWrites [][]byte | |
| 157 | 171 | paints int | |
| 172 | + | resizeSizes [2]domain.Size | |
| 173 | + | resizes int | |
| 158 | 174 | resizedSize domain.Size | |
| 159 | 175 | } | |
| 160 | 176 | ||
| @@ -182,13 +198,14 @@ func newPerformanceFixture(t testing.TB, config performanceConfig) *performanceF | |||
| 182 | 198 | sess.snapEligible.Store(true) | |
| 183 | 199 | ||
| 184 | 200 | fixture := &performanceFixture{ | |
| 185 | - | t: t, | |
| 186 | - | d: d, | |
| 187 | - | sess: sess, | |
| 188 | - | ac: ac, | |
| 189 | - | output: output, | |
| 190 | - | snaps: &countingSnapshotStore{}, | |
| 191 | - | liveWrites: [][]byte{[]byte("\x1b[1;1HA\x1b[2;2HA"), []byte("\x1b[1;1HB\x1b[2;2HB")}, | |
| 201 | + | t: t, | |
| 202 | + | d: d, | |
| 203 | + | sess: sess, | |
| 204 | + | ac: ac, | |
| 205 | + | output: output, | |
| 206 | + | snaps: &countingSnapshotStore{}, | |
| 207 | + | liveWrites: [][]byte{[]byte("\x1b[1;1HA\x1b[2;2HA"), []byte("\x1b[1;1HB\x1b[2;2HB")}, | |
| 208 | + | resizeSizes: [2]domain.Size{{Cols: 100, Rows: 30}, config.size}, | |
| 192 | 209 | } | |
| 193 | 210 | WithSnapshotStore(fixture.snaps)(d) | |
| 194 | 211 | for tabIndex, tb := range sess.tabs { | |
| @@ -279,7 +296,8 @@ func (f *performanceFixture) searchMatches() int { | |||
| 279 | 296 | } | |
| 280 | 297 | ||
| 281 | 298 | func (f *performanceFixture) resize() { | |
| 282 | - | f.resizedSize = domain.Size{Cols: 100, Rows: 30} | |
| 299 | + | f.resizedSize = f.resizeSizes[f.resizes%len(f.resizeSizes)] | |
| 300 | + | f.resizes++ | |
| 283 | 301 | f.d.resize(f.sess, f.ac, f.resizedSize) | |
| 284 | 302 | } | |
| 285 | 303 | ||
| @@ -36,6 +36,16 @@ func TestPerformanceFixtureCounters(t *testing.T) { | |||||
| 36 | require.True(t, fixture.resized()) | 36 | require.True(t, fixture.resized()) | ||
| 37 | } | 37 | } | ||
| 38 | 38 | ||||
| 39 | + | func TestPerformanceFixtureResizeAlternatesRealDimensions(t *testing.T) { | |||
| 40 | + | fixture := newPerformanceFixture(t, performanceConfig{size: domain.Size{Cols: 120, Rows: 40}}) | |||
| 41 | + | ||||
| 42 | + | for _, want := range []domain.Size{{Cols: 100, Rows: 30}, {Cols: 120, Rows: 40}} { | |||
| 43 | + | fixture.resize() | |||
| 44 | + | require.Equal(t, want, fixture.ac.size) | |||
| 45 | + | require.True(t, fixture.resized()) | |||
| 46 | + | } | |||
| 47 | + | } | |||
| 48 | + | ||||
| 39 | func TestPerformanceFixtureLargeHistoryTopology(t *testing.T) { | 49 | func TestPerformanceFixtureLargeHistoryTopology(t *testing.T) { | ||
| 40 | fixture := newPerformanceFixture(t, performanceConfig{size: domain.Size{Cols: 120, Rows: 40}, tabs: 4, panes: 4, historyRows: 10_000}) | 50 | fixture := newPerformanceFixture(t, performanceConfig{size: domain.Size{Cols: 120, Rows: 40}, tabs: 4, panes: 4, historyRows: 10_000}) | ||
| 41 | 51 | ||||
| @@ -95,6 +105,9 @@ func benchmarkDaemonLargeHistory(b *testing.B, workload string, run func(*perfor | |||||
| 95 | if workload == "live-paint" && metrics.outputFrames != uint64(b.N) { | 105 | if workload == "live-paint" && metrics.outputFrames != uint64(b.N) { | ||
| 96 | b.Fatalf("live paint emitted %d frames for %d operations", metrics.outputFrames, b.N) | 106 | b.Fatalf("live paint emitted %d frames for %d operations", metrics.outputFrames, b.N) | ||
| 97 | } | 107 | } | ||
| 108 | + | if workload == "snapshot-capture" && metrics.snapshotWrites != uint64(b.N) { | |||
| 109 | + | b.Fatalf("snapshot capture wrote %d snapshots for %d operations", metrics.snapshotWrites, b.N) | |||
| 110 | + | } | |||
| 98 | benchmarkReportMetrics(b, metrics, b.N, 10_000) | 111 | benchmarkReportMetrics(b, metrics, b.N, 10_000) | ||
| 99 | }) | 112 | }) | ||
| 100 | } | 113 | } | ||
| @@ -109,6 +122,7 @@ func benchmarkReportMetrics(b *testing.B, metrics performanceMetrics, operations | |||||
| 109 | b.ReportMetric(float64(metrics.outputFrames)/perOperation, "outputframes/op") | 122 | b.ReportMetric(float64(metrics.outputFrames)/perOperation, "outputframes/op") | ||
| 110 | b.ReportMetric(float64(metrics.outputBytes)/perOperation, "outputbytes/op") | 123 | b.ReportMetric(float64(metrics.outputBytes)/perOperation, "outputbytes/op") | ||
| 111 | b.ReportMetric(float64(metrics.outputPayloadBytes)/perOperation, "framepayloadbytes/op") | 124 | b.ReportMetric(float64(metrics.outputPayloadBytes)/perOperation, "framepayloadbytes/op") | ||
| 125 | + | b.ReportMetric(float64(metrics.snapshotWrites)/perOperation, "snapshotwrites/op") | |||
| 112 | b.ReportMetric(float64(metrics.snapshotBytes)/perOperation, "snapshotbytes/op") | 126 | b.ReportMetric(float64(metrics.snapshotBytes)/perOperation, "snapshotbytes/op") | ||
| 113 | b.ReportMetric(float64(historyRows), "historyrows/pane") | 127 | b.ReportMetric(float64(historyRows), "historyrows/pane") | ||
| 114 | } | 128 | } | ||
| @@ -155,6 +169,8 @@ type performanceFixture struct { | |||||
| 155 | activePane *pane | 169 | activePane *pane | ||
| 156 | liveWrites [][]byte | 170 | liveWrites [][]byte | ||
| 157 | paints int | 171 | paints int | ||
| 172 | + | resizeSizes [2]domain.Size | |||
| 173 | + | resizes int | |||
| 158 | resizedSize domain.Size | 174 | resizedSize domain.Size | ||
| 159 | } | 175 | } | ||
| 160 | 176 | ||||
| @@ -182,13 +198,14 @@ func newPerformanceFixture(t testing.TB, config performanceConfig) *performanceF | |||||
| 182 | sess.snapEligible.Store(true) | 198 | sess.snapEligible.Store(true) | ||
| 183 | 199 | ||||
| 184 | fixture := &performanceFixture{ | 200 | fixture := &performanceFixture{ | ||
| 185 | - | t: t, | 201 | + | t: t, |
| 186 | - | d: d, | 202 | + | d: d, |
| 187 | - | sess: sess, | 203 | + | sess: sess, |
| 188 | - | ac: ac, | 204 | + | ac: ac, |
| 189 | - | output: output, | 205 | + | output: output, |
| 190 | - | snaps: &countingSnapshotStore{}, | 206 | + | snaps: &countingSnapshotStore{}, |
| 191 | - | liveWrites: [][]byte{[]byte("\x1b[1;1HA\x1b[2;2HA"), []byte("\x1b[1;1HB\x1b[2;2HB")}, | 207 | + | liveWrites: [][]byte{[]byte("\x1b[1;1HA\x1b[2;2HA"), []byte("\x1b[1;1HB\x1b[2;2HB")}, |
| 208 | + | resizeSizes: [2]domain.Size{{Cols: 100, Rows: 30}, config.size}, | |||
| 192 | } | 209 | } | ||
| 193 | WithSnapshotStore(fixture.snaps)(d) | 210 | WithSnapshotStore(fixture.snaps)(d) | ||
| 194 | for tabIndex, tb := range sess.tabs { | 211 | for tabIndex, tb := range sess.tabs { | ||
| @@ -279,7 +296,8 @@ func (f *performanceFixture) searchMatches() int { | |||||
| 279 | } | 296 | } | ||
| 280 | 297 | ||||
| 281 | func (f *performanceFixture) resize() { | 298 | func (f *performanceFixture) resize() { | ||
| 282 | - | f.resizedSize = domain.Size{Cols: 100, Rows: 30} | 299 | + | f.resizedSize = f.resizeSizes[f.resizes%len(f.resizeSizes)] |
| 300 | + | f.resizes++ | |||
| 283 | f.d.resize(f.sess, f.ac, f.resizedSize) | 301 | f.d.resize(f.sess, f.ac, f.resizedSize) | ||
| 284 | } | 302 | } | ||
| 285 | 303 | ||||
M internal/usecase/daemon/render_test.go +0 -6
| @@ -107,12 +107,6 @@ func TestPaletteBackdropProductionRenderAndDismissal(t *testing.T) { | |||
| 107 | 107 | ||
| 108 | 108 | // --- test doubles ----------------------------------------------------------- | |
| 109 | 109 | ||
| 110 | - | type discardTransport struct{} | |
| 111 | - | ||
| 112 | - | func (discardTransport) Send(ports.Frame) error { return nil } | |
| 113 | - | func (discardTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF } | |
| 114 | - | func (discardTransport) Close() error { return nil } | |
| 115 | - | ||
| 116 | 110 | // stubClock returns timers whose channel never fires, so a scheduler under it | |
| 117 | 111 | // blocks in its debounce loop until the session context is cancelled. Used by | |
| 118 | 112 | ||
| @@ -107,12 +107,6 @@ func TestPaletteBackdropProductionRenderAndDismissal(t *testing.T) { | |||||
| 107 | 107 | ||||
| 108 | // --- test doubles ----------------------------------------------------------- | 108 | // --- test doubles ----------------------------------------------------------- | ||
| 109 | 109 | ||||
| 110 | - | type discardTransport struct{} | |||
| 111 | - | ||||
| 112 | - | func (discardTransport) Send(ports.Frame) error { return nil } | |||
| 113 | - | func (discardTransport) Recv() (ports.Frame, error) { return ports.Frame{}, io.EOF } | |||
| 114 | - | func (discardTransport) Close() error { return nil } | |||
| 115 | - | ||||
| 116 | // stubClock returns timers whose channel never fires, so a scheduler under it | 110 | // stubClock returns timers whose channel never fires, so a scheduler under it | ||
| 117 | // blocks in its debounce loop until the session context is cancelled. Used by | 111 | // blocks in its debounce loop until the session context is cancelled. Used by | ||
| 118 | 112 | ||||