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