| // Copyright 2025 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| //go:build !(go1.27 && !http2legacy) | |
| package http2 | |
| import ( | |
| "testing" | |
| ) | |
| func benchmarkThroughput(b *testing.B, wsFunc func() WriteScheduler, priority PriorityParam) { | |
| const maxFrameSize = 16 | |
| const streamCount = 100 | |
| ws := wsFunc() | |
| sc := &serverConn{maxFrameSize: maxFrameSize} | |
| streams := make([]*stream, streamCount) | |
| // Possible stream payloads. We vary the payload size of different streams | |
| // to simulate real traffic somewhat. | |
| streamsFrame := [][]byte{ | |
| make([]byte, maxFrameSize*5), | |
| make([]byte, maxFrameSize*10), | |
| make([]byte, maxFrameSize*15), | |
| make([]byte, maxFrameSize*20), | |
| make([]byte, maxFrameSize*25), | |
| } | |
| for i := range streams { | |
| streamID := uint32(i) + 1 | |
| streams[i] = &stream{ | |
| id: streamID, | |
| sc: sc, | |
| } | |
| streams[i].flow.add(1 << 30) // arbitrary large value | |
| ws.OpenStream(streamID, OpenStreamOptions{ | |
| priority: priority, | |
| }) | |
| } | |
| for b.Loop() { | |
| for i := range streams { | |
| streamID := uint32(i) + 1 | |
| ws.Push(FrameWriteRequest{ | |
| write: &writeData{ | |
| streamID: streamID, | |
| p: streamsFrame[i%len(streamsFrame)], | |
| endStream: false, | |
| }, | |
| stream: streams[i], | |
| }) | |
| } | |
| for { | |
| wr, ok := ws.Pop() | |
| if !ok { | |
| break | |
| } | |
| if wr.DataSize() != maxFrameSize { | |
| b.Fatalf("wr.Pop() = %v data bytes, want %v", wr.DataSize(), maxFrameSize) | |
| } | |
| } | |
| } | |
| for i := range streams { | |
| streamID := uint32(i) + 1 | |
| ws.CloseStream(streamID) | |
| } | |
| } | |
| func benchmarkStreamLifetime(b *testing.B, wsFunc func() WriteScheduler, priority PriorityParam) { | |
| const maxFrameSize = 16 | |
| const streamCount = 100 | |
| ws := wsFunc() | |
| sc := &serverConn{maxFrameSize: maxFrameSize} | |
| streams := make([]*stream, streamCount) | |
| // Possible stream payloads. We vary the payload size of different streams | |
| // to simulate real traffic somewhat. | |
| streamsFrame := [][]byte{ | |
| make([]byte, maxFrameSize*5), | |
| make([]byte, maxFrameSize*10), | |
| make([]byte, maxFrameSize*15), | |
| make([]byte, maxFrameSize*20), | |
| make([]byte, maxFrameSize*25), | |
| } | |
| for i := range streams { | |
| streamID := uint32(i) + 1 | |
| streams[i] = &stream{ | |
| id: streamID, | |
| sc: sc, | |
| } | |
| streams[i].flow.add(1 << 30) // arbitrary large value | |
| } | |
| for b.Loop() { | |
| for i := range streams { | |
| streamID := uint32(i) + 1 | |
| ws.OpenStream(streamID, OpenStreamOptions{ | |
| priority: priority, | |
| }) | |
| ws.Push(FrameWriteRequest{ | |
| write: &writeData{ | |
| streamID: streamID, | |
| p: streamsFrame[i%len(streamsFrame)], | |
| endStream: false, | |
| }, | |
| stream: streams[i], | |
| }) | |
| } | |
| for { | |
| wr, ok := ws.Pop() | |
| if !ok { | |
| break | |
| } | |
| if wr.DataSize() != maxFrameSize { | |
| b.Fatalf("wr.Pop() = %v data bytes, want %v", wr.DataSize(), maxFrameSize) | |
| } | |
| } | |
| for i := range streams { | |
| streamID := uint32(i) + 1 | |
| ws.CloseStream(streamID) | |
| } | |
| } | |
| } | |
| func BenchmarkWriteSchedulerThroughputRoundRobin(b *testing.B) { | |
| benchmarkThroughput(b, newRoundRobinWriteScheduler, PriorityParam{}) | |
| } | |
| func BenchmarkWriteSchedulerLifetimeRoundRobin(b *testing.B) { | |
| benchmarkStreamLifetime(b, newRoundRobinWriteScheduler, PriorityParam{}) | |
| } | |
| func BenchmarkWriteSchedulerThroughputRandom(b *testing.B) { | |
| benchmarkThroughput(b, NewRandomWriteScheduler, PriorityParam{}) | |
| } | |
| func BenchmarkWriteSchedulerLifetimeRandom(b *testing.B) { | |
| benchmarkStreamLifetime(b, NewRandomWriteScheduler, PriorityParam{}) | |
| } | |
| func BenchmarkWriteSchedulerThroughputPriorityRFC7540(b *testing.B) { | |
| benchmarkThroughput(b, func() WriteScheduler { return NewPriorityWriteScheduler(nil) }, PriorityParam{}) | |
| } | |
| func BenchmarkWriteSchedulerLifetimePriorityRFC7540(b *testing.B) { | |
| // RFC7540 priority scheduler does not always succeed in closing the | |
| // stream, causing this benchmark to panic due to opening an already open | |
| // stream. | |
| b.SkipNow() | |
| benchmarkStreamLifetime(b, func() WriteScheduler { return NewPriorityWriteScheduler(nil) }, PriorityParam{}) | |
| } | |
| func BenchmarkWriteSchedulerThroughputPriorityRFC9218Incremental(b *testing.B) { | |
| benchmarkThroughput(b, newPriorityWriteSchedulerRFC9218, PriorityParam{ | |
| incremental: 1, | |
| }) | |
| } | |
| func BenchmarkWriteSchedulerLifetimePriorityRFC9218Incremental(b *testing.B) { | |
| benchmarkStreamLifetime(b, newPriorityWriteSchedulerRFC9218, PriorityParam{ | |
| incremental: 1, | |
| }) | |
| } | |
| func BenchmarkWriteSchedulerThroughputPriorityRFC9218NonIncremental(b *testing.B) { | |
| benchmarkThroughput(b, newPriorityWriteSchedulerRFC9218, PriorityParam{ | |
| incremental: 0, | |
| }) | |
| } | |
| func BenchmarkWriteSchedulerLifetimePriorityRFC9218NonIncremental(b *testing.B) { | |
| benchmarkStreamLifetime(b, newPriorityWriteSchedulerRFC9218, PriorityParam{ | |
| incremental: 0, | |
| }) | |
| } | |
| func BenchmarkWriteQueue(b *testing.B) { | |
| var qp writeQueuePool | |
| frameCount := 25 | |
| for b.Loop() { | |
| q := qp.get() | |
| for range frameCount { | |
| q.push(FrameWriteRequest{}) | |
| } | |
| for !q.empty() { | |
| // Since we pushed empty frames, consuming 1 byte is enough to | |
| // consume the entire frame. | |
| q.consume(1) | |
| } | |
| qp.put(q) | |
| } | |
| } |