perf(proxy): benchmark ANSI relay workloads
- commit
- b54b862c80d4d3be95208e5921ae685df3f0e0e0
- author
- brice · · 2026-08-01 04:53 UTC
- parent
- 919bcd1
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +94 -0
M internal/usecase/daemon/performance_benchmark_test.go +94 -0
| @@ -1,6 +1,7 @@ | |||
| 1 | 1 | package daemon | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "bytes" | |
| 4 | 5 | "context" | |
| 5 | 6 | "errors" | |
| 6 | 7 | "fmt" | |
| @@ -196,6 +197,71 @@ func TestCountingOutputTransportCountsOpaquePayloadAndRejectsShortPayload(t *tes | |||
| 196 | 197 | require.Equal(t, payload, transport.lastPayload(), "the counting transport must retain the payload slice header, not copy it") | |
| 197 | 198 | } | |
| 198 | 199 | ||
| 200 | + | func TestProxyANSIBenchmarkFixtures(t *testing.T) { | |
| 201 | + | fixtures := proxyANSIBenchmarkFixtures() | |
| 202 | + | require.Len(t, fixtures, 4) | |
| 203 | + | require.Equal(t, []string{ | |
| 204 | + | "absolute-position-one-cell", | |
| 205 | + | "120-column-full-line", | |
| 206 | + | "fragmented-truecolor-styled-line", | |
| 207 | + | "full-width-40-row-scroll", | |
| 208 | + | }, []string{fixtures[0].name, fixtures[1].name, fixtures[2].name, fixtures[3].name}) | |
| 209 | + | require.Equal(t, fixtures, proxyANSIBenchmarkFixtures(), "fixtures must be deterministic") | |
| 210 | + | require.Equal(t, []byte("\x1b[20;60HX"), fixtures[0].data) | |
| 211 | + | require.Equal(t, append([]byte("\x1b[1;1H"), bytes.Repeat([]byte("l"), 120)...), fixtures[1].data) | |
| 212 | + | require.Equal(t, 12, bytes.Count(fixtures[2].data, []byte("\x1b[38;2;"))) | |
| 213 | + | require.Equal(t, 40, bytes.Count(fixtures[3].data, []byte("\r\n"))) | |
| 214 | + | ||
| 215 | + | for _, fixture := range fixtures { | |
| 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}) | |
| 218 | + | require.NoError(t, err) | |
| 219 | + | ||
| 220 | + | ack, reset, changed := proxy.applyOutputForGeneration(proxy.linkGeneration, ports.Output{ | |
| 221 | + | BaseStateNum: 0, | |
| 222 | + | NewStateNum: 1, | |
| 223 | + | Data: fixture.data, | |
| 224 | + | }) | |
| 225 | + | ||
| 226 | + | require.Equal(t, uint64(1), ack) | |
| 227 | + | require.False(t, reset) | |
| 228 | + | require.True(t, changed) | |
| 229 | + | proxy.mu.Lock() | |
| 230 | + | require.Equal(t, domain.Size{Cols: 120, Rows: 40}, proxy.contentSize) | |
| 231 | + | proxy.mu.Unlock() | |
| 232 | + | }) | |
| 233 | + | } | |
| 234 | + | } | |
| 235 | + | ||
| 236 | + | type proxyANSIBenchmarkFixture struct { | |
| 237 | + | name string | |
| 238 | + | data []byte | |
| 239 | + | } | |
| 240 | + | ||
| 241 | + | func proxyANSIBenchmarkFixtures() []proxyANSIBenchmarkFixture { | |
| 242 | + | fullLine := append([]byte("\x1b[1;1H"), bytes.Repeat([]byte("l"), 120)...) | |
| 243 | + | ||
| 244 | + | var styledLine []byte | |
| 245 | + | for fragment := range 12 { | |
| 246 | + | styledLine = fmt.Appendf(styledLine, "\x1b[12;%dH\x1b[38;2;%d;%d;%dm", fragment*10+1, fragment*17, fragment*13, fragment*7) | |
| 247 | + | styledLine = append(styledLine, bytes.Repeat([]byte{byte('a' + fragment)}, 10)...) | |
| 248 | + | } | |
| 249 | + | styledLine = append(styledLine, "\x1b[0m"...) | |
| 250 | + | ||
| 251 | + | scroll := make([]byte, 0, 40*122) | |
| 252 | + | for row := range 40 { | |
| 253 | + | scroll = append(scroll, performanceFullWidthRow(120, 0, row)...) | |
| 254 | + | scroll = append(scroll, '\r', '\n') | |
| 255 | + | } | |
| 256 | + | ||
| 257 | + | return []proxyANSIBenchmarkFixture{ | |
| 258 | + | {name: "absolute-position-one-cell", data: []byte("\x1b[20;60HX")}, | |
| 259 | + | {name: "120-column-full-line", data: fullLine}, | |
| 260 | + | {name: "fragmented-truecolor-styled-line", data: styledLine}, | |
| 261 | + | {name: "full-width-40-row-scroll", data: scroll}, | |
| 262 | + | } | |
| 263 | + | } | |
| 264 | + | ||
| 199 | 265 | func TestPerformanceFixtureCounters(t *testing.T) { | |
| 200 | 266 | fixture := newPerformanceFixture(t, performanceConfig{}) | |
| 201 | 267 | ||
| @@ -359,6 +425,34 @@ var daemonSnapshotTopologies = []daemonHistoryTopology{ | |||
| 359 | 425 | {name: "1tab-4panes", tabs: 1, panes: 4}, | |
| 360 | 426 | } | |
| 361 | 427 | ||
| 428 | + | func BenchmarkProxyANSIApply(b *testing.B) { | |
| 429 | + | for _, fixture := range proxyANSIBenchmarkFixtures() { | |
| 430 | + | b.Run(fixture.name, func(b *testing.B) { | |
| 431 | + | proxy, err := newProxySession(domain.RemoteSessionKey{Host: "benchmark", Name: "ansi"}, domain.Size{Cols: 120, Rows: 40}) | |
| 432 | + | if err != nil { | |
| 433 | + | b.Fatal(err) | |
| 434 | + | } | |
| 435 | + | generation := proxy.linkGeneration | |
| 436 | + | state := uint64(0) | |
| 437 | + | b.ReportAllocs() | |
| 438 | + | b.SetBytes(int64(len(fixture.data))) | |
| 439 | + | for b.Loop() { | |
| 440 | + | next := state + 1 | |
| 441 | + | ack, reset, changed := proxy.applyOutputForGeneration(generation, ports.Output{ | |
| 442 | + | BaseStateNum: state, | |
| 443 | + | NewStateNum: next, | |
| 444 | + | Data: fixture.data, | |
| 445 | + | }) | |
| 446 | + | if ack != next || reset || !changed { | |
| 447 | + | b.Fatalf("apply output = ack %d, reset %t, changed %t; want ack %d, reset false, changed true", ack, reset, changed, next) | |
| 448 | + | } | |
| 449 | + | state = next | |
| 450 | + | } | |
| 451 | + | b.ReportMetric(float64(len(fixture.data)), "ansibytes/op") | |
| 452 | + | }) | |
| 453 | + | } | |
| 454 | + | } | |
| 455 | + | ||
| 362 | 456 | func BenchmarkDaemonHistoryLivePaint(b *testing.B) { | |
| 363 | 457 | benchmarkDaemonLargeHistory(b, "live-paint", func(f *performanceFixture) { f.paintLive() }) | |
| 364 | 458 | } | |
| @@ -1,6 +1,7 @@ | |||||
| 1 | package daemon | 1 | package daemon | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "bytes" | |||
| 4 | "context" | 5 | "context" | ||
| 5 | "errors" | 6 | "errors" | ||
| 6 | "fmt" | 7 | "fmt" | ||
| @@ -196,6 +197,71 @@ func TestCountingOutputTransportCountsOpaquePayloadAndRejectsShortPayload(t *tes | |||||
| 196 | require.Equal(t, payload, transport.lastPayload(), "the counting transport must retain the payload slice header, not copy it") | 197 | require.Equal(t, payload, transport.lastPayload(), "the counting transport must retain the payload slice header, not copy it") | ||
| 197 | } | 198 | } | ||
| 198 | 199 | ||||
| 200 | + | func TestProxyANSIBenchmarkFixtures(t *testing.T) { | |||
| 201 | + | fixtures := proxyANSIBenchmarkFixtures() | |||
| 202 | + | require.Len(t, fixtures, 4) | |||
| 203 | + | require.Equal(t, []string{ | |||
| 204 | + | "absolute-position-one-cell", | |||
| 205 | + | "120-column-full-line", | |||
| 206 | + | "fragmented-truecolor-styled-line", | |||
| 207 | + | "full-width-40-row-scroll", | |||
| 208 | + | }, []string{fixtures[0].name, fixtures[1].name, fixtures[2].name, fixtures[3].name}) | |||
| 209 | + | require.Equal(t, fixtures, proxyANSIBenchmarkFixtures(), "fixtures must be deterministic") | |||
| 210 | + | require.Equal(t, []byte("\x1b[20;60HX"), fixtures[0].data) | |||
| 211 | + | require.Equal(t, append([]byte("\x1b[1;1H"), bytes.Repeat([]byte("l"), 120)...), fixtures[1].data) | |||
| 212 | + | require.Equal(t, 12, bytes.Count(fixtures[2].data, []byte("\x1b[38;2;"))) | |||
| 213 | + | require.Equal(t, 40, bytes.Count(fixtures[3].data, []byte("\r\n"))) | |||
| 214 | + | ||||
| 215 | + | for _, fixture := range fixtures { | |||
| 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}) | |||
| 218 | + | require.NoError(t, err) | |||
| 219 | + | ||||
| 220 | + | ack, reset, changed := proxy.applyOutputForGeneration(proxy.linkGeneration, ports.Output{ | |||
| 221 | + | BaseStateNum: 0, | |||
| 222 | + | NewStateNum: 1, | |||
| 223 | + | Data: fixture.data, | |||
| 224 | + | }) | |||
| 225 | + | ||||
| 226 | + | require.Equal(t, uint64(1), ack) | |||
| 227 | + | require.False(t, reset) | |||
| 228 | + | require.True(t, changed) | |||
| 229 | + | proxy.mu.Lock() | |||
| 230 | + | require.Equal(t, domain.Size{Cols: 120, Rows: 40}, proxy.contentSize) | |||
| 231 | + | proxy.mu.Unlock() | |||
| 232 | + | }) | |||
| 233 | + | } | |||
| 234 | + | } | |||
| 235 | + | ||||
| 236 | + | type proxyANSIBenchmarkFixture struct { | |||
| 237 | + | name string | |||
| 238 | + | data []byte | |||
| 239 | + | } | |||
| 240 | + | ||||
| 241 | + | func proxyANSIBenchmarkFixtures() []proxyANSIBenchmarkFixture { | |||
| 242 | + | fullLine := append([]byte("\x1b[1;1H"), bytes.Repeat([]byte("l"), 120)...) | |||
| 243 | + | ||||
| 244 | + | var styledLine []byte | |||
| 245 | + | for fragment := range 12 { | |||
| 246 | + | styledLine = fmt.Appendf(styledLine, "\x1b[12;%dH\x1b[38;2;%d;%d;%dm", fragment*10+1, fragment*17, fragment*13, fragment*7) | |||
| 247 | + | styledLine = append(styledLine, bytes.Repeat([]byte{byte('a' + fragment)}, 10)...) | |||
| 248 | + | } | |||
| 249 | + | styledLine = append(styledLine, "\x1b[0m"...) | |||
| 250 | + | ||||
| 251 | + | scroll := make([]byte, 0, 40*122) | |||
| 252 | + | for row := range 40 { | |||
| 253 | + | scroll = append(scroll, performanceFullWidthRow(120, 0, row)...) | |||
| 254 | + | scroll = append(scroll, '\r', '\n') | |||
| 255 | + | } | |||
| 256 | + | ||||
| 257 | + | return []proxyANSIBenchmarkFixture{ | |||
| 258 | + | {name: "absolute-position-one-cell", data: []byte("\x1b[20;60HX")}, | |||
| 259 | + | {name: "120-column-full-line", data: fullLine}, | |||
| 260 | + | {name: "fragmented-truecolor-styled-line", data: styledLine}, | |||
| 261 | + | {name: "full-width-40-row-scroll", data: scroll}, | |||
| 262 | + | } | |||
| 263 | + | } | |||
| 264 | + | ||||
| 199 | func TestPerformanceFixtureCounters(t *testing.T) { | 265 | func TestPerformanceFixtureCounters(t *testing.T) { | ||
| 200 | fixture := newPerformanceFixture(t, performanceConfig{}) | 266 | fixture := newPerformanceFixture(t, performanceConfig{}) | ||
| 201 | 267 | ||||
| @@ -359,6 +425,34 @@ var daemonSnapshotTopologies = []daemonHistoryTopology{ | |||||
| 359 | {name: "1tab-4panes", tabs: 1, panes: 4}, | 425 | {name: "1tab-4panes", tabs: 1, panes: 4}, | ||
| 360 | } | 426 | } | ||
| 361 | 427 | ||||
| 428 | + | func BenchmarkProxyANSIApply(b *testing.B) { | |||
| 429 | + | for _, fixture := range proxyANSIBenchmarkFixtures() { | |||
| 430 | + | b.Run(fixture.name, func(b *testing.B) { | |||
| 431 | + | proxy, err := newProxySession(domain.RemoteSessionKey{Host: "benchmark", Name: "ansi"}, domain.Size{Cols: 120, Rows: 40}) | |||
| 432 | + | if err != nil { | |||
| 433 | + | b.Fatal(err) | |||
| 434 | + | } | |||
| 435 | + | generation := proxy.linkGeneration | |||
| 436 | + | state := uint64(0) | |||
| 437 | + | b.ReportAllocs() | |||
| 438 | + | b.SetBytes(int64(len(fixture.data))) | |||
| 439 | + | for b.Loop() { | |||
| 440 | + | next := state + 1 | |||
| 441 | + | ack, reset, changed := proxy.applyOutputForGeneration(generation, ports.Output{ | |||
| 442 | + | BaseStateNum: state, | |||
| 443 | + | NewStateNum: next, | |||
| 444 | + | Data: fixture.data, | |||
| 445 | + | }) | |||
| 446 | + | if ack != next || reset || !changed { | |||
| 447 | + | b.Fatalf("apply output = ack %d, reset %t, changed %t; want ack %d, reset false, changed true", ack, reset, changed, next) | |||
| 448 | + | } | |||
| 449 | + | state = next | |||
| 450 | + | } | |||
| 451 | + | b.ReportMetric(float64(len(fixture.data)), "ansibytes/op") | |||
| 452 | + | }) | |||
| 453 | + | } | |||
| 454 | + | } | |||
| 455 | + | ||||
| 362 | func BenchmarkDaemonHistoryLivePaint(b *testing.B) { | 456 | func BenchmarkDaemonHistoryLivePaint(b *testing.B) { | ||
| 363 | benchmarkDaemonLargeHistory(b, "live-paint", func(f *performanceFixture) { f.paintLive() }) | 457 | benchmarkDaemonLargeHistory(b, "live-paint", func(f *performanceFixture) { f.paintLive() }) | ||
| 364 | } | 458 | } | ||