145 lines
2.6 KiB
Go
145 lines
2.6 KiB
Go
|
|
package notify
|
||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"io"
|
||
|
|
"net"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
func BenchmarkRawTCPLocalhostThroughput(b *testing.B) {
|
||
|
|
cases := []struct {
|
||
|
|
name string
|
||
|
|
payloadSize int
|
||
|
|
}{
|
||
|
|
{
|
||
|
|
name: "raw_64KiB",
|
||
|
|
payloadSize: 64 * 1024,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "raw_256KiB",
|
||
|
|
payloadSize: 256 * 1024,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "raw_512KiB",
|
||
|
|
payloadSize: 512 * 1024,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "raw_1MiB",
|
||
|
|
payloadSize: 1024 * 1024,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tc := range cases {
|
||
|
|
b.Run(tc.name, func(b *testing.B) {
|
||
|
|
benchmarkRawTCPLocalhostThroughput(b, tc.payloadSize)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func benchmarkRawTCPLocalhostThroughput(b *testing.B, payloadSize int) {
|
||
|
|
b.Helper()
|
||
|
|
|
||
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||
|
|
if err != nil {
|
||
|
|
b.Fatalf("net.Listen failed: %v", err)
|
||
|
|
}
|
||
|
|
b.Cleanup(func() {
|
||
|
|
_ = listener.Close()
|
||
|
|
})
|
||
|
|
|
||
|
|
acceptCh := make(chan net.Conn, 1)
|
||
|
|
acceptErrCh := make(chan error, 1)
|
||
|
|
go func() {
|
||
|
|
conn, err := listener.Accept()
|
||
|
|
if err != nil {
|
||
|
|
acceptErrCh <- err
|
||
|
|
return
|
||
|
|
}
|
||
|
|
acceptCh <- conn
|
||
|
|
}()
|
||
|
|
|
||
|
|
clientConn, err := net.Dial("tcp", listener.Addr().String())
|
||
|
|
if err != nil {
|
||
|
|
b.Fatalf("net.Dial failed: %v", err)
|
||
|
|
}
|
||
|
|
b.Cleanup(func() {
|
||
|
|
_ = clientConn.Close()
|
||
|
|
})
|
||
|
|
if tcpConn, ok := clientConn.(*net.TCPConn); ok {
|
||
|
|
_ = tcpConn.SetNoDelay(true)
|
||
|
|
}
|
||
|
|
|
||
|
|
var serverConn net.Conn
|
||
|
|
select {
|
||
|
|
case conn := <-acceptCh:
|
||
|
|
serverConn = conn
|
||
|
|
case err := <-acceptErrCh:
|
||
|
|
b.Fatalf("Accept failed: %v", err)
|
||
|
|
case <-time.After(5 * time.Second):
|
||
|
|
b.Fatal("timed out waiting for accept")
|
||
|
|
}
|
||
|
|
b.Cleanup(func() {
|
||
|
|
if serverConn != nil {
|
||
|
|
_ = serverConn.Close()
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
drainDone := make(chan error, 1)
|
||
|
|
go func() {
|
||
|
|
_, err := io.Copy(io.Discard, serverConn)
|
||
|
|
if err != nil && !errors.Is(err, io.EOF) {
|
||
|
|
drainDone <- err
|
||
|
|
return
|
||
|
|
}
|
||
|
|
drainDone <- nil
|
||
|
|
}()
|
||
|
|
|
||
|
|
payload := make([]byte, payloadSize)
|
||
|
|
for i := range payload {
|
||
|
|
payload[i] = byte(i)
|
||
|
|
}
|
||
|
|
|
||
|
|
b.ReportAllocs()
|
||
|
|
b.SetBytes(int64(payloadSize))
|
||
|
|
b.ResetTimer()
|
||
|
|
for i := 0; i < b.N; i++ {
|
||
|
|
if err := benchmarkRawTCPWriteFull(clientConn, payload); err != nil {
|
||
|
|
b.Fatalf("raw tcp write failed at iter %d: %v", i, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
b.StopTimer()
|
||
|
|
|
||
|
|
if tcpConn, ok := clientConn.(*net.TCPConn); ok {
|
||
|
|
_ = tcpConn.CloseWrite()
|
||
|
|
} else {
|
||
|
|
_ = clientConn.Close()
|
||
|
|
}
|
||
|
|
|
||
|
|
select {
|
||
|
|
case err := <-drainDone:
|
||
|
|
if err != nil {
|
||
|
|
b.Fatalf("server drain failed: %v", err)
|
||
|
|
}
|
||
|
|
case <-time.After(10 * time.Second):
|
||
|
|
b.Fatal("timed out waiting for server drain")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func benchmarkRawTCPWriteFull(conn net.Conn, payload []byte) error {
|
||
|
|
for len(payload) > 0 {
|
||
|
|
n, err := conn.Write(payload)
|
||
|
|
if n > 0 {
|
||
|
|
payload = payload[n:]
|
||
|
|
}
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if n == 0 {
|
||
|
|
return io.ErrNoProgress
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|