redis-cli-sh/cmd/kv-write-canary/main.go

132 lines
2.6 KiB
Go

package main
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"time"
valkey "github.com/redis/go-redis/v9"
)
func main() {
url := os.Args[1]
ctx := context.Background()
opt, err := valkey.ParseURL(url)
if err != nil {
panic(err)
}
client := valkey.NewClient(opt)
if err != nil {
panic(err)
}
defer client.Close()
if err := client.Ping(ctx).Err(); err != nil {
panic(err)
}
spans := poll(ctx, client)
format := "15:04:05"
for i, span := range spans.Spans {
log.Printf("[%d] (%vs) %v .. %v", i, int(span[1].Sub(span[0]).Seconds()), span[0].Format(format), span[1].Format(format))
if i < len(spans.Spans)-1 {
log.Printf("[%d] (%vs...)", i, int(spans.Spans[i+1][0].Sub(span[1]).Seconds()))
}
}
misses := 0
for i, write := range spans.Writes {
result := client.Get(ctx, write)
if err := result.Err(); err != nil {
misses += 1
log.Printf("[%d] expected %s but %v", i, write, err)
}
}
log.Printf("cannot confirm %d/%d entries", misses, len(spans.Writes))
}
type Poll struct {
Spans [][2]time.Time
Writes []string
}
func poll(ctx context.Context, client *valkey.Client) Poll {
ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT)
defer can()
spans := [][2]time.Time{
{time.Now(), time.Now()},
}
writes := []string{}
for ctx.Err() == nil {
waitFailure(ctx, client, func(s string) {
writes = append(writes, s)
})
spans[len(spans)-1][1] = time.Now()
waitSuccess(ctx, client, func(s string) {
writes = append(writes, s)
})
now := time.Now()
spans = append(spans, [2]time.Time{now, now})
}
if last := spans[len(spans)-1]; last[1] == last[0] {
spans = spans[:len(spans)-1]
}
return Poll{Spans: spans, Writes: writes}
}
func waitFailure(ctx context.Context, client *valkey.Client, cb func(string)) {
waitResult(ctx, client, false, cb)
}
func waitSuccess(ctx context.Context, client *valkey.Client, cb func(string)) {
waitResult(ctx, client, true, cb)
}
func waitResult(ctx context.Context, client *valkey.Client, wantOK bool, cb func(string)) {
wait(ctx, func() bool {
k := time.Now().String()
result := client.Do(ctx, "SET", k, "ok")
err := result.Err()
got := err == nil
if got {
cb(k)
}
if got != wantOK {
fmt.Fprintf(os.Stderr, ".")
} else if wantOK {
fmt.Fprintf(os.Stderr, "\n")
log.Println("okay!")
} else {
fmt.Fprintf(os.Stderr, "\n")
log.Println("failed!")
}
return got == wantOK
})
}
func wait(ctx context.Context, foo func() bool) {
c := time.NewTicker(250 * time.Millisecond)
defer c.Stop()
for {
select {
case <-c.C:
case <-ctx.Done():
fmt.Println()
return
}
if foo() {
return
}
}
}