Skip to content

Commit 5c5ca75

Browse files
feat(traffic_replay): Add compare option (#5555)
Signed-off-by: Stepan Bagritsevich <[email protected]>
1 parent e5c7800 commit 5c5ca75

File tree

2 files changed

+99
-1
lines changed

2 files changed

+99
-1
lines changed

tools/replay/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
)
1515

1616
var fHost = flag.String("host", "127.0.0.1:6379", "Redis host")
17+
var fCompareHost = flag.String("compare-host", "", "Redis host to compare with")
1718
var fClientBuffer = flag.Int("buffer", 100, "How many records to buffer per client")
1819
var fPace = flag.Bool("pace", true, "whether to pace the traffic according to the original timings.false - to pace as fast as possible")
1920
var fSkip = flag.Uint("skip", 0, "skip N records")

tools/replay/workers.go

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ func DetermineBaseTime(files []string) time.Time {
4343
// Handles a single connection/client
4444
type ClientWorker struct {
4545
redis *redis.Client
46+
compare *redis.Client
4647
incoming chan Record
4748
processed uint
4849
pipe redis.Pipeliner
50+
comparePipe redis.Pipeliner
4951
}
5052

5153
// Pipeline length ranges for summary
@@ -60,6 +62,16 @@ var pipelineRanges = []struct {
6062
{"200+", 200, 1 << 30},
6163
}
6264

65+
var compareIgnoreCmds = []string{
66+
"HELLO",
67+
"AUTH",
68+
"SELECT",
69+
"INFO",
70+
"TIME",
71+
"CLIENT",
72+
"CONFIG",
73+
}
74+
6375
// Handles a single file and distributes messages to clients
6476
type FileWorker struct {
6577
clientGroup sync.WaitGroup
@@ -100,11 +112,82 @@ func trackLatency(worker *FileWorker, batchLatency float64, size int) {
100112
}
101113
}
102114

115+
func ignoreCompareCmd(c redis.Cmder) bool {
116+
args := c.Args()
117+
if len(args) == 0 {
118+
return true
119+
}
120+
name := strings.ToUpper(fmt.Sprint(args[0]))
121+
for _, ign := range compareIgnoreCmds {
122+
if name == ign {
123+
return true
124+
}
125+
}
126+
return false
127+
}
128+
129+
func cmdAsString(c redis.Cmder) string {
130+
args := c.Args()
131+
if len(args) == 0 {
132+
return "<no-args>"
133+
}
134+
135+
name := strings.ToUpper(fmt.Sprint(args[0]))
136+
if len(args) == 1 {
137+
return name
138+
}
139+
140+
parts := make([]string, 0, len(args) - 1)
141+
for _, a := range args[1:] {
142+
s := fmt.Sprint(a)
143+
parts = append(parts, s)
144+
}
145+
return name + " " + strings.Join(parts, " ")
146+
}
147+
148+
func cmdResultString(cm redis.Cmder) string {
149+
if err := cm.Err(); err != nil {
150+
if err == redis.Nil {
151+
return "(nil)"
152+
}
153+
return "ERR: " + err.Error()
154+
}
155+
156+
if cmd, ok := cm.(*redis.Cmd); ok {
157+
v := cmd.Val()
158+
s := fmt.Sprintf("%v", v)
159+
return s
160+
}
161+
162+
return fmt.Sprintf("<unknown Cmder %T>", cm)
163+
}
164+
165+
func compareCmdResults(a, b []redis.Cmder, lastMsg Record) {
166+
if len(a) != len(b) {
167+
log.Fatalf("[COMPARE] mismatch count: primary=%d compare=%d (last client=%d time=%d)", len(a), len(b), lastMsg.Client, lastMsg.Time)
168+
return
169+
}
170+
171+
for i := range a {
172+
if (ignoreCompareCmd(a[i])) {
173+
continue
174+
}
175+
pa := cmdResultString(a[i])
176+
pb := cmdResultString(b[i])
177+
if pa != pb {
178+
log.Fatalf("[COMPARE] mismatch at idx %d cmd=%s\n primary=%s\n compare=%s\n (client=%d time=%d)", i, cmdAsString(a[i]), pa, pb, lastMsg.Client, lastMsg.Time)
179+
}
180+
}
181+
}
182+
103183
func (c *ClientWorker) Run(pace bool, worker *FileWorker) {
104184
for msg := range c.incoming {
105185
if c.processed == 0 && msg.DbIndex != 0 {
106186
// There is no easy way to switch, we rely on connection pool consisting only of one connection
107187
c.redis.Do(context.Background(), []interface{}{"SELECT", fmt.Sprint(msg.DbIndex)})
188+
if c.compare != nil {
189+
c.compare.Do(context.Background(), []interface{}{"SELECT", fmt.Sprint(msg.DbIndex)})
190+
}
108191
}
109192

110193
lag := time.Until(worker.HappensAt(time.Unix(0, int64(msg.Time))))
@@ -117,15 +200,24 @@ func (c *ClientWorker) Run(pace bool, worker *FileWorker) {
117200
}
118201

119202
c.pipe.Do(context.Background(), msg.values...).Result()
203+
if c.comparePipe != nil {
204+
c.comparePipe.Do(context.Background(), msg.values...).Result()
205+
}
206+
120207
atomic.AddUint64(&worker.processed, 1)
121208

122209
if msg.HasMore == 0 {
123210
size := c.pipe.Len()
124211
start := time.Now()
125-
c.pipe.Exec(context.Background())
212+
cmds, _ := c.pipe.Exec(context.Background())
126213
batchLatency := float64(time.Since(start).Microseconds())
127214
trackLatency(worker, batchLatency, size)
128215
c.processed += uint(size)
216+
217+
if c.comparePipe != nil {
218+
ccmds, _ := c.comparePipe.Exec(context.Background())
219+
compareCmdResults(cmds, ccmds, msg)
220+
}
129221
}
130222
}
131223

@@ -147,6 +239,11 @@ func NewClient(w *FileWorker, pace bool) *ClientWorker {
147239
}
148240
client.pipe = client.redis.Pipeline()
149241

242+
if *fCompareHost != "" {
243+
client.compare = redis.NewClient(&redis.Options{Addr: *fCompareHost, PoolSize: 1, DisableIndentity: true})
244+
client.comparePipe = client.compare.Pipeline()
245+
}
246+
150247
atomic.AddUint64(&w.clients, 1)
151248
w.clientGroup.Add(1)
152249
go client.Run(pace, w)

0 commit comments

Comments
 (0)