Skip to content

Commit 5e5bb59

Browse files
committed
Cleanup and isolate tests
1 parent 8bdf0bf commit 5e5bb59

File tree

2 files changed

+309
-317
lines changed

2 files changed

+309
-317
lines changed

bench_decode_test.go

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
package redis
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"net"
8+
"testing"
9+
"time"
10+
11+
"github.com/go-redis/redis/v8/internal/proto"
12+
)
13+
14+
var ctx = context.TODO()
15+
16+
type ClientStub struct {
17+
Cmdable
18+
resp []byte
19+
}
20+
21+
func NewClientStub(resp []byte) *ClientStub {
22+
stub := &ClientStub{
23+
resp: resp,
24+
}
25+
stub.Cmdable = NewClient(&Options{
26+
PoolSize: 128,
27+
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
28+
return stub.stubConn(), nil
29+
},
30+
})
31+
return stub
32+
}
33+
34+
func NewClusterClientStub(resp []byte) *ClientStub {
35+
stub := &ClientStub{
36+
resp: resp,
37+
}
38+
39+
client := NewClusterClient(&ClusterOptions{
40+
PoolSize: 128,
41+
Addrs: []string{"127.0.0.1:6379"},
42+
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
43+
return stub.stubConn(), nil
44+
},
45+
ClusterSlots: func(_ context.Context) ([]ClusterSlot, error) {
46+
return []ClusterSlot{
47+
{
48+
Start: 0,
49+
End: 16383,
50+
Nodes: []ClusterNode{{Addr: "127.0.0.1:6379"}},
51+
},
52+
}, nil
53+
},
54+
})
55+
56+
// init command.
57+
tmpClient := NewClient(&Options{Addr: ":6379"})
58+
cmdsInfo, err := tmpClient.Command(ctx).Result()
59+
_ = tmpClient.Close()
60+
client.cmdsInfoCache = newCmdsInfoCache(func(_ context.Context) (map[string]*CommandInfo, error) {
61+
return cmdsInfo, err
62+
})
63+
64+
stub.Cmdable = client
65+
return stub
66+
}
67+
68+
func (c *ClientStub) stubConn() *ConnStub {
69+
return &ConnStub{
70+
resp: c.resp,
71+
}
72+
}
73+
74+
type ConnStub struct {
75+
resp []byte
76+
pos int
77+
}
78+
79+
func (c *ConnStub) Read(b []byte) (n int, err error) {
80+
if len(c.resp) == 0 {
81+
return 0, io.EOF
82+
}
83+
84+
if c.pos >= len(c.resp) {
85+
c.pos = 0
86+
}
87+
n = copy(b, c.resp[c.pos:])
88+
c.pos += n
89+
return n, nil
90+
}
91+
92+
func (c *ConnStub) Write(b []byte) (n int, err error) { return len(b), nil }
93+
func (c *ConnStub) Close() error { return nil }
94+
func (c *ConnStub) LocalAddr() net.Addr { return nil }
95+
func (c *ConnStub) RemoteAddr() net.Addr { return nil }
96+
func (c *ConnStub) SetDeadline(_ time.Time) error { return nil }
97+
func (c *ConnStub) SetReadDeadline(_ time.Time) error { return nil }
98+
func (c *ConnStub) SetWriteDeadline(_ time.Time) error { return nil }
99+
100+
type ClientStubFunc func([]byte) *ClientStub
101+
102+
func BenchmarkDecode(b *testing.B) {
103+
type Benchmark struct {
104+
name string
105+
stub ClientStubFunc
106+
}
107+
108+
benchmarks := []Benchmark{
109+
{"single", NewClientStub},
110+
{"cluster", NewClusterClientStub},
111+
}
112+
113+
for _, bench := range benchmarks {
114+
b.Run(fmt.Sprintf("RespError-%s", bench.name), func(b *testing.B) {
115+
respError(b, bench.stub)
116+
})
117+
b.Run(fmt.Sprintf("RespStatus-%s", bench.name), func(b *testing.B) {
118+
respStatus(b, bench.stub)
119+
})
120+
b.Run(fmt.Sprintf("RespInt-%s", bench.name), func(b *testing.B) {
121+
respInt(b, bench.stub)
122+
})
123+
b.Run(fmt.Sprintf("RespString-%s", bench.name), func(b *testing.B) {
124+
respString(b, bench.stub)
125+
})
126+
b.Run(fmt.Sprintf("RespArray-%s", bench.name), func(b *testing.B) {
127+
respArray(b, bench.stub)
128+
})
129+
b.Run(fmt.Sprintf("RespPipeline-%s", bench.name), func(b *testing.B) {
130+
respPipeline(b, bench.stub)
131+
})
132+
b.Run(fmt.Sprintf("RespTxPipeline-%s", bench.name), func(b *testing.B) {
133+
respTxPipeline(b, bench.stub)
134+
})
135+
136+
// goroutine
137+
b.Run(fmt.Sprintf("DynamicGoroutine-%s-pool=5", bench.name), func(b *testing.B) {
138+
dynamicGoroutine(b, bench.stub, 5)
139+
})
140+
b.Run(fmt.Sprintf("DynamicGoroutine-%s-pool=20", bench.name), func(b *testing.B) {
141+
dynamicGoroutine(b, bench.stub, 20)
142+
})
143+
b.Run(fmt.Sprintf("DynamicGoroutine-%s-pool=50", bench.name), func(b *testing.B) {
144+
dynamicGoroutine(b, bench.stub, 50)
145+
})
146+
b.Run(fmt.Sprintf("DynamicGoroutine-%s-pool=100", bench.name), func(b *testing.B) {
147+
dynamicGoroutine(b, bench.stub, 100)
148+
})
149+
150+
b.Run(fmt.Sprintf("StaticGoroutine-%s-pool=5", bench.name), func(b *testing.B) {
151+
staticGoroutine(b, bench.stub, 5)
152+
})
153+
b.Run(fmt.Sprintf("StaticGoroutine-%s-pool=20", bench.name), func(b *testing.B) {
154+
staticGoroutine(b, bench.stub, 20)
155+
})
156+
b.Run(fmt.Sprintf("StaticGoroutine-%s-pool=50", bench.name), func(b *testing.B) {
157+
staticGoroutine(b, bench.stub, 50)
158+
})
159+
b.Run(fmt.Sprintf("StaticGoroutine-%s-pool=100", bench.name), func(b *testing.B) {
160+
staticGoroutine(b, bench.stub, 100)
161+
})
162+
}
163+
}
164+
165+
func respError(b *testing.B, stub ClientStubFunc) {
166+
rdb := stub([]byte("-ERR test error\r\n"))
167+
respErr := proto.RedisError("ERR test error")
168+
169+
b.ResetTimer()
170+
for i := 0; i < b.N; i++ {
171+
if err := rdb.Get(ctx, "key").Err(); err != respErr {
172+
b.Fatalf("response error, got %q, want %q", err, respErr)
173+
}
174+
}
175+
}
176+
177+
func respStatus(b *testing.B, stub ClientStubFunc) {
178+
rdb := stub([]byte("+OK\r\n"))
179+
var val string
180+
181+
b.ResetTimer()
182+
for i := 0; i < b.N; i++ {
183+
if val = rdb.Set(ctx, "key", "value", 0).Val(); val != "OK" {
184+
b.Fatalf("response error, got %q, want OK", val)
185+
}
186+
}
187+
}
188+
189+
func respInt(b *testing.B, stub ClientStubFunc) {
190+
rdb := stub([]byte(":10\r\n"))
191+
var val int64
192+
193+
b.ResetTimer()
194+
for i := 0; i < b.N; i++ {
195+
if val = rdb.Incr(ctx, "key").Val(); val != 10 {
196+
b.Fatalf("response error, got %q, want 10", val)
197+
}
198+
}
199+
}
200+
201+
func respString(b *testing.B, stub ClientStubFunc) {
202+
rdb := stub([]byte("$5\r\nhello\r\n"))
203+
var val string
204+
205+
b.ResetTimer()
206+
for i := 0; i < b.N; i++ {
207+
if val = rdb.Get(ctx, "key").Val(); val != "hello" {
208+
b.Fatalf("response error, got %q, want hello", val)
209+
}
210+
}
211+
}
212+
213+
func respArray(b *testing.B, stub ClientStubFunc) {
214+
rdb := stub([]byte("*3\r\n$5\r\nhello\r\n:10\r\n+OK\r\n"))
215+
var val []interface{}
216+
217+
b.ResetTimer()
218+
for i := 0; i < b.N; i++ {
219+
if val = rdb.MGet(ctx, "key").Val(); len(val) != 3 {
220+
b.Fatalf("response error, got len(%d), want len(3)", len(val))
221+
}
222+
}
223+
}
224+
225+
func respPipeline(b *testing.B, stub ClientStubFunc) {
226+
rdb := stub([]byte("+OK\r\n$5\r\nhello\r\n:1\r\n"))
227+
var pipe Pipeliner
228+
229+
b.ResetTimer()
230+
for i := 0; i < b.N; i++ {
231+
pipe = rdb.Pipeline()
232+
set := pipe.Set(ctx, "key", "value", 0)
233+
get := pipe.Get(ctx, "key")
234+
del := pipe.Del(ctx, "key")
235+
_, err := pipe.Exec(ctx)
236+
if err != nil {
237+
b.Fatalf("response error, got %q, want nil", err)
238+
}
239+
if set.Val() != "OK" || get.Val() != "hello" || del.Val() != 1 {
240+
b.Fatal("response error")
241+
}
242+
}
243+
}
244+
245+
func respTxPipeline(b *testing.B, stub ClientStubFunc) {
246+
rdb := stub([]byte("+OK\r\n+QUEUED\r\n+QUEUED\r\n+QUEUED\r\n*3\r\n+OK\r\n$5\r\nhello\r\n:1\r\n"))
247+
248+
b.ResetTimer()
249+
for i := 0; i < b.N; i++ {
250+
var set *StatusCmd
251+
var get *StringCmd
252+
var del *IntCmd
253+
_, err := rdb.TxPipelined(ctx, func(pipe Pipeliner) error {
254+
set = pipe.Set(ctx, "key", "value", 0)
255+
get = pipe.Get(ctx, "key")
256+
del = pipe.Del(ctx, "key")
257+
return nil
258+
})
259+
if err != nil {
260+
b.Fatalf("response error, got %q, want nil", err)
261+
}
262+
if set.Val() != "OK" || get.Val() != "hello" || del.Val() != 1 {
263+
b.Fatal("response error")
264+
}
265+
}
266+
}
267+
268+
func dynamicGoroutine(b *testing.B, stub ClientStubFunc, concurrency int) {
269+
rdb := stub([]byte("$5\r\nhello\r\n"))
270+
c := make(chan struct{}, concurrency)
271+
272+
b.ResetTimer()
273+
for i := 0; i < b.N; i++ {
274+
c <- struct{}{}
275+
go func() {
276+
if val := rdb.Get(ctx, "key").Val(); val != "hello" {
277+
panic(fmt.Sprintf("response error, got %q, want hello", val))
278+
}
279+
<-c
280+
}()
281+
}
282+
// Here no longer wait for all goroutines to complete, it will not affect the test results.
283+
close(c)
284+
}
285+
286+
func staticGoroutine(b *testing.B, stub ClientStubFunc, concurrency int) {
287+
rdb := stub([]byte("$5\r\nhello\r\n"))
288+
c := make(chan struct{}, concurrency)
289+
290+
b.ResetTimer()
291+
292+
for i := 0; i < concurrency; i++ {
293+
go func() {
294+
for {
295+
_, ok := <-c
296+
if !ok {
297+
return
298+
}
299+
if val := rdb.Get(ctx, "key").Val(); val != "hello" {
300+
panic(fmt.Sprintf("response error, got %q, want hello", val))
301+
}
302+
}
303+
}()
304+
}
305+
for i := 0; i < b.N; i++ {
306+
c <- struct{}{}
307+
}
308+
close(c)
309+
}

0 commit comments

Comments
 (0)