From 53d8ecbefdf92a7109874f4685d2f1e292b1fcbf Mon Sep 17 00:00:00 2001 From: lucasliang Date: Tue, 30 Apr 2024 14:32:20 +0800 Subject: [PATCH 01/30] benchtool: basement of benchmarking tool for TiKV. Signed-off-by: lucasliang --- examples/benchtool/config/config.go | 102 ++++++ examples/benchtool/go.mod | 58 ++++ examples/benchtool/main.go | 71 +++++ .../benchtool/utils/statistics/histogram.go | 129 ++++++++ .../utils/statistics/historgram_test.go | 32 ++ examples/benchtool/utils/statistics/misc.go | 102 ++++++ examples/benchtool/utils/util.go | 136 ++++++++ examples/benchtool/workloads/base.go | 65 ++++ examples/benchtool/workloads/rawkv/rawkv.go | 301 ++++++++++++++++++ 9 files changed, 996 insertions(+) create mode 100644 examples/benchtool/config/config.go create mode 100644 examples/benchtool/go.mod create mode 100644 examples/benchtool/main.go create mode 100644 examples/benchtool/utils/statistics/histogram.go create mode 100644 examples/benchtool/utils/statistics/historgram_test.go create mode 100644 examples/benchtool/utils/statistics/misc.go create mode 100644 examples/benchtool/utils/util.go create mode 100644 examples/benchtool/workloads/base.go create mode 100644 examples/benchtool/workloads/rawkv/rawkv.go diff --git a/examples/benchtool/config/config.go b/examples/benchtool/config/config.go new file mode 100644 index 0000000000..8beb1f39d1 --- /dev/null +++ b/examples/benchtool/config/config.go @@ -0,0 +1,102 @@ +// Copyright 2024 TiKV Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "strconv" + "time" + + "github.com/spf13/cobra" + "github.com/tikv/client-go/v2/config" +) + +type GlobalConfig struct { + hosts []string + port int + StatusPort int + + Username string + Password string + DbName string + + Threads int + TotalTime time.Duration + TotalCount int + DropData bool + IgnoreError bool + OutputInterval time.Duration + Silence bool + OutputStyle string + + Targets []string + Security config.Security +} + +func (c *GlobalConfig) parsePdAddrs() { + if len(c.hosts) == 0 { + return + } + targets := make([]string, 0, len(c.hosts)) + for _, host := range c.hosts { + targets = append(targets, host+":"+strconv.Itoa(c.port)) + } + c.Targets = targets +} + +type CommandLineParser struct { + command *cobra.Command + config *GlobalConfig +} + +func NewCommandLineParser() *CommandLineParser { + return &CommandLineParser{} +} + +func (p *CommandLineParser) Initialize() { + var globalCfg = &GlobalConfig{} + var rootCmd = &cobra.Command{ + Use: "bench-tool", + Short: "Benchmark tikv with different workloads", + } + rootCmd.PersistentFlags().StringSliceVarP(&globalCfg.hosts, "host", "H", []string{"127.0.0.1"}, "PD host") + rootCmd.PersistentFlags().IntVarP(&globalCfg.port, "port", "P", 4000, "PD port") + rootCmd.PersistentFlags().IntVarP(&globalCfg.StatusPort, "statusPort", "S", 10080, "PD status port") + + rootCmd.PersistentFlags().StringVarP(&globalCfg.Username, "username", "U", "root", "Database user") + rootCmd.PersistentFlags().StringVarP(&globalCfg.Password, "password", "p", "", "Password for user") + rootCmd.PersistentFlags().StringVarP(&globalCfg.DbName, "db", "D", "test", "Database name") + + rootCmd.PersistentFlags().IntVarP(&globalCfg.Threads, "threads", "T", 1, "Thread concurrency") + rootCmd.PersistentFlags().DurationVar(&globalCfg.TotalTime, "time", 1<<63-1, "Total execution time") + rootCmd.PersistentFlags().IntVar(&globalCfg.TotalCount, "count", 0, "Total execution count, 0 means infinite") + rootCmd.PersistentFlags().BoolVar(&globalCfg.DropData, "dropdata", false, "Cleanup data before prepare") + rootCmd.PersistentFlags().BoolVar(&globalCfg.IgnoreError, "ignore-error", false, "Ignore error when running workload") + rootCmd.PersistentFlags().BoolVar(&globalCfg.Silence, "silence", false, "Don't print error when running workload") + rootCmd.PersistentFlags().DurationVar(&globalCfg.OutputInterval, "interval", 10*time.Second, "Output interval time") + rootCmd.PersistentFlags().StringVar(&globalCfg.OutputStyle, "output", "plain", "output style, valid values can be { plain | table | json }") + + cobra.EnablePrefixMatching = true + + p.command = rootCmd + p.config = globalCfg +} + +func (p *CommandLineParser) GetConfig() *GlobalConfig { + return p.config +} + +func (p *CommandLineParser) GetCommand() *cobra.Command { + return p.command +} diff --git a/examples/benchtool/go.mod b/examples/benchtool/go.mod new file mode 100644 index 0000000000..6cba93766f --- /dev/null +++ b/examples/benchtool/go.mod @@ -0,0 +1,58 @@ +module benchtool + +go 1.21.0 + +require ( + github.com/HdrHistogram/hdrhistogram-go v1.1.2 + github.com/olekukonko/tablewriter v0.0.5 + github.com/spf13/cobra v1.8.0 + github.com/tikv/client-go/v2 v2.0.7 +) + +require ( + github.com/benbjohnson/clock v1.3.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-systemd/v22 v22.3.2 // indirect + github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect + github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 // indirect + github.com/elastic/gosigar v0.14.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/btree v1.1.2 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c // indirect + github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c // indirect + github.com/pingcap/kvproto v0.0.0-20230403051650-e166ae588106 // indirect + github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.39.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/tiancaiamao/gp v0.0.0-20221230034425-4025bc8a4d4a // indirect + github.com/tikv/pd/client v0.0.0-20230329114254-1948c247c2b1 // indirect + github.com/twmb/murmur3 v1.1.3 // indirect + go.etcd.io/etcd/api/v3 v3.5.2 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.2 // indirect + go.etcd.io/etcd/client/v3 v3.5.2 // indirect + go.uber.org/atomic v1.10.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + go.uber.org/zap v1.24.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633 // indirect + google.golang.org/grpc v1.54.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect +) diff --git a/examples/benchtool/main.go b/examples/benchtool/main.go new file mode 100644 index 0000000000..180ff00971 --- /dev/null +++ b/examples/benchtool/main.go @@ -0,0 +1,71 @@ +// Copyright 2024 TiKV Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "benchtool/config" + "benchtool/workloads" + "benchtool/workloads/rawkv" + "context" + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "github.com/spf13/cobra" +) + +func main() { + cobra.EnablePrefixMatching = true + + commandLineParser := config.NewCommandLineParser() + commandLineParser.Initialize() + + // register all workloads + // TODO: add more workloads + rawkv.Register(commandLineParser) + + var cancel context.CancelFunc + workloads.GlobalContext, cancel = context.WithCancel(context.Background()) + + sc := make(chan os.Signal, 1) + signal.Notify(sc, + syscall.SIGHUP, + syscall.SIGINT, + syscall.SIGTERM, + syscall.SIGQUIT) + + closeDone := make(chan struct{}, 1) + go func() { + sig := <-sc + fmt.Printf("\nGot signal [%v] to exit.\n", sig) + cancel() + + select { + case <-sc: + // send signal again, return directly + fmt.Printf("\nGot signal [%v] again to exit.\n", sig) + os.Exit(1) + case <-time.After(10 * time.Second): + fmt.Print("\nWait 10s for closed, force exit\n") + os.Exit(1) + case <-closeDone: + return + } + }() + commandLineParser.GetCommand().Execute() + cancel() +} diff --git a/examples/benchtool/utils/statistics/histogram.go b/examples/benchtool/utils/statistics/histogram.go new file mode 100644 index 0000000000..e8c543c194 --- /dev/null +++ b/examples/benchtool/utils/statistics/histogram.go @@ -0,0 +1,129 @@ +// Copyright 2024 TiKV Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package statistics + +import ( + "fmt" + "sync" + "time" + + "benchtool/utils" + + "github.com/HdrHistogram/hdrhistogram-go" +) + +type RuntimeStatistics struct { + elapsed float64 + + // Operation statistics + sum float64 + count int64 + ops float64 + + // Execution time statistics + p50 float64 + p90 float64 + p95 float64 + p99 float64 + p999 float64 + p9999 float64 + avg float64 + min float64 + max float64 +} + +type PerfHistogram struct { + m sync.RWMutex + + startTime time.Time + sum int64 + *hdrhistogram.Histogram +} + +func NewPerfHistogram(minLat, maxLat time.Duration, sf int) *PerfHistogram { + return &PerfHistogram{Histogram: hdrhistogram.New(minLat.Nanoseconds(), maxLat.Nanoseconds(), sf), startTime: time.Now()} +} + +func (h *PerfHistogram) Record(rawLatency time.Duration) { + latency := rawLatency + low := time.Duration(h.LowestTrackableValue()) + high := time.Duration(h.HighestTrackableValue()) + if latency < low { + latency = low + } else if latency > high { + latency = high + } + + h.m.Lock() + err := h.RecordValue(latency.Nanoseconds()) + h.sum += rawLatency.Nanoseconds() + h.m.Unlock() + if err != nil { + panic(fmt.Sprintf(`recording value error: %s`, err)) + } +} + +func (h *PerfHistogram) Empty() bool { + h.m.Lock() + defer h.m.Unlock() + return h.TotalCount() == 0 +} + +func (h *PerfHistogram) Format() []string { + res := h.GetRuntimeStatistics() + + // Format: "Elapsed" - "Sum" - "Count" - "Ops" - "Avg" - "P50" - "P90" - "P95" - "P99" - "P999" - "P9999" - "Min" - "Max + return []string{ + utils.FloatToString(res.elapsed), + utils.FloatToString(res.sum), + utils.IntToString(res.count), + utils.FloatToString(res.ops * 60), + utils.FloatToString(res.avg), + utils.FloatToString(res.p50), + utils.FloatToString(res.p90), + utils.FloatToString(res.p95), + utils.FloatToString(res.p99), + utils.FloatToString(res.p999), + utils.FloatToString(res.p999), + utils.FloatToString(res.min), + utils.FloatToString(res.max), + } +} + +func (h *PerfHistogram) GetRuntimeStatistics() RuntimeStatistics { + h.m.RLock() + defer h.m.RUnlock() + sum := time.Duration(h.sum).Seconds() * 1000 + avg := time.Duration(h.Mean()).Seconds() * 1000 + elapsed := time.Since(h.startTime).Seconds() + count := h.TotalCount() + ops := float64(count) / elapsed + info := RuntimeStatistics{ + elapsed: elapsed, + sum: sum, + count: count, + ops: ops, + avg: avg, + p50: time.Duration(h.ValueAtQuantile(50)).Seconds() * 1000, + p90: time.Duration(h.ValueAtQuantile(90)).Seconds() * 1000, + p95: time.Duration(h.ValueAtQuantile(95)).Seconds() * 1000, + p99: time.Duration(h.ValueAtQuantile(99)).Seconds() * 1000, + p999: time.Duration(h.ValueAtQuantile(99.9)).Seconds() * 1000, + p9999: time.Duration(h.ValueAtQuantile(99.99)).Seconds() * 1000, + min: time.Duration(h.Min()).Seconds() * 1000, + max: time.Duration(h.Max()).Seconds() * 1000, + } + return info +} diff --git a/examples/benchtool/utils/statistics/historgram_test.go b/examples/benchtool/utils/statistics/historgram_test.go new file mode 100644 index 0000000000..5aca6e0776 --- /dev/null +++ b/examples/benchtool/utils/statistics/historgram_test.go @@ -0,0 +1,32 @@ +// Copyright 2024 TiKV Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package statistics + +import ( + "math/rand" + "testing" + "time" +) + +func TestHist(t *testing.T) { + h := NewPerfHistogram(1*time.Millisecond, 20*time.Minute, 1) + for i := 0; i < 10000; i++ { + n := rand.Intn(15020) + h.Record(time.Millisecond * time.Duration(n)) + } + h.Record(time.Minute * 9) + h.Record(time.Minute * 100) + t.Logf("%+v", h.Format()) +} diff --git a/examples/benchtool/utils/statistics/misc.go b/examples/benchtool/utils/statistics/misc.go new file mode 100644 index 0000000000..86ffdf8764 --- /dev/null +++ b/examples/benchtool/utils/statistics/misc.go @@ -0,0 +1,102 @@ +// Copyright 2024 TiKV Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package statistics + +import ( + "sync" + "time" +) + +const ( + defaultMinLatency = 1 * time.Millisecond + DefaultMaxLatency = 16 * time.Second +) + +type PerfProfile struct { + sync.RWMutex + + MinLatency time.Duration + MaxLatency time.Duration + SigFigs int + PeriodicalPerfHist map[string]*PerfHistogram + SummaryPerfHist map[string]*PerfHistogram +} + +func NewPerfProfile() *PerfProfile { + return &PerfProfile{ + MinLatency: defaultMinLatency, + MaxLatency: DefaultMaxLatency, + SigFigs: 1, + PeriodicalPerfHist: make(map[string]*PerfHistogram, 16), + SummaryPerfHist: make(map[string]*PerfHistogram, 16), + } +} + +func (p *PerfProfile) Record(op string, latency time.Duration) { + p.Lock() + defer p.Unlock() + + if _, ok := p.PeriodicalPerfHist[op]; !ok { + p.PeriodicalPerfHist[op] = NewPerfHistogram(p.MinLatency, p.MaxLatency, p.SigFigs) + } + p.PeriodicalPerfHist[op].Record(latency) + if _, ok := p.SummaryPerfHist[op]; !ok { + p.SummaryPerfHist[op] = NewPerfHistogram(p.MinLatency, p.MaxLatency, p.SigFigs) + } + p.SummaryPerfHist[op].Record(latency) +} + +func (p *PerfProfile) Get(op string, sum bool) *PerfHistogram { + histMap := p.PeriodicalPerfHist + if sum { + histMap = p.SummaryPerfHist + } + + p.RLock() + hist, ok := histMap[op] + p.RUnlock() + if !ok { + perfHist := NewPerfHistogram(p.MinLatency, p.MaxLatency, p.SigFigs) + p.Lock() + histMap[op] = perfHist + hist = histMap[op] + p.Unlock() + } + return hist +} + +func (p *PerfProfile) TakePeriodHist() map[string]*PerfHistogram { + p.Lock() + defer p.Unlock() + periodicalHist := make(map[string]*PerfHistogram, len(p.PeriodicalPerfHist)) + swapOutHist := p.PeriodicalPerfHist + p.PeriodicalPerfHist = periodicalHist + return swapOutHist +} + +// Prints the PerfProfile. +func (p *PerfProfile) PrintFmt(ifSummaryReport bool, outputStyle string, outputFunc func(string, string, map[string]*PerfHistogram)) { + if ifSummaryReport { + p.RLock() + defer p.RUnlock() + outputFunc(outputStyle, "[Summary] ", p.SummaryPerfHist) + return + } + // Clear current PerfHistogram and print current PerfHistogram. + periodicalHist := p.TakePeriodHist() + p.RLock() + defer p.RUnlock() + outputFunc(outputStyle, "[Current] ", periodicalHist) +} diff --git a/examples/benchtool/utils/util.go b/examples/benchtool/utils/util.go new file mode 100644 index 0000000000..3a3b5ee9fe --- /dev/null +++ b/examples/benchtool/utils/util.go @@ -0,0 +1,136 @@ +package utils + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "strconv" + "strings" + + "github.com/olekukonko/tablewriter" +) + +const ( + DEFAULT_PRECISION = 2 +) + +const ( + OutputStylePlain = "plain" + OutputStyleTable = "table" + OutputStyleJson = "json" +) + +type ReadWriteRatio struct { + Ratio string + readPercent int + writePercent int +} + +func NewReadWriteRatio(ratio string) *ReadWriteRatio { + return &ReadWriteRatio{Ratio: ratio, readPercent: -1, writePercent: -1} +} + +func (r *ReadWriteRatio) ParseRatio() error { + if r.Ratio == "" { + return fmt.Errorf("empty read-write-ratio") + } + ratios := strings.Split(r.Ratio, ":") + if len(ratios) == 2 { + readRatio := 0 + writeRatio := 0 + + readRatio, _ = strconv.Atoi(ratios[0]) + writeRatio, _ = strconv.Atoi(ratios[1]) + + sumRatio := readRatio + writeRatio + + r.readPercent = readRatio * 100 / sumRatio + r.writePercent = 100 - r.readPercent + } else { + return fmt.Errorf("invalid read-write-ratio format") + } + return nil +} + +func (r *ReadWriteRatio) GetPercent(choice string) int { + if r.Ratio == "" { + return 0 + } + // Not parsed yet. + if r.readPercent == -1 || r.writePercent == -1 { + if r.ParseRatio() != nil { + return 0 + } + } + if choice == "read" { + return r.readPercent + } else if choice == "write" { + return r.writePercent + } + return 0 +} + +func FloatToString(num float64) string { + return strconv.FormatFloat(num, 'f', DEFAULT_PRECISION, 64) +} + +func IntToString(num int64) string { + return strconv.FormatInt(num, 10) +} + +func RenderString(format string, headers []string, values [][]string) { + if len(values) == 0 { + return + } + if len(headers) == 0 { + for _, value := range values { + args := make([]interface{}, len(value)) + for i, v := range value { + args[i] = v + } + fmt.Printf(format, args...) + } + return + } + + buf := new(bytes.Buffer) + for _, value := range values { + args := make([]string, len(headers)-2) + for i, header := range headers[2:] { + args[i] = header + ": " + value[i+2] + } + buf.WriteString(fmt.Sprintf(format, value[0], value[1], strings.Join(args, ", "))) + } + fmt.Print(buf.String()) +} + +func RenderTable(headers []string, values [][]string) { + if len(values) == 0 { + return + } + tb := tablewriter.NewWriter(os.Stdout) + tb.SetHeader(headers) + tb.AppendBulk(values) + tb.Render() +} + +func RenderJson(headers []string, values [][]string) { + if len(values) == 0 { + return + } + data := make([]map[string]string, 0, len(values)) + for _, value := range values { + line := make(map[string]string, 0) + for i, header := range headers { + line[header] = value[i] + } + data = append(data, line) + } + outStr, err := json.Marshal(data) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(string(outStr)) +} diff --git a/examples/benchtool/workloads/base.go b/examples/benchtool/workloads/base.go new file mode 100644 index 0000000000..ab644b32d5 --- /dev/null +++ b/examples/benchtool/workloads/base.go @@ -0,0 +1,65 @@ +package workloads + +import ( + "context" + "database/sql" + "fmt" + "time" +) + +const ( + DefaultDriver = "mysql" +) + +// WorkloadInterface is the interface for running customized workload +type WorkloadInterface interface { + Name() string + InitThread(ctx context.Context, threadID int) error + CleanupThread(ctx context.Context, threadID int) + Prepare(ctx context.Context, threadID int) error + CheckPrepare(ctx context.Context, threadID int) error + Run(ctx context.Context, threadID int) error + Cleanup(ctx context.Context, threadID int) error + Check(ctx context.Context, threadID int) error + OutputStats(ifSummaryReport bool) + DBName() string +} + +var GlobalContext context.Context +var GlobalDB *sql.DB // Maybe useless, as the tikv.Client is the only enter to access the TiKV. + +func DispatchExecution(timeoutCtx context.Context, w WorkloadInterface, action string, count int, threadIdx int, silence bool, ignoreError bool) error { + if err := w.InitThread(context.Background(), threadIdx); err != nil { + return err + } + defer w.CleanupThread(timeoutCtx, threadIdx) + + switch action { + case "prepare": + return w.Prepare(timeoutCtx, threadIdx) + case "cleanup": + return w.Cleanup(timeoutCtx, threadIdx) + case "check": + return w.Check(timeoutCtx, threadIdx) + } + + if count > 0 { + for i := 0; i < count || count <= 0; i++ { + err := w.Run(timeoutCtx, threadIdx) + if err != nil { + if !silence { + fmt.Printf("[%s] execute %s failed, err %v\n", time.Now().Format("2006-01-02 15:04:05"), action, err) + } + if !ignoreError { + return err + } + } + select { + case <-timeoutCtx.Done(): + return nil + default: + } + } + } + return nil +} diff --git a/examples/benchtool/workloads/rawkv/rawkv.go b/examples/benchtool/workloads/rawkv/rawkv.go new file mode 100644 index 0000000000..09c3241b43 --- /dev/null +++ b/examples/benchtool/workloads/rawkv/rawkv.go @@ -0,0 +1,301 @@ +package rawkv + +import ( + "benchtool/config" + "benchtool/utils" + "benchtool/utils/statistics" + "benchtool/workloads" + "context" + "fmt" + "sort" + "strings" + "sync" + "time" + + "github.com/spf13/cobra" + "github.com/tikv/client-go/v2/rawkv" +) + +const ( + WorkloadImplName = "rawkv" + + WorkloadTypePut = "put" + WorkloadTypeGet = "get" +) + +// Format: "Elapsed" - "Sum" - "Count" - "Ops" - "Avg" - "P50" - "P90" - "P95" - "P99" - "P999" - "P9999" - "Min" - "Max +var workloadFormat = []string{"Prefix", "Operation", "Takes(s)", "Count", "Ops", "Avg(ms)", "50th(ms)", "90th(ms)", "95th(ms)", "99th(ms)", "99.9th(ms)", "99.99th(ms)", "Min(ms)", "Max(ms)"} + +type RawKVConfig struct { + keySize int + valueSize int + + prepareRetryCount int + prepareRetryInterval time.Duration + randomize bool + readWriteRatio *utils.ReadWriteRatio + + global *config.GlobalConfig +} + +func (c *RawKVConfig) Validate() error { + if c.keySize <= 0 || c.valueSize <= 0 { + return fmt.Errorf("key size or value size must be greater than 0") + } + if err := c.readWriteRatio.ParseRatio(); err != nil { + return fmt.Errorf("parse read-write-ratio failed: %v", err) + } + return nil +} + +// Register registers the workload to the command line parser +func Register(command *config.CommandLineParser) *RawKVConfig { + if command == nil { + return nil + } + rawKVConfig := &RawKVConfig{ + global: command.GetConfig(), + readWriteRatio: utils.NewReadWriteRatio("1:1"), + } + + cmd := &cobra.Command{ + Use: WorkloadImplName, + } + cmd.PersistentFlags().IntVar(&rawKVConfig.keySize, "key-size", 1, "Size of key in bytes") + cmd.PersistentFlags().IntVar(&rawKVConfig.valueSize, "value-size", 1, "Size of value in bytes") + cmd.PersistentFlags().BoolVar(&rawKVConfig.randomize, "random", false, "Whether to randomize each value") + cmd.PersistentFlags().StringVar(&rawKVConfig.readWriteRatio.Ratio, "read-write-ratio", "1:1", "Read write ratio") + + var cmdPrepare = &cobra.Command{ + Use: "prepare", + Short: "Prepare data for RawKV workload", + Run: func(cmd *cobra.Command, _ []string) { + execRawKV("prepare") + }, + } + cmdPrepare.PersistentFlags().IntVar(&rawKVConfig.prepareRetryCount, "retry-count", 50, "Retry count when errors occur") + cmdPrepare.PersistentFlags().DurationVar(&rawKVConfig.prepareRetryInterval, "retry-interval", 10*time.Millisecond, "The interval for each retry") + + var cmdRun = &cobra.Command{ + Use: "run", + Short: "Run workload", + Run: func(cmd *cobra.Command, _ []string) { + execRawKV("run") + }, + } + + var cmdCleanup = &cobra.Command{ + Use: "cleanup", + Short: "Cleanup data for the workload", + Run: func(cmd *cobra.Command, _ []string) { + execRawKV("cleanup") + }, + } + + var cmdCheck = &cobra.Command{ + Use: "check", + Short: "Check data consistency for the workload", + Run: func(cmd *cobra.Command, _ []string) { + execRawKV("check") + }, + } + + cmd.AddCommand(cmdRun, cmdPrepare, cmdCleanup, cmdCheck) + + command.GetCommand().AddCommand(cmd) + + return rawKVConfig +} + +func getRawKvConfig(ctx context.Context) *RawKVConfig { + c := ctx.Value(WorkloadImplName).(*RawKVConfig) + return c +} + +type WorkloadImpl struct { + cfg *RawKVConfig + clients []*rawkv.Client + + wait sync.WaitGroup + + stats *statistics.PerfProfile +} + +func NewRawKVWorkload(cfg *RawKVConfig) *WorkloadImpl { + return &WorkloadImpl{ + cfg: cfg, + stats: statistics.NewPerfProfile(), + } +} + +func (w *WorkloadImpl) Name() string { + return WorkloadImplName +} + +func (w *WorkloadImpl) isValid() bool { + return w.cfg != nil && w.cfg.global != nil && len(w.clients) > 0 +} + +func (w *WorkloadImpl) isValidThread(threadID int) bool { + return w.isValid() && threadID < len(w.clients) +} + +// InitThread implements WorkloadInterface +func (w *WorkloadImpl) InitThread(ctx context.Context, threadID int) error { + // new RawKVClient + if len(w.clients) == 0 && threadID == 0 { + w.clients = make([]*rawkv.Client, 0, w.cfg.global.Threads) + client, err := rawkv.NewClient(workloads.GlobalContext, w.cfg.global.Targets, w.cfg.global.Security) + if err != nil { + return err + } + w.clients = append(w.clients, client) + + w.wait.Done() + w.wait.Wait() + } + return nil +} + +// CleanupThread implements WorkloadInterface +func (w *WorkloadImpl) CleanupThread(ctx context.Context, threadID int) { + if w.isValidThread(threadID) { + client := w.clients[threadID] + if client != nil { + client.Close() + } + } +} + +// Prepare implements WorkloadInterface +func (w *WorkloadImpl) Prepare(ctx context.Context, threadID int) error { + if !w.isValidThread(threadID) { + return fmt.Errorf("no valid RawKV clients") + } + + // return prepareWorkloadImpl(ctx, w, w.cfg.Threads, w.cfg.Warehouses, threadID) + // TODO: add prepare stage + return nil +} + +// CheckPrepare implements WorkloadInterface +func (w *WorkloadImpl) CheckPrepare(ctx context.Context, threadID int) error { + return nil +} + +func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { + if !w.isValidThread(threadID) { + return fmt.Errorf("no valid RawKV clients") + } + client := w.clients[threadID] + + for i := 0; i < 10; i++ { + start := time.Now() + // Put + client.Put(ctx, []byte("Company"), []byte("PingCAP")) + w.stats.Record(WorkloadTypePut, time.Since(start)) + // Get + start = time.Now() + client.Get(ctx, []byte("Company")) + w.stats.Record(WorkloadTypeGet, time.Since(start)) + } + return nil +} + +// Check implements WorkloadInterface +func (w *WorkloadImpl) Check(ctx context.Context, threadID int) error { + return nil +} + +// Cleanup implements WorkloadInterface +func (w *WorkloadImpl) Cleanup(ctx context.Context, threadID int) error { + if !w.isValidThread(threadID) { + return fmt.Errorf("no valid RawKV clients") + } + if threadID == 0 { + client := w.clients[threadID] + client.DeleteRange(ctx, []byte(""), []byte("")) // delete all keys + } + return nil +} + +func outputFunc(outputStyle string, prefix string, perfHist map[string]*statistics.PerfHistogram) { + keys := make([]string, 0, len(perfHist)) + for k := range perfHist { + keys = append(keys, k) + } + sort.Strings(keys) + + lines := [][]string{} + for _, op := range keys { + hist := perfHist[op] + if !hist.Empty() { + op = strings.ToUpper(op) + line := []string{prefix, op} + line = append(line, hist.Format()...) + lines = append(lines, line) + } + } + switch outputStyle { + case utils.OutputStylePlain: + utils.RenderString("%s%-6s - %s\n", workloadFormat, lines) + case utils.OutputStyleTable: + utils.RenderTable(workloadFormat, lines) + case utils.OutputStyleJson: + utils.RenderJson(workloadFormat, lines) + } +} + +func (w *WorkloadImpl) OutputStats(ifSummaryReport bool) { + w.stats.PrintFmt(ifSummaryReport, w.cfg.global.OutputStyle, outputFunc) +} + +// DBName returns the name of test db. +func (w *WorkloadImpl) DBName() string { + return w.cfg.global.DbName +} + +func (w *WorkloadImpl) Execute(cmd string) { + w.wait.Add(w.cfg.global.Threads) + + ctx, cancel := context.WithCancel(workloads.GlobalContext) + ch := make(chan struct{}, 1) + go func() { + ticker := time.NewTicker(w.cfg.global.OutputInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + ch <- struct{}{} + return + case <-ticker.C: + w.OutputStats(false) + } + } + }() + + count := w.cfg.global.TotalCount / w.cfg.global.Threads + for i := 0; i < w.cfg.global.Threads; i++ { + go func(index int) { + defer w.wait.Done() + if err := workloads.DispatchExecution(ctx, w, cmd, count, index, w.cfg.global.Silence, w.cfg.global.IgnoreError); err != nil { + fmt.Printf("[%s] execute %s failed, err %v\n", time.Now().Format("2006-01-02 15:04:05"), cmd, err) + return + } + }(i) + } + + w.wait.Wait() + cancel() + <-ch +} + +func execRawKV(cmd string) { + if cmd == "" { + return + } + rawKVConfig := getRawKvConfig(workloads.GlobalContext) + workload := NewRawKVWorkload(rawKVConfig) + workload.Execute(cmd) +} From e552ec04f46170588d2bdcbe74061128afe80824 Mon Sep 17 00:00:00 2001 From: lucasliang Date: Tue, 30 Apr 2024 16:54:37 +0800 Subject: [PATCH 02/30] Format and fix errors. Signed-off-by: lucasliang --- examples/benchtool/config/config.go | 10 ++++- examples/benchtool/workloads/rawkv/rawkv.go | 41 +++++++++++++-------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/examples/benchtool/config/config.go b/examples/benchtool/config/config.go index 8beb1f39d1..71bf149f72 100644 --- a/examples/benchtool/config/config.go +++ b/examples/benchtool/config/config.go @@ -15,6 +15,7 @@ package config import ( + "fmt" "strconv" "time" @@ -44,7 +45,7 @@ type GlobalConfig struct { Security config.Security } -func (c *GlobalConfig) parsePdAddrs() { +func (c *GlobalConfig) ParsePdAddrs() { if len(c.hosts) == 0 { return } @@ -55,6 +56,11 @@ func (c *GlobalConfig) parsePdAddrs() { c.Targets = targets } +func (c *GlobalConfig) Format() string { + return fmt.Sprintf("Hosts: %v, Port: %d, StatusPort: %d, Username: %s, Password: %s, DbName: %s, Threads: %d, TotalTime: %v, TotalCount: %d, DropData: %t, IgnoreError: %t, OutputInterval: %v, Silence: %t, OutputStyle: %s", + c.hosts, c.port, c.StatusPort, c.Username, c.Password, c.DbName, c.Threads, c.TotalTime, c.TotalCount, c.DropData, c.IgnoreError, c.OutputInterval, c.Silence, c.OutputStyle) +} + type CommandLineParser struct { command *cobra.Command config *GlobalConfig @@ -73,7 +79,7 @@ func (p *CommandLineParser) Initialize() { rootCmd.PersistentFlags().StringSliceVarP(&globalCfg.hosts, "host", "H", []string{"127.0.0.1"}, "PD host") rootCmd.PersistentFlags().IntVarP(&globalCfg.port, "port", "P", 4000, "PD port") rootCmd.PersistentFlags().IntVarP(&globalCfg.StatusPort, "statusPort", "S", 10080, "PD status port") - + // Maybe unnecessary rootCmd.PersistentFlags().StringVarP(&globalCfg.Username, "username", "U", "root", "Database user") rootCmd.PersistentFlags().StringVarP(&globalCfg.Password, "password", "p", "", "Password for user") rootCmd.PersistentFlags().StringVarP(&globalCfg.DbName, "db", "D", "test", "Database name") diff --git a/examples/benchtool/workloads/rawkv/rawkv.go b/examples/benchtool/workloads/rawkv/rawkv.go index 09c3241b43..d07ab8f0fc 100644 --- a/examples/benchtool/workloads/rawkv/rawkv.go +++ b/examples/benchtool/workloads/rawkv/rawkv.go @@ -60,6 +60,9 @@ func Register(command *config.CommandLineParser) *RawKVConfig { cmd := &cobra.Command{ Use: WorkloadImplName, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + workloads.GlobalContext = context.WithValue(workloads.GlobalContext, WorkloadImplName, rawKVConfig) + }, } cmd.PersistentFlags().IntVar(&rawKVConfig.keySize, "key-size", 1, "Size of key in bytes") cmd.PersistentFlags().IntVar(&rawKVConfig.valueSize, "value-size", 1, "Size of value in bytes") @@ -121,11 +124,24 @@ type WorkloadImpl struct { stats *statistics.PerfProfile } -func NewRawKVWorkload(cfg *RawKVConfig) *WorkloadImpl { - return &WorkloadImpl{ +func NewRawKVWorkload(cfg *RawKVConfig) (*WorkloadImpl, error) { + if err := cfg.Validate(); err != nil { + return nil, err + } + w := &WorkloadImpl{ cfg: cfg, stats: statistics.NewPerfProfile(), } + + w.clients = make([]*rawkv.Client, 0, w.cfg.global.Threads) + for i := 0; i < w.cfg.global.Threads; i++ { + client, err := rawkv.NewClient(workloads.GlobalContext, w.cfg.global.Targets, w.cfg.global.Security) + if err != nil { + return nil, err + } + w.clients = append(w.clients, client) + } + return w, nil } func (w *WorkloadImpl) Name() string { @@ -142,18 +158,7 @@ func (w *WorkloadImpl) isValidThread(threadID int) bool { // InitThread implements WorkloadInterface func (w *WorkloadImpl) InitThread(ctx context.Context, threadID int) error { - // new RawKVClient - if len(w.clients) == 0 && threadID == 0 { - w.clients = make([]*rawkv.Client, 0, w.cfg.global.Threads) - client, err := rawkv.NewClient(workloads.GlobalContext, w.cfg.global.Targets, w.cfg.global.Security) - if err != nil { - return err - } - w.clients = append(w.clients, client) - - w.wait.Done() - w.wait.Wait() - } + // Nothing to do return nil } @@ -296,6 +301,12 @@ func execRawKV(cmd string) { return } rawKVConfig := getRawKvConfig(workloads.GlobalContext) - workload := NewRawKVWorkload(rawKVConfig) + + var workload *WorkloadImpl + var err error + if workload, err = NewRawKVWorkload(rawKVConfig); err != nil { + fmt.Printf("create RawKV workload failed: %v\n", err) + return + } workload.Execute(cmd) } From 70b6d84a76b79b04c9bf73c1f4f09d1116a39007 Mon Sep 17 00:00:00 2001 From: lucasliang Date: Mon, 6 May 2024 09:43:51 +0800 Subject: [PATCH 03/30] Polish codes on rawkv part. Signed-off-by: lucasliang --- examples/benchtool/workloads/rawkv/rawkv.go | 35 +++++++++++++++------ 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/examples/benchtool/workloads/rawkv/rawkv.go b/examples/benchtool/workloads/rawkv/rawkv.go index d07ab8f0fc..dd45d81d06 100644 --- a/examples/benchtool/workloads/rawkv/rawkv.go +++ b/examples/benchtool/workloads/rawkv/rawkv.go @@ -7,6 +7,7 @@ import ( "benchtool/workloads" "context" "fmt" + "math/rand" "sort" "strings" "sync" @@ -21,6 +22,11 @@ const ( WorkloadTypePut = "put" WorkloadTypeGet = "get" + + WorkloadDefaultKey = "rawkv_key" + WorkloadDefaultValue = "rawkv_value" + + WorkloadMaxInt64 = 1<<63 - 1 ) // Format: "Elapsed" - "Sum" - "Count" - "Ops" - "Avg" - "P50" - "P90" - "P95" - "P99" - "P999" - "P9999" - "Min" - "Max @@ -34,6 +40,7 @@ type RawKVConfig struct { prepareRetryInterval time.Duration randomize bool readWriteRatio *utils.ReadWriteRatio + commandType string global *config.GlobalConfig } @@ -64,6 +71,7 @@ func Register(command *config.CommandLineParser) *RawKVConfig { workloads.GlobalContext = context.WithValue(workloads.GlobalContext, WorkloadImplName, rawKVConfig) }, } + cmd.PersistentFlags().StringVar(&rawKVConfig.commandType, "cmd", "put", "Type of command to execute (put/get)") cmd.PersistentFlags().IntVar(&rawKVConfig.keySize, "key-size", 1, "Size of key in bytes") cmd.PersistentFlags().IntVar(&rawKVConfig.valueSize, "value-size", 1, "Size of value in bytes") cmd.PersistentFlags().BoolVar(&rawKVConfig.randomize, "random", false, "Whether to randomize each value") @@ -115,6 +123,10 @@ func getRawKvConfig(ctx context.Context) *RawKVConfig { return c } +func genRandomStr(prefix string, keySize int) string { + return fmt.Sprintf("%s_%0*d", prefix, keySize, rand.Intn(WorkloadMaxInt64)) +} + type WorkloadImpl struct { cfg *RawKVConfig clients []*rawkv.Client @@ -193,17 +205,22 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { return fmt.Errorf("no valid RawKV clients") } client := w.clients[threadID] + key := WorkloadDefaultKey + val := WorkloadDefaultValue + if w.cfg.randomize { + key = genRandomStr(WorkloadDefaultKey, w.cfg.keySize) + val = genRandomStr(WorkloadDefaultValue, w.cfg.valueSize) + } - for i := 0; i < 10; i++ { - start := time.Now() - // Put - client.Put(ctx, []byte("Company"), []byte("PingCAP")) - w.stats.Record(WorkloadTypePut, time.Since(start)) - // Get - start = time.Now() - client.Get(ctx, []byte("Company")) - w.stats.Record(WorkloadTypeGet, time.Since(start)) + start := time.Now() + switch w.cfg.commandType { + case WorkloadTypePut: + client.Put(ctx, []byte(key), []byte(val)) + case WorkloadTypeGet: + client.Get(ctx, []byte(key)) + // TODO: add BatchGet and BatchPut } + w.stats.Record(w.cfg.commandType, time.Since(start)) return nil } From b312db71619db2959d26a8e6678fe63a3d989b0f Mon Sep 17 00:00:00 2001 From: lucasliang Date: Mon, 6 May 2024 12:25:19 +0800 Subject: [PATCH 04/30] Add timeout. Signed-off-by: lucasliang --- examples/benchtool/workloads/rawkv/rawkv.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/benchtool/workloads/rawkv/rawkv.go b/examples/benchtool/workloads/rawkv/rawkv.go index dd45d81d06..53eb300a09 100644 --- a/examples/benchtool/workloads/rawkv/rawkv.go +++ b/examples/benchtool/workloads/rawkv/rawkv.go @@ -325,5 +325,12 @@ func execRawKV(cmd string) { fmt.Printf("create RawKV workload failed: %v\n", err) return } + + timeoutCtx, cancel := context.WithTimeout(workloads.GlobalContext, rawKVConfig.global.TotalTime) + workloads.GlobalContext = timeoutCtx + defer cancel() + workload.Execute(cmd) + fmt.Println("RawKV workload finished") + workload.OutputStats(true) } From f87391ba9925255a2a77cd431a33430a0ebbd5dc Mon Sep 17 00:00:00 2001 From: lucasliang Date: Mon, 6 May 2024 15:49:31 +0800 Subject: [PATCH 05/30] Polish codes and the output format of RawKV. Signed-off-by: lucasliang --- examples/benchtool/config/config.go | 5 ++- .../benchtool/utils/statistics/histogram.go | 37 ++++++++++++++++++- examples/benchtool/utils/util.go | 4 +- examples/benchtool/workloads/rawkv/rawkv.go | 36 +----------------- 4 files changed, 42 insertions(+), 40 deletions(-) diff --git a/examples/benchtool/config/config.go b/examples/benchtool/config/config.go index 71bf149f72..e64df911d7 100644 --- a/examples/benchtool/config/config.go +++ b/examples/benchtool/config/config.go @@ -45,15 +45,16 @@ type GlobalConfig struct { Security config.Security } -func (c *GlobalConfig) ParsePdAddrs() { +func (c *GlobalConfig) ParsePdAddrs() error { if len(c.hosts) == 0 { - return + return fmt.Errorf("PD address is empty") } targets := make([]string, 0, len(c.hosts)) for _, host := range c.hosts { targets = append(targets, host+":"+strconv.Itoa(c.port)) } c.Targets = targets + return nil } func (c *GlobalConfig) Format() string { diff --git a/examples/benchtool/utils/statistics/histogram.go b/examples/benchtool/utils/statistics/histogram.go index e8c543c194..ca67d6fe13 100644 --- a/examples/benchtool/utils/statistics/histogram.go +++ b/examples/benchtool/utils/statistics/histogram.go @@ -16,6 +16,8 @@ package statistics import ( "fmt" + "sort" + "strings" "sync" "time" @@ -24,6 +26,9 @@ import ( "github.com/HdrHistogram/hdrhistogram-go" ) +// Format: "Elapsed" - "Sum" - "Count" - "Ops" - "Avg" - "P50" - "P90" - "P95" - "P99" - "P999" - "P9999" - "Min" - "Max +var WorkloadFormat = []string{"Elapsed(s)", "Sum", "Count", "Ops", "Avg(ms)", "50th(ms)", "90th(ms)", "95th(ms)", "99th(ms)", "99.9th(ms)", "99.99th(ms)", "Min(ms)", "Max(ms)"} + type RuntimeStatistics struct { elapsed float64 @@ -84,7 +89,7 @@ func (h *PerfHistogram) Empty() bool { func (h *PerfHistogram) Format() []string { res := h.GetRuntimeStatistics() - // Format: "Elapsed" - "Sum" - "Count" - "Ops" - "Avg" - "P50" - "P90" - "P95" - "P99" - "P999" - "P9999" - "Min" - "Max + // Format: "Elapsed(s)" - "Sum" - "Count" - "Ops" - "Avg" - "P50" - "P90" - "P95" - "P99" - "P999" - "P9999" - "Min" - "Max return []string{ utils.FloatToString(res.elapsed), utils.FloatToString(res.sum), @@ -96,7 +101,7 @@ func (h *PerfHistogram) Format() []string { utils.FloatToString(res.p95), utils.FloatToString(res.p99), utils.FloatToString(res.p999), - utils.FloatToString(res.p999), + utils.FloatToString(res.p9999), utils.FloatToString(res.min), utils.FloatToString(res.max), } @@ -127,3 +132,31 @@ func (h *PerfHistogram) GetRuntimeStatistics() RuntimeStatistics { } return info } + +func HistogramOutputFunc(outputStyle string, prefix string, perfHist map[string]*PerfHistogram) { + keys := make([]string, 0, len(perfHist)) + for k := range perfHist { + keys = append(keys, k) + } + sort.Strings(keys) + + lines := [][]string{} + for _, op := range keys { + hist := perfHist[op] + if !hist.Empty() { + op = strings.ToUpper(op) + line := []string{prefix, op} + line = append(line, hist.Format()...) + fmt.Println(line) + lines = append(lines, line) + } + } + switch outputStyle { + case utils.OutputStylePlain: + utils.RenderString("%s%-6s - %s\n", WorkloadFormat, lines) + case utils.OutputStyleTable: + utils.RenderTable(WorkloadFormat, lines) + case utils.OutputStyleJson: + utils.RenderJson(WorkloadFormat, lines) + } +} diff --git a/examples/benchtool/utils/util.go b/examples/benchtool/utils/util.go index 3a3b5ee9fe..5ca4263ec3 100644 --- a/examples/benchtool/utils/util.go +++ b/examples/benchtool/utils/util.go @@ -96,8 +96,8 @@ func RenderString(format string, headers []string, values [][]string) { buf := new(bytes.Buffer) for _, value := range values { - args := make([]string, len(headers)-2) - for i, header := range headers[2:] { + args := make([]string, len(headers)) + for i, header := range headers { args[i] = header + ": " + value[i+2] } buf.WriteString(fmt.Sprintf(format, value[0], value[1], strings.Join(args, ", "))) diff --git a/examples/benchtool/workloads/rawkv/rawkv.go b/examples/benchtool/workloads/rawkv/rawkv.go index 53eb300a09..f006bef90d 100644 --- a/examples/benchtool/workloads/rawkv/rawkv.go +++ b/examples/benchtool/workloads/rawkv/rawkv.go @@ -8,8 +8,6 @@ import ( "context" "fmt" "math/rand" - "sort" - "strings" "sync" "time" @@ -29,9 +27,6 @@ const ( WorkloadMaxInt64 = 1<<63 - 1 ) -// Format: "Elapsed" - "Sum" - "Count" - "Ops" - "Avg" - "P50" - "P90" - "P95" - "P99" - "P999" - "P9999" - "Min" - "Max -var workloadFormat = []string{"Prefix", "Operation", "Takes(s)", "Count", "Ops", "Avg(ms)", "50th(ms)", "90th(ms)", "95th(ms)", "99th(ms)", "99.9th(ms)", "99.99th(ms)", "Min(ms)", "Max(ms)"} - type RawKVConfig struct { keySize int valueSize int @@ -52,7 +47,7 @@ func (c *RawKVConfig) Validate() error { if err := c.readWriteRatio.ParseRatio(); err != nil { return fmt.Errorf("parse read-write-ratio failed: %v", err) } - return nil + return c.global.ParsePdAddrs() } // Register registers the workload to the command line parser @@ -241,35 +236,8 @@ func (w *WorkloadImpl) Cleanup(ctx context.Context, threadID int) error { return nil } -func outputFunc(outputStyle string, prefix string, perfHist map[string]*statistics.PerfHistogram) { - keys := make([]string, 0, len(perfHist)) - for k := range perfHist { - keys = append(keys, k) - } - sort.Strings(keys) - - lines := [][]string{} - for _, op := range keys { - hist := perfHist[op] - if !hist.Empty() { - op = strings.ToUpper(op) - line := []string{prefix, op} - line = append(line, hist.Format()...) - lines = append(lines, line) - } - } - switch outputStyle { - case utils.OutputStylePlain: - utils.RenderString("%s%-6s - %s\n", workloadFormat, lines) - case utils.OutputStyleTable: - utils.RenderTable(workloadFormat, lines) - case utils.OutputStyleJson: - utils.RenderJson(workloadFormat, lines) - } -} - func (w *WorkloadImpl) OutputStats(ifSummaryReport bool) { - w.stats.PrintFmt(ifSummaryReport, w.cfg.global.OutputStyle, outputFunc) + w.stats.PrintFmt(ifSummaryReport, w.cfg.global.OutputStyle, statistics.HistogramOutputFunc) } // DBName returns the name of test db. From 14d0c459dfd9799251390ff9aa1a4a4fd48001dd Mon Sep 17 00:00:00 2001 From: lucasliang Date: Mon, 6 May 2024 16:05:34 +0800 Subject: [PATCH 06/30] Polish output format of RawKV. Signed-off-by: lucasliang --- examples/benchtool/utils/statistics/histogram.go | 1 - examples/benchtool/workloads/rawkv/rawkv.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/benchtool/utils/statistics/histogram.go b/examples/benchtool/utils/statistics/histogram.go index ca67d6fe13..f3dc617ffc 100644 --- a/examples/benchtool/utils/statistics/histogram.go +++ b/examples/benchtool/utils/statistics/histogram.go @@ -147,7 +147,6 @@ func HistogramOutputFunc(outputStyle string, prefix string, perfHist map[string] op = strings.ToUpper(op) line := []string{prefix, op} line = append(line, hist.Format()...) - fmt.Println(line) lines = append(lines, line) } } diff --git a/examples/benchtool/workloads/rawkv/rawkv.go b/examples/benchtool/workloads/rawkv/rawkv.go index f006bef90d..bd866bca1d 100644 --- a/examples/benchtool/workloads/rawkv/rawkv.go +++ b/examples/benchtool/workloads/rawkv/rawkv.go @@ -57,7 +57,7 @@ func Register(command *config.CommandLineParser) *RawKVConfig { } rawKVConfig := &RawKVConfig{ global: command.GetConfig(), - readWriteRatio: utils.NewReadWriteRatio("1:1"), + readWriteRatio: utils.NewReadWriteRatio("1:1"), // TODO: generate workloads meeting the read-write ratio } cmd := &cobra.Command{ From b0a74a18993ea54e57a7759cfd3703269967f79b Mon Sep 17 00:00:00 2001 From: lucasliang Date: Tue, 7 May 2024 15:40:54 +0800 Subject: [PATCH 07/30] Support batch cmds for RawKV workloads. Signed-off-by: lucasliang --- examples/benchtool/go.mod | 4 ++ examples/benchtool/utils/util.go | 53 +++++++++++++++- examples/benchtool/utils/util_test.go | 51 ++++++++++++++++ examples/benchtool/workloads/base.go | 14 +++++ examples/benchtool/workloads/rawkv/rawkv.go | 67 ++++++++++++++++----- 5 files changed, 174 insertions(+), 15 deletions(-) create mode 100644 examples/benchtool/utils/util_test.go diff --git a/examples/benchtool/go.mod b/examples/benchtool/go.mod index 6cba93766f..e39c17d7b4 100644 --- a/examples/benchtool/go.mod +++ b/examples/benchtool/go.mod @@ -6,6 +6,7 @@ require ( github.com/HdrHistogram/hdrhistogram-go v1.1.2 github.com/olekukonko/tablewriter v0.0.5 github.com/spf13/cobra v1.8.0 + github.com/stretchr/testify v1.8.1 github.com/tikv/client-go/v2 v2.0.7 ) @@ -16,6 +17,7 @@ require ( github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd/v22 v22.3.2 // indirect github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 // indirect github.com/elastic/gosigar v0.14.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -32,6 +34,7 @@ require ( github.com/pingcap/kvproto v0.0.0-20230403051650-e166ae588106 // indirect github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.39.0 // indirect @@ -55,4 +58,5 @@ require ( google.golang.org/grpc v1.54.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/examples/benchtool/utils/util.go b/examples/benchtool/utils/util.go index 5ca4263ec3..0f145abcd8 100644 --- a/examples/benchtool/utils/util.go +++ b/examples/benchtool/utils/util.go @@ -1,9 +1,24 @@ +// Copyright 2024 TiKV Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package utils import ( "bytes" "encoding/json" "fmt" + "math/rand" "os" "strconv" "strings" @@ -13,6 +28,7 @@ import ( const ( DEFAULT_PRECISION = 2 + MAX_INT64 = 1<<63 - 1 ) const ( @@ -21,6 +37,7 @@ const ( OutputStyleJson = "json" ) +// ReadWriteRatio is used to parse the read-write ratio. type ReadWriteRatio struct { Ratio string readPercent int @@ -42,9 +59,11 @@ func (r *ReadWriteRatio) ParseRatio() error { readRatio, _ = strconv.Atoi(ratios[0]) writeRatio, _ = strconv.Atoi(ratios[1]) + if readRatio < 0 || writeRatio < 0 { + return fmt.Errorf("invalid read-write-ratio format") + } sumRatio := readRatio + writeRatio - r.readPercent = readRatio * 100 / sumRatio r.writePercent = 100 - r.readPercent } else { @@ -71,6 +90,8 @@ func (r *ReadWriteRatio) GetPercent(choice string) int { return 0 } +// Converting functions. + func FloatToString(num float64) string { return strconv.FormatFloat(num, 'f', DEFAULT_PRECISION, 64) } @@ -79,6 +100,36 @@ func IntToString(num int64) string { return strconv.FormatInt(num, 10) } +func StrArrsToByteArrs(strArrs []string) [][]byte { + byteArrs := make([][]byte, 0, len(strArrs)) + for _, strArr := range strArrs { + byteArrs = append(byteArrs, []byte(strArr)) + } + return byteArrs +} + +func GenRandomStr(prefix string, keySize int) string { + return fmt.Sprintf("%s@%0*d", prefix, keySize, rand.Intn(MAX_INT64)) +} + +func GenRandomStrArrs(prefix string, keySize, count int) []string { + keys := make([]string, 0, count) + for i := 0; i < count; i++ { + keys = append(keys, GenRandomStr(prefix, keySize)) + } + return keys +} + +func GenRandomByteArrs(prefix string, keySize, count int) [][]byte { + keys := make([][]byte, 0, count) + for i := 0; i < count; i++ { + keys = append(keys, []byte(GenRandomStr(prefix, keySize))) + } + return keys +} + +// Output formatting functions. + func RenderString(format string, headers []string, values [][]string) { if len(values) == 0 { return diff --git a/examples/benchtool/utils/util_test.go b/examples/benchtool/utils/util_test.go new file mode 100644 index 0000000000..32d373d24a --- /dev/null +++ b/examples/benchtool/utils/util_test.go @@ -0,0 +1,51 @@ +// Copyright 2024 TiKV Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestReadWriteRatio(t *testing.T) { + r := NewReadWriteRatio("100:0") + assert.Nil(t, r.ParseRatio()) + assert.Equal(t, 100, r.readPercent) + assert.Equal(t, 0, r.writePercent) + + r = NewReadWriteRatio("90:10") + assert.Nil(t, r.ParseRatio()) + assert.Equal(t, 90, r.readPercent) + assert.Equal(t, 10, r.writePercent) + + r = NewReadWriteRatio("-10:110") + assert.Error(t, r.ParseRatio()) +} + +func TestBasics(t *testing.T) { + prefix := "test" + str := GenRandomStr(prefix, 256) + assert.True(t, len(str) > 256+len(prefix)) + + str = GenRandomStr(prefix, 0) + assert.True(t, len(str) > len(prefix)) + + strs := GenRandomStrArrs(prefix, 256, 10) + assert.Equal(t, 10, len(strs)) + + byteArrs := GenRandomByteArrs(prefix, 256, 10) + assert.Equal(t, 10, len(byteArrs)) +} diff --git a/examples/benchtool/workloads/base.go b/examples/benchtool/workloads/base.go index ab644b32d5..06c33e3bb3 100644 --- a/examples/benchtool/workloads/base.go +++ b/examples/benchtool/workloads/base.go @@ -1,3 +1,17 @@ +// Copyright 2024 TiKV Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package workloads import ( diff --git a/examples/benchtool/workloads/rawkv/rawkv.go b/examples/benchtool/workloads/rawkv/rawkv.go index bd866bca1d..b80670c249 100644 --- a/examples/benchtool/workloads/rawkv/rawkv.go +++ b/examples/benchtool/workloads/rawkv/rawkv.go @@ -1,3 +1,17 @@ +// Copyright 2024 TiKV Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package rawkv import ( @@ -7,7 +21,6 @@ import ( "benchtool/workloads" "context" "fmt" - "math/rand" "sync" "time" @@ -18,18 +31,23 @@ import ( const ( WorkloadImplName = "rawkv" - WorkloadTypePut = "put" - WorkloadTypeGet = "get" + WorkloadTypePut = "put" + WorkloadTypeGet = "get" + WorkloadTypeBatchPut = "batch_put" + WorkloadTypeBatchGet = "batch_get" WorkloadDefaultKey = "rawkv_key" WorkloadDefaultValue = "rawkv_value" - - WorkloadMaxInt64 = 1<<63 - 1 ) +func isReadCommand(cmd string) bool { + return cmd == WorkloadTypeGet || cmd == WorkloadTypeBatchGet +} + type RawKVConfig struct { keySize int valueSize int + batchSize int prepareRetryCount int prepareRetryInterval time.Duration @@ -69,6 +87,7 @@ func Register(command *config.CommandLineParser) *RawKVConfig { cmd.PersistentFlags().StringVar(&rawKVConfig.commandType, "cmd", "put", "Type of command to execute (put/get)") cmd.PersistentFlags().IntVar(&rawKVConfig.keySize, "key-size", 1, "Size of key in bytes") cmd.PersistentFlags().IntVar(&rawKVConfig.valueSize, "value-size", 1, "Size of value in bytes") + cmd.PersistentFlags().IntVar(&rawKVConfig.batchSize, "batch-size", 1, "Size of batch for batch operations") cmd.PersistentFlags().BoolVar(&rawKVConfig.randomize, "random", false, "Whether to randomize each value") cmd.PersistentFlags().StringVar(&rawKVConfig.readWriteRatio.Ratio, "read-write-ratio", "1:1", "Read write ratio") @@ -118,10 +137,6 @@ func getRawKvConfig(ctx context.Context) *RawKVConfig { return c } -func genRandomStr(prefix string, keySize int) string { - return fmt.Sprintf("%s_%0*d", prefix, keySize, rand.Intn(WorkloadMaxInt64)) -} - type WorkloadImpl struct { cfg *RawKVConfig clients []*rawkv.Client @@ -199,12 +214,33 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { if !w.isValidThread(threadID) { return fmt.Errorf("no valid RawKV clients") } + client := w.clients[threadID] + + // For unary operations. key := WorkloadDefaultKey val := WorkloadDefaultValue - if w.cfg.randomize { - key = genRandomStr(WorkloadDefaultKey, w.cfg.keySize) - val = genRandomStr(WorkloadDefaultValue, w.cfg.valueSize) + + // For batch operations. + var ( + keys [][]byte + vals [][]byte + ) + switch w.cfg.commandType { + case WorkloadTypePut, WorkloadTypeGet: + if w.cfg.randomize { + key = utils.GenRandomStr(WorkloadDefaultKey, w.cfg.keySize) + if !isReadCommand(w.cfg.commandType) { + val = utils.GenRandomStr(WorkloadDefaultValue, w.cfg.valueSize) + } + } + case WorkloadTypeBatchPut, WorkloadTypeBatchGet: + if w.cfg.randomize { + keys = utils.GenRandomByteArrs(WorkloadDefaultKey, w.cfg.keySize, w.cfg.batchSize) + if !isReadCommand(w.cfg.commandType) { + vals = utils.GenRandomByteArrs(WorkloadDefaultValue, w.cfg.valueSize, w.cfg.batchSize) + } + } } start := time.Now() @@ -213,7 +249,10 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { client.Put(ctx, []byte(key), []byte(val)) case WorkloadTypeGet: client.Get(ctx, []byte(key)) - // TODO: add BatchGet and BatchPut + case WorkloadTypeBatchPut: + client.BatchPut(ctx, keys, vals) + case WorkloadTypeBatchGet: + client.BatchGet(ctx, keys) } w.stats.Record(w.cfg.commandType, time.Since(start)) return nil @@ -231,7 +270,7 @@ func (w *WorkloadImpl) Cleanup(ctx context.Context, threadID int) error { } if threadID == 0 { client := w.clients[threadID] - client.DeleteRange(ctx, []byte(""), []byte("")) // delete all keys + client.DeleteRange(ctx, []byte(WorkloadDefaultKey), []byte(WorkloadDefaultKey)) // delete all keys } return nil } From d269823ea146c5ab5a0eabcadcf645151a42a103 Mon Sep 17 00:00:00 2001 From: lucasliang Date: Thu, 9 May 2024 11:15:19 +0800 Subject: [PATCH 08/30] Build basic framework for supporting txn workloads. Signed-off-by: lucasliang --- examples/benchtool/go.mod | 2 +- examples/benchtool/main.go | 2 + examples/benchtool/workloads/rawkv/rawkv.go | 12 +- examples/benchtool/workloads/txnkv/txn.go | 348 ++++++++++++++++++++ 4 files changed, 359 insertions(+), 5 deletions(-) create mode 100644 examples/benchtool/workloads/txnkv/txn.go diff --git a/examples/benchtool/go.mod b/examples/benchtool/go.mod index e39c17d7b4..e127555045 100644 --- a/examples/benchtool/go.mod +++ b/examples/benchtool/go.mod @@ -6,7 +6,7 @@ require ( github.com/HdrHistogram/hdrhistogram-go v1.1.2 github.com/olekukonko/tablewriter v0.0.5 github.com/spf13/cobra v1.8.0 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.9.0 github.com/tikv/client-go/v2 v2.0.7 ) diff --git a/examples/benchtool/main.go b/examples/benchtool/main.go index 180ff00971..7f68e4cce7 100644 --- a/examples/benchtool/main.go +++ b/examples/benchtool/main.go @@ -18,6 +18,7 @@ import ( "benchtool/config" "benchtool/workloads" "benchtool/workloads/rawkv" + "benchtool/workloads/txnkv" "context" "fmt" "os" @@ -37,6 +38,7 @@ func main() { // register all workloads // TODO: add more workloads rawkv.Register(commandLineParser) + txnkv.Register(commandLineParser) var cancel context.CancelFunc workloads.GlobalContext, cancel = context.WithCancel(context.Background()) diff --git a/examples/benchtool/workloads/rawkv/rawkv.go b/examples/benchtool/workloads/rawkv/rawkv.go index b80670c249..e9aec8db44 100644 --- a/examples/benchtool/workloads/rawkv/rawkv.go +++ b/examples/benchtool/workloads/rawkv/rawkv.go @@ -225,6 +225,7 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { var ( keys [][]byte vals [][]byte + err error ) switch w.cfg.commandType { case WorkloadTypePut, WorkloadTypeGet: @@ -246,13 +247,16 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { start := time.Now() switch w.cfg.commandType { case WorkloadTypePut: - client.Put(ctx, []byte(key), []byte(val)) + err = client.Put(ctx, []byte(key), []byte(val)) case WorkloadTypeGet: - client.Get(ctx, []byte(key)) + _, err = client.Get(ctx, []byte(key)) case WorkloadTypeBatchPut: - client.BatchPut(ctx, keys, vals) + err = client.BatchPut(ctx, keys, vals) case WorkloadTypeBatchGet: - client.BatchGet(ctx, keys) + _, err = client.BatchGet(ctx, keys) + } + if err != nil { + return fmt.Errorf("execute %s failed: %v", w.cfg.commandType, err) } w.stats.Record(w.cfg.commandType, time.Since(start)) return nil diff --git a/examples/benchtool/workloads/txnkv/txn.go b/examples/benchtool/workloads/txnkv/txn.go new file mode 100644 index 0000000000..a69e3b6ddd --- /dev/null +++ b/examples/benchtool/workloads/txnkv/txn.go @@ -0,0 +1,348 @@ +// Copyright 2024 TiKV Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package txnkv + +import ( + "benchtool/config" + "benchtool/utils" + "benchtool/utils/statistics" + "benchtool/workloads" + "context" + "fmt" + "sync" + "time" + + "github.com/spf13/cobra" + clientConfig "github.com/tikv/client-go/v2/config" + clientTxnKV "github.com/tikv/client-go/v2/txnkv" +) + +const ( + WorkloadImplName = "txnkv" + + WorkloadTypeWrite = "write" + WorkloadTypeRead = "read" + + WorkloadDefaultKey = "txnkv_key" + WorkloadDefaultValue = "txnkv_value" + + WorkloadTxnModeDefault = "2pc" + WorkloadTxnMode1PC = "1pc" + WorkloadTxnModeAsyncCommit = "async-commit" +) + +type TxnKVConfig struct { + keySize int + valueSize int + columnSize int + txnSize int + + prepareRetryCount int + prepareRetryInterval time.Duration + readWriteRatio *utils.ReadWriteRatio + txnMode string + + global *config.GlobalConfig +} + +func (c *TxnKVConfig) Validate() error { + if c.keySize <= 0 || c.valueSize <= 0 { + return fmt.Errorf("key size or value size must be greater than 0") + } + if err := c.readWriteRatio.ParseRatio(); err != nil { + return fmt.Errorf("parse read-write-ratio failed: %v", err) + } + return c.global.ParsePdAddrs() +} + +// Register registers the workload to the command line parser +func Register(command *config.CommandLineParser) *TxnKVConfig { + if command == nil { + return nil + } + txnKVConfig := &TxnKVConfig{ + global: command.GetConfig(), + readWriteRatio: utils.NewReadWriteRatio("1:1"), // TODO: generate workloads meeting the read-write ratio + } + + cmd := &cobra.Command{ + Use: WorkloadImplName, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + workloads.GlobalContext = context.WithValue(workloads.GlobalContext, WorkloadImplName, txnKVConfig) + }, + } + cmd.PersistentFlags().StringVar(&txnKVConfig.readWriteRatio.Ratio, "read-write-ratio", "1:1", "Read write ratio") + cmd.PersistentFlags().IntVar(&txnKVConfig.keySize, "key-size", 1, "Size of key in bytes") + cmd.PersistentFlags().IntVar(&txnKVConfig.valueSize, "value-size", 1, "Size of value in bytes") + cmd.PersistentFlags().IntVar(&txnKVConfig.columnSize, "column-size", 1, "Size of column") + cmd.PersistentFlags().IntVar(&txnKVConfig.txnSize, "txn-size", 1, "Size of transaction (normally, the lines of kv pairs)") + cmd.PersistentFlags().StringVar(&txnKVConfig.txnMode, "txn-mode", "2pc", "Mode of transaction (2pc/1pc/async-commit), default: 2pc") + + var cmdPrepare = &cobra.Command{ + Use: "prepare", + Short: "Prepare data for TxnKV workload", + Run: func(cmd *cobra.Command, _ []string) { + execTxnKV("prepare") + }, + } + cmdPrepare.PersistentFlags().IntVar(&txnKVConfig.prepareRetryCount, "retry-count", 50, "Retry count when errors occur") + cmdPrepare.PersistentFlags().DurationVar(&txnKVConfig.prepareRetryInterval, "retry-interval", 10*time.Millisecond, "The interval for each retry") + + var cmdRun = &cobra.Command{ + Use: "run", + Short: "Run workload", + Run: func(cmd *cobra.Command, _ []string) { + execTxnKV("run") + }, + } + + var cmdCleanup = &cobra.Command{ + Use: "cleanup", + Short: "Cleanup data for the workload", + Run: func(cmd *cobra.Command, _ []string) { + execTxnKV("cleanup") + }, + } + + var cmdCheck = &cobra.Command{ + Use: "check", + Short: "Check data consistency for the workload", + Run: func(cmd *cobra.Command, _ []string) { + execTxnKV("check") + }, + } + + cmd.AddCommand(cmdRun, cmdPrepare, cmdCleanup, cmdCheck) + + command.GetCommand().AddCommand(cmd) + + return txnKVConfig +} + +func getTxnKVConfig(ctx context.Context) *TxnKVConfig { + c := ctx.Value(WorkloadImplName).(*TxnKVConfig) + return c +} + +// Workload is the implementation of WorkloadInterface +type WorkloadImpl struct { + cfg *TxnKVConfig + clients []*clientTxnKV.Client + + wait sync.WaitGroup + + stats *statistics.PerfProfile +} + +func NewTxnKVWorkload(cfg *TxnKVConfig) (*WorkloadImpl, error) { + if err := cfg.Validate(); err != nil { + return nil, err + } + w := &WorkloadImpl{ + cfg: cfg, + stats: statistics.NewPerfProfile(), + } + + clientConfig.UpdateGlobal(func(conf *clientConfig.Config) { + conf.TiKVClient.MaxBatchSize = (uint)(cfg.txnSize + 10) + }) + // TODO: setting batch. + // defer config.UpdateGlobal(func(conf *config.Config) { + // conf.TiKVClient.MaxBatchSize = 0 + // conf.TiKVClient.GrpcConnectionCount = 1 + // })() + + w.clients = make([]*clientTxnKV.Client, 0, w.cfg.global.Threads) + for i := 0; i < w.cfg.global.Threads; i++ { + client, err := clientTxnKV.NewClient(w.cfg.global.Targets) + if err != nil { + return nil, err + } + w.clients = append(w.clients, client) + } + return w, nil +} + +func (w *WorkloadImpl) Name() string { + return WorkloadImplName +} + +func (w *WorkloadImpl) isValid() bool { + return w.cfg != nil && w.cfg.global != nil && len(w.clients) > 0 +} + +func (w *WorkloadImpl) isValidThread(threadID int) bool { + return w.isValid() && threadID < len(w.clients) +} + +// InitThread implements WorkloadInterface +func (w *WorkloadImpl) InitThread(ctx context.Context, threadID int) error { + // Nothing to do + return nil +} + +// CleanupThread implements WorkloadInterface +func (w *WorkloadImpl) CleanupThread(ctx context.Context, threadID int) { + if w.isValidThread(threadID) { + client := w.clients[threadID] + if client != nil { + client.Close() + } + } +} + +// Prepare implements WorkloadInterface +func (w *WorkloadImpl) Prepare(ctx context.Context, threadID int) error { + if !w.isValidThread(threadID) { + return fmt.Errorf("no valid TxnKV clients") + } + + // return prepareWorkloadImpl(ctx, w, w.cfg.Threads, w.cfg.Warehouses, threadID) + // TODO: add prepare stage + return nil +} + +// CheckPrepare implements WorkloadInterface +func (w *WorkloadImpl) CheckPrepare(ctx context.Context, threadID int) error { + return nil +} + +func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { + if !w.isValidThread(threadID) { + return fmt.Errorf("no valid TxnKV clients") + } + + client := w.clients[threadID] + key := WorkloadDefaultKey + val := utils.GenRandomStr(WorkloadDefaultValue, w.cfg.valueSize) + + // Constructs the txn client and sets the txn mode + txn, err := client.Begin() + if err != nil { + return fmt.Errorf("txn begin failed, err %v", err) + } + switch w.cfg.txnMode { + case WorkloadTxnMode1PC: + txn.SetEnable1PC(true) + case WorkloadTxnModeAsyncCommit: + txn.SetEnableAsyncCommit(true) + } + + for row := 0; row < w.cfg.txnSize; row++ { + key = fmt.Sprintf("%s@col_", utils.GenRandomStr(key, w.cfg.keySize)) + for col := 0; col < w.cfg.columnSize; col++ { + colKey := fmt.Sprintf("%s%d", key, col) + err = txn.Set([]byte(colKey), []byte(val)) + if err != nil { + return fmt.Errorf("txn set failed, err %v", err) + } + _, err = txn.Get(ctx, []byte(colKey)) + if err != nil { + return fmt.Errorf("txn get failed, err %v", err) + } + } + } + start := time.Now() + err = txn.Commit(ctx) + if err != nil { + return fmt.Errorf("txn commit failed, err %v", err) + } + w.stats.Record(w.cfg.txnMode, time.Since(start)) + return nil +} + +// Check implements WorkloadInterface +func (w *WorkloadImpl) Check(ctx context.Context, threadID int) error { + return nil +} + +// Cleanup implements WorkloadInterface +func (w *WorkloadImpl) Cleanup(ctx context.Context, threadID int) error { + if !w.isValidThread(threadID) { + return fmt.Errorf("no valid TxnKV clients") + } + if threadID == 0 { + client := w.clients[threadID] + client.DeleteRange(ctx, []byte(WorkloadDefaultKey), []byte(WorkloadDefaultKey), w.cfg.global.Threads) // delete all keys + } + return nil +} + +func (w *WorkloadImpl) OutputStats(ifSummaryReport bool) { + w.stats.PrintFmt(ifSummaryReport, w.cfg.global.OutputStyle, statistics.HistogramOutputFunc) +} + +// DBName returns the name of test db. +func (w *WorkloadImpl) DBName() string { + return w.cfg.global.DbName +} + +func (w *WorkloadImpl) Execute(cmd string) { + w.wait.Add(w.cfg.global.Threads) + + ctx, cancel := context.WithCancel(workloads.GlobalContext) + ch := make(chan struct{}, 1) + go func() { + ticker := time.NewTicker(w.cfg.global.OutputInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + ch <- struct{}{} + return + case <-ticker.C: + w.OutputStats(false) + } + } + }() + + count := w.cfg.global.TotalCount / w.cfg.global.Threads + for i := 0; i < w.cfg.global.Threads; i++ { + go func(index int) { + defer w.wait.Done() + if err := workloads.DispatchExecution(ctx, w, cmd, count, index, w.cfg.global.Silence, w.cfg.global.IgnoreError); err != nil { + fmt.Printf("[%s] execute %s failed, err %v\n", time.Now().Format("2006-01-02 15:04:05"), cmd, err) + return + } + }(i) + } + + w.wait.Wait() + cancel() + <-ch +} + +func execTxnKV(cmd string) { + if cmd == "" { + return + } + TxnKVConfig := getTxnKVConfig(workloads.GlobalContext) + + var workload *WorkloadImpl + var err error + if workload, err = NewTxnKVWorkload(TxnKVConfig); err != nil { + fmt.Printf("create TxnKV workload failed: %v\n", err) + return + } + + timeoutCtx, cancel := context.WithTimeout(workloads.GlobalContext, TxnKVConfig.global.TotalTime) + workloads.GlobalContext = timeoutCtx + defer cancel() + + workload.Execute(cmd) + fmt.Println("TxnKV workload finished") + workload.OutputStats(true) +} From ee2e15417720c6895773431d6b9eff99accf7b22 Mon Sep 17 00:00:00 2001 From: lucasliang Date: Thu, 9 May 2024 11:43:36 +0800 Subject: [PATCH 09/30] Polish codes. Signed-off-by: lucasliang --- examples/benchtool/workloads/txnkv/txn.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/benchtool/workloads/txnkv/txn.go b/examples/benchtool/workloads/txnkv/txn.go index a69e3b6ddd..56ac9bb898 100644 --- a/examples/benchtool/workloads/txnkv/txn.go +++ b/examples/benchtool/workloads/txnkv/txn.go @@ -38,8 +38,8 @@ const ( WorkloadDefaultKey = "txnkv_key" WorkloadDefaultValue = "txnkv_value" - WorkloadTxnModeDefault = "2pc" - WorkloadTxnMode1PC = "1pc" + WorkloadTxnModeDefault = "2PC" + WorkloadTxnMode1PC = "1PC" WorkloadTxnModeAsyncCommit = "async-commit" ) @@ -88,7 +88,7 @@ func Register(command *config.CommandLineParser) *TxnKVConfig { cmd.PersistentFlags().IntVar(&txnKVConfig.valueSize, "value-size", 1, "Size of value in bytes") cmd.PersistentFlags().IntVar(&txnKVConfig.columnSize, "column-size", 1, "Size of column") cmd.PersistentFlags().IntVar(&txnKVConfig.txnSize, "txn-size", 1, "Size of transaction (normally, the lines of kv pairs)") - cmd.PersistentFlags().StringVar(&txnKVConfig.txnMode, "txn-mode", "2pc", "Mode of transaction (2pc/1pc/async-commit), default: 2pc") + cmd.PersistentFlags().StringVar(&txnKVConfig.txnMode, "txn-mode", "2PC", "Mode of transaction (2PC/1PC/async-commit)") var cmdPrepare = &cobra.Command{ Use: "prepare", From 4241a506823857bf3e49de9389a4ee9f648457b2 Mon Sep 17 00:00:00 2001 From: lucasliang Date: Thu, 9 May 2024 14:05:29 +0800 Subject: [PATCH 10/30] Polish codes. Signed-off-by: lucasliang --- examples/benchtool/utils/util.go | 9 +++++++-- examples/benchtool/workloads/txnkv/txn.go | 15 ++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/examples/benchtool/utils/util.go b/examples/benchtool/utils/util.go index 0f145abcd8..aa993ab9b0 100644 --- a/examples/benchtool/utils/util.go +++ b/examples/benchtool/utils/util.go @@ -37,6 +37,11 @@ const ( OutputStyleJson = "json" ) +const ( + ReadPercent = "read" + WritePercent = "write" +) + // ReadWriteRatio is used to parse the read-write ratio. type ReadWriteRatio struct { Ratio string @@ -82,9 +87,9 @@ func (r *ReadWriteRatio) GetPercent(choice string) int { return 0 } } - if choice == "read" { + if choice == ReadPercent { return r.readPercent - } else if choice == "write" { + } else if choice == WritePercent { return r.writePercent } return 0 diff --git a/examples/benchtool/workloads/txnkv/txn.go b/examples/benchtool/workloads/txnkv/txn.go index 56ac9bb898..6941c4c2d4 100644 --- a/examples/benchtool/workloads/txnkv/txn.go +++ b/examples/benchtool/workloads/txnkv/txn.go @@ -21,6 +21,7 @@ import ( "benchtool/workloads" "context" "fmt" + "math/rand" "sync" "time" @@ -240,17 +241,21 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { txn.SetEnableAsyncCommit(true) } + sum := w.cfg.txnSize * w.cfg.columnSize + readCount := sum - w.cfg.readWriteRatio.GetPercent(utils.ReadPercent)/100 + for row := 0; row < w.cfg.txnSize; row++ { key = fmt.Sprintf("%s@col_", utils.GenRandomStr(key, w.cfg.keySize)) for col := 0; col < w.cfg.columnSize; col++ { colKey := fmt.Sprintf("%s%d", key, col) - err = txn.Set([]byte(colKey), []byte(val)) - if err != nil { - return fmt.Errorf("txn set failed, err %v", err) + if readCount > 0 && rand.Intn(sum)/2 == 0 { + _, err = txn.Get(ctx, []byte(colKey)) + readCount -= 1 + } else { + err = txn.Set([]byte(colKey), []byte(val)) } - _, err = txn.Get(ctx, []byte(colKey)) if err != nil { - return fmt.Errorf("txn get failed, err %v", err) + return fmt.Errorf("txn set / get failed, err %v", err) } } } From 18ce51beab42ecc0faaa54a1155cdff5ded0a004 Mon Sep 17 00:00:00 2001 From: lucasliang Date: Thu, 9 May 2024 14:51:27 +0800 Subject: [PATCH 11/30] Fix bugs. Signed-off-by: lucasliang --- examples/benchtool/workloads/txnkv/txn.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/benchtool/workloads/txnkv/txn.go b/examples/benchtool/workloads/txnkv/txn.go index 6941c4c2d4..1b3058245f 100644 --- a/examples/benchtool/workloads/txnkv/txn.go +++ b/examples/benchtool/workloads/txnkv/txn.go @@ -27,6 +27,7 @@ import ( "github.com/spf13/cobra" clientConfig "github.com/tikv/client-go/v2/config" + tikverr "github.com/tikv/client-go/v2/error" clientTxnKV "github.com/tikv/client-go/v2/txnkv" ) @@ -90,6 +91,7 @@ func Register(command *config.CommandLineParser) *TxnKVConfig { cmd.PersistentFlags().IntVar(&txnKVConfig.columnSize, "column-size", 1, "Size of column") cmd.PersistentFlags().IntVar(&txnKVConfig.txnSize, "txn-size", 1, "Size of transaction (normally, the lines of kv pairs)") cmd.PersistentFlags().StringVar(&txnKVConfig.txnMode, "txn-mode", "2PC", "Mode of transaction (2PC/1PC/async-commit)") + // TODO: add more flags on txn, such as pessimistic/optimistic lock, etc. var cmdPrepare = &cobra.Command{ Use: "prepare", @@ -250,6 +252,9 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { colKey := fmt.Sprintf("%s%d", key, col) if readCount > 0 && rand.Intn(sum)/2 == 0 { _, err = txn.Get(ctx, []byte(colKey)) + if tikverr.IsErrNotFound(err) { + err = txn.Set([]byte(colKey), []byte(val)) + } readCount -= 1 } else { err = txn.Set([]byte(colKey), []byte(val)) From 968b610a34fa6bc34d5ab639bfe9a98692b0ce49 Mon Sep 17 00:00:00 2001 From: lucasliang Date: Thu, 9 May 2024 15:00:43 +0800 Subject: [PATCH 12/30] Fix errs when cleaning up keys. Signed-off-by: lucasliang --- examples/benchtool/workloads/rawkv/rawkv.go | 7 ++++--- examples/benchtool/workloads/txnkv/txn.go | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/benchtool/workloads/rawkv/rawkv.go b/examples/benchtool/workloads/rawkv/rawkv.go index e9aec8db44..4f5f8be93f 100644 --- a/examples/benchtool/workloads/rawkv/rawkv.go +++ b/examples/benchtool/workloads/rawkv/rawkv.go @@ -36,8 +36,9 @@ const ( WorkloadTypeBatchPut = "batch_put" WorkloadTypeBatchGet = "batch_get" - WorkloadDefaultKey = "rawkv_key" - WorkloadDefaultValue = "rawkv_value" + WorkloadDefaultKey = "rawkv_key" + WorkloadDefaultEndKey = "rawkv_key`" + WorkloadDefaultValue = "rawkv_value" ) func isReadCommand(cmd string) bool { @@ -274,7 +275,7 @@ func (w *WorkloadImpl) Cleanup(ctx context.Context, threadID int) error { } if threadID == 0 { client := w.clients[threadID] - client.DeleteRange(ctx, []byte(WorkloadDefaultKey), []byte(WorkloadDefaultKey)) // delete all keys + client.DeleteRange(ctx, []byte(WorkloadDefaultKey), []byte(WorkloadDefaultEndKey)) // delete all keys } return nil } diff --git a/examples/benchtool/workloads/txnkv/txn.go b/examples/benchtool/workloads/txnkv/txn.go index 1b3058245f..9d400e2262 100644 --- a/examples/benchtool/workloads/txnkv/txn.go +++ b/examples/benchtool/workloads/txnkv/txn.go @@ -37,8 +37,9 @@ const ( WorkloadTypeWrite = "write" WorkloadTypeRead = "read" - WorkloadDefaultKey = "txnkv_key" - WorkloadDefaultValue = "txnkv_value" + WorkloadDefaultKey = "txnkv_key" + WorkloadDefaultEndKey = "txnkv_key`" + WorkloadDefaultValue = "txnkv_value" WorkloadTxnModeDefault = "2PC" WorkloadTxnMode1PC = "1PC" @@ -285,7 +286,7 @@ func (w *WorkloadImpl) Cleanup(ctx context.Context, threadID int) error { } if threadID == 0 { client := w.clients[threadID] - client.DeleteRange(ctx, []byte(WorkloadDefaultKey), []byte(WorkloadDefaultKey), w.cfg.global.Threads) // delete all keys + client.DeleteRange(ctx, []byte(WorkloadDefaultKey), []byte(WorkloadDefaultEndKey), w.cfg.global.Threads) // delete all keys } return nil } From d1f50b5d76be3774b0717bf70db79007cc8b80fe Mon Sep 17 00:00:00 2001 From: lucasliang Date: Thu, 9 May 2024 15:05:31 +0800 Subject: [PATCH 13/30] Remove unnecessary arguments. Signed-off-by: lucasliang --- examples/benchtool/config/config.go | 12 ++---------- examples/benchtool/workloads/base.go | 1 - examples/benchtool/workloads/rawkv/rawkv.go | 5 ----- examples/benchtool/workloads/txnkv/txn.go | 5 ----- 4 files changed, 2 insertions(+), 21 deletions(-) diff --git a/examples/benchtool/config/config.go b/examples/benchtool/config/config.go index e64df911d7..dbda851f48 100644 --- a/examples/benchtool/config/config.go +++ b/examples/benchtool/config/config.go @@ -28,10 +28,6 @@ type GlobalConfig struct { port int StatusPort int - Username string - Password string - DbName string - Threads int TotalTime time.Duration TotalCount int @@ -58,8 +54,8 @@ func (c *GlobalConfig) ParsePdAddrs() error { } func (c *GlobalConfig) Format() string { - return fmt.Sprintf("Hosts: %v, Port: %d, StatusPort: %d, Username: %s, Password: %s, DbName: %s, Threads: %d, TotalTime: %v, TotalCount: %d, DropData: %t, IgnoreError: %t, OutputInterval: %v, Silence: %t, OutputStyle: %s", - c.hosts, c.port, c.StatusPort, c.Username, c.Password, c.DbName, c.Threads, c.TotalTime, c.TotalCount, c.DropData, c.IgnoreError, c.OutputInterval, c.Silence, c.OutputStyle) + return fmt.Sprintf("Hosts: %v, Port: %d, StatusPort: %d, Threads: %d, TotalTime: %v, TotalCount: %d, DropData: %t, IgnoreError: %t, OutputInterval: %v, Silence: %t, OutputStyle: %s", + c.hosts, c.port, c.StatusPort, c.Threads, c.TotalTime, c.TotalCount, c.DropData, c.IgnoreError, c.OutputInterval, c.Silence, c.OutputStyle) } type CommandLineParser struct { @@ -80,10 +76,6 @@ func (p *CommandLineParser) Initialize() { rootCmd.PersistentFlags().StringSliceVarP(&globalCfg.hosts, "host", "H", []string{"127.0.0.1"}, "PD host") rootCmd.PersistentFlags().IntVarP(&globalCfg.port, "port", "P", 4000, "PD port") rootCmd.PersistentFlags().IntVarP(&globalCfg.StatusPort, "statusPort", "S", 10080, "PD status port") - // Maybe unnecessary - rootCmd.PersistentFlags().StringVarP(&globalCfg.Username, "username", "U", "root", "Database user") - rootCmd.PersistentFlags().StringVarP(&globalCfg.Password, "password", "p", "", "Password for user") - rootCmd.PersistentFlags().StringVarP(&globalCfg.DbName, "db", "D", "test", "Database name") rootCmd.PersistentFlags().IntVarP(&globalCfg.Threads, "threads", "T", 1, "Thread concurrency") rootCmd.PersistentFlags().DurationVar(&globalCfg.TotalTime, "time", 1<<63-1, "Total execution time") diff --git a/examples/benchtool/workloads/base.go b/examples/benchtool/workloads/base.go index 06c33e3bb3..aa10068db2 100644 --- a/examples/benchtool/workloads/base.go +++ b/examples/benchtool/workloads/base.go @@ -36,7 +36,6 @@ type WorkloadInterface interface { Cleanup(ctx context.Context, threadID int) error Check(ctx context.Context, threadID int) error OutputStats(ifSummaryReport bool) - DBName() string } var GlobalContext context.Context diff --git a/examples/benchtool/workloads/rawkv/rawkv.go b/examples/benchtool/workloads/rawkv/rawkv.go index 4f5f8be93f..4afbf4efce 100644 --- a/examples/benchtool/workloads/rawkv/rawkv.go +++ b/examples/benchtool/workloads/rawkv/rawkv.go @@ -284,11 +284,6 @@ func (w *WorkloadImpl) OutputStats(ifSummaryReport bool) { w.stats.PrintFmt(ifSummaryReport, w.cfg.global.OutputStyle, statistics.HistogramOutputFunc) } -// DBName returns the name of test db. -func (w *WorkloadImpl) DBName() string { - return w.cfg.global.DbName -} - func (w *WorkloadImpl) Execute(cmd string) { w.wait.Add(w.cfg.global.Threads) diff --git a/examples/benchtool/workloads/txnkv/txn.go b/examples/benchtool/workloads/txnkv/txn.go index 9d400e2262..b413f45f83 100644 --- a/examples/benchtool/workloads/txnkv/txn.go +++ b/examples/benchtool/workloads/txnkv/txn.go @@ -295,11 +295,6 @@ func (w *WorkloadImpl) OutputStats(ifSummaryReport bool) { w.stats.PrintFmt(ifSummaryReport, w.cfg.global.OutputStyle, statistics.HistogramOutputFunc) } -// DBName returns the name of test db. -func (w *WorkloadImpl) DBName() string { - return w.cfg.global.DbName -} - func (w *WorkloadImpl) Execute(cmd string) { w.wait.Add(w.cfg.global.Threads) From aafac36ad075c05202e609e41eb69fee9c89974b Mon Sep 17 00:00:00 2001 From: lucasliang Date: Mon, 13 May 2024 16:35:43 +0800 Subject: [PATCH 14/30] Support pessimistic mode. Signed-off-by: lucasliang --- examples/benchtool/workloads/txnkv/txn.go | 29 +++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/examples/benchtool/workloads/txnkv/txn.go b/examples/benchtool/workloads/txnkv/txn.go index b413f45f83..8685aec37e 100644 --- a/examples/benchtool/workloads/txnkv/txn.go +++ b/examples/benchtool/workloads/txnkv/txn.go @@ -56,6 +56,7 @@ type TxnKVConfig struct { prepareRetryInterval time.Duration readWriteRatio *utils.ReadWriteRatio txnMode string + lockTimeout int global *config.GlobalConfig } @@ -92,6 +93,7 @@ func Register(command *config.CommandLineParser) *TxnKVConfig { cmd.PersistentFlags().IntVar(&txnKVConfig.columnSize, "column-size", 1, "Size of column") cmd.PersistentFlags().IntVar(&txnKVConfig.txnSize, "txn-size", 1, "Size of transaction (normally, the lines of kv pairs)") cmd.PersistentFlags().StringVar(&txnKVConfig.txnMode, "txn-mode", "2PC", "Mode of transaction (2PC/1PC/async-commit)") + cmd.PersistentFlags().IntVar(&txnKVConfig.lockTimeout, "lock-timeout", 0, "Lock timeout for each key in txn (>0 means pessimistic mode, 0 means optimistic mode)") // TODO: add more flags on txn, such as pessimistic/optimistic lock, etc. var cmdPrepare = &cobra.Command{ @@ -140,6 +142,14 @@ func getTxnKVConfig(ctx context.Context) *TxnKVConfig { return c } +// Assistants for TxnKV workload +func prepareLockKeyWithTimeout(ctx context.Context, txn *clientTxnKV.KVTxn, key []byte, timeout int64) error { + if timeout > 0 { + return txn.LockKeysWithWaitTime(ctx, timeout, key) + } + return nil +} + // Workload is the implementation of WorkloadInterface type WorkloadImpl struct { cfg *TxnKVConfig @@ -231,6 +241,7 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { client := w.clients[threadID] key := WorkloadDefaultKey val := utils.GenRandomStr(WorkloadDefaultValue, w.cfg.valueSize) + lockTimeout := int64(w.cfg.lockTimeout) // Constructs the txn client and sets the txn mode txn, err := client.Begin() @@ -243,22 +254,36 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { case WorkloadTxnModeAsyncCommit: txn.SetEnableAsyncCommit(true) } + // Default is optimistic lock mode. + + txn.SetPessimistic(lockTimeout > 0) sum := w.cfg.txnSize * w.cfg.columnSize - readCount := sum - w.cfg.readWriteRatio.GetPercent(utils.ReadPercent)/100 + readCount := sum * w.cfg.readWriteRatio.GetPercent(utils.ReadPercent) / 100 + writeCount := sum - readCount + canRead := func(sum, readCount, writeCount int) bool { + return readCount > 0 && (writeCount <= 0 || rand.Intn(sum)/2 == 0) + } for row := 0; row < w.cfg.txnSize; row++ { key = fmt.Sprintf("%s@col_", utils.GenRandomStr(key, w.cfg.keySize)) + // Lock the key with timeout if necessary. + if err = prepareLockKeyWithTimeout(ctx, txn, []byte(key), lockTimeout); err != nil { + fmt.Printf("txn lock key failed, err %v", err) + continue + } for col := 0; col < w.cfg.columnSize; col++ { colKey := fmt.Sprintf("%s%d", key, col) - if readCount > 0 && rand.Intn(sum)/2 == 0 { + if canRead(sum, readCount, writeCount) { _, err = txn.Get(ctx, []byte(colKey)) if tikverr.IsErrNotFound(err) { err = txn.Set([]byte(colKey), []byte(val)) + writeCount -= 1 } readCount -= 1 } else { err = txn.Set([]byte(colKey), []byte(val)) + writeCount -= 1 } if err != nil { return fmt.Errorf("txn set / get failed, err %v", err) From b7b91f0fb333f8636e5c79792899429bf39a7ca3 Mon Sep 17 00:00:00 2001 From: lucasliang Date: Thu, 16 May 2024 15:13:52 +0800 Subject: [PATCH 15/30] Support other RawKV commands expect xxx_ttl commands. Signed-off-by: lucasliang --- examples/benchtool/workloads/rawkv/rawkv.go | 70 ++++++++++++++++----- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/examples/benchtool/workloads/rawkv/rawkv.go b/examples/benchtool/workloads/rawkv/rawkv.go index 4afbf4efce..762c8eac08 100644 --- a/examples/benchtool/workloads/rawkv/rawkv.go +++ b/examples/benchtool/workloads/rawkv/rawkv.go @@ -31,14 +31,23 @@ import ( const ( WorkloadImplName = "rawkv" - WorkloadTypePut = "put" - WorkloadTypeGet = "get" - WorkloadTypeBatchPut = "batch_put" - WorkloadTypeBatchGet = "batch_get" + WorkloadTypePut = "put" + WorkloadTypeGet = "get" + WorkloadTypeDel = "del" + WorkloadTypeBatchPut = "batch_put" + WorkloadTypeBatchGet = "batch_get" + WorkloadTypeBatchDel = "batch_del" + WorkloadTypeScan = "scan" + WorkloadTypeReverseScan = "reverse_scan" + WorkloadTypeCompareAndSwap = "cas" WorkloadDefaultKey = "rawkv_key" WorkloadDefaultEndKey = "rawkv_key`" WorkloadDefaultValue = "rawkv_value" + + WorkloadColumnFamilyDefault = "CF_DEFAULT" + WorkloadColumnFamilyWrite = "CF_WRITE" + WorkloadColumnFamilyLock = "CF_LOCK" ) func isReadCommand(cmd string) bool { @@ -50,11 +59,11 @@ type RawKVConfig struct { valueSize int batchSize int + columnFamily string + commandType string prepareRetryCount int prepareRetryInterval time.Duration randomize bool - readWriteRatio *utils.ReadWriteRatio - commandType string global *config.GlobalConfig } @@ -63,8 +72,10 @@ func (c *RawKVConfig) Validate() error { if c.keySize <= 0 || c.valueSize <= 0 { return fmt.Errorf("key size or value size must be greater than 0") } - if err := c.readWriteRatio.ParseRatio(); err != nil { - return fmt.Errorf("parse read-write-ratio failed: %v", err) + if c.columnFamily != WorkloadColumnFamilyDefault && + c.columnFamily != WorkloadColumnFamilyWrite && + c.columnFamily != WorkloadColumnFamilyLock { + return fmt.Errorf("invalid column family: %s", c.columnFamily) } return c.global.ParsePdAddrs() } @@ -75,8 +86,7 @@ func Register(command *config.CommandLineParser) *RawKVConfig { return nil } rawKVConfig := &RawKVConfig{ - global: command.GetConfig(), - readWriteRatio: utils.NewReadWriteRatio("1:1"), // TODO: generate workloads meeting the read-write ratio + global: command.GetConfig(), } cmd := &cobra.Command{ @@ -85,12 +95,12 @@ func Register(command *config.CommandLineParser) *RawKVConfig { workloads.GlobalContext = context.WithValue(workloads.GlobalContext, WorkloadImplName, rawKVConfig) }, } - cmd.PersistentFlags().StringVar(&rawKVConfig.commandType, "cmd", "put", "Type of command to execute (put/get)") + cmd.PersistentFlags().StringVar(&rawKVConfig.columnFamily, "cf", "default", "Column family name (default|write|lock)") + cmd.PersistentFlags().StringVar(&rawKVConfig.commandType, "cmd", "put", "Type of command to execute (put|get|del|batch_put|batch_get|batch_del|scan|reserve_scan|cas)") cmd.PersistentFlags().IntVar(&rawKVConfig.keySize, "key-size", 1, "Size of key in bytes") cmd.PersistentFlags().IntVar(&rawKVConfig.valueSize, "value-size", 1, "Size of value in bytes") cmd.PersistentFlags().IntVar(&rawKVConfig.batchSize, "batch-size", 1, "Size of batch for batch operations") cmd.PersistentFlags().BoolVar(&rawKVConfig.randomize, "random", false, "Whether to randomize each value") - cmd.PersistentFlags().StringVar(&rawKVConfig.readWriteRatio.Ratio, "read-write-ratio", "1:1", "Read write ratio") var cmdPrepare = &cobra.Command{ Use: "prepare", @@ -182,6 +192,12 @@ func (w *WorkloadImpl) isValidThread(threadID int) bool { // InitThread implements WorkloadInterface func (w *WorkloadImpl) InitThread(ctx context.Context, threadID int) error { // Nothing to do + if !w.isValidThread(threadID) { + return fmt.Errorf("no valid RawKV clients") + } + client := w.clients[threadID] + client.SetAtomicForCAS(w.cfg.commandType == WorkloadTypeCompareAndSwap) + client.SetColumnFamily(w.cfg.columnFamily) return nil } @@ -229,14 +245,14 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { err error ) switch w.cfg.commandType { - case WorkloadTypePut, WorkloadTypeGet: + case WorkloadTypePut, WorkloadTypeGet, WorkloadTypeDel, WorkloadTypeCompareAndSwap, WorkloadTypeScan, WorkloadTypeReverseScan: if w.cfg.randomize { key = utils.GenRandomStr(WorkloadDefaultKey, w.cfg.keySize) if !isReadCommand(w.cfg.commandType) { val = utils.GenRandomStr(WorkloadDefaultValue, w.cfg.valueSize) } } - case WorkloadTypeBatchPut, WorkloadTypeBatchGet: + case WorkloadTypeBatchPut, WorkloadTypeBatchGet, WorkloadTypeBatchDel: if w.cfg.randomize { keys = utils.GenRandomByteArrs(WorkloadDefaultKey, w.cfg.keySize, w.cfg.batchSize) if !isReadCommand(w.cfg.commandType) { @@ -251,12 +267,24 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { err = client.Put(ctx, []byte(key), []byte(val)) case WorkloadTypeGet: _, err = client.Get(ctx, []byte(key)) + case WorkloadTypeDel: + err = client.Delete(ctx, []byte(key)) case WorkloadTypeBatchPut: err = client.BatchPut(ctx, keys, vals) case WorkloadTypeBatchGet: _, err = client.BatchGet(ctx, keys) + case WorkloadTypeBatchDel: + err = client.BatchDelete(ctx, keys) + case WorkloadTypeCompareAndSwap: + var oldVal []byte + oldVal, _ = client.Get(ctx, []byte(key)) + _, _, err = client.CompareAndSwap(ctx, []byte(key), []byte(oldVal), []byte(val)) // Experimental + case WorkloadTypeScan: + _, _, err = client.Scan(ctx, []byte(key), []byte(WorkloadDefaultEndKey), w.cfg.batchSize) + case WorkloadTypeReverseScan: + _, _, err = client.ReverseScan(ctx, []byte(key), []byte(WorkloadDefaultKey), w.cfg.batchSize) } - if err != nil { + if err != nil && !w.cfg.global.IgnoreError { return fmt.Errorf("execute %s failed: %v", w.cfg.commandType, err) } w.stats.Record(w.cfg.commandType, time.Since(start)) @@ -265,6 +293,18 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { // Check implements WorkloadInterface func (w *WorkloadImpl) Check(ctx context.Context, threadID int) error { + if !w.isValidThread(threadID) { + return fmt.Errorf("no valid RawKV clients") + } + if threadID == 0 { + client := w.clients[threadID] + checksum, err := client.Checksum(ctx, []byte(WorkloadDefaultKey), []byte(WorkloadDefaultEndKey)) + if err != nil { + return nil + } else { + fmt.Printf("RawKV checksum: %d\n", checksum) + } + } return nil } From 08dcf3f1f8bc1746730f4d2ca3e9c4296807c0d3 Mon Sep 17 00:00:00 2001 From: lucasliang Date: Tue, 25 Jun 2024 18:55:33 +0800 Subject: [PATCH 16/30] Polish codes. Signed-off-by: lucasliang --- .../benchtool/config/{config.go => global.go} | 6 + examples/benchtool/config/rawkv.go | 46 +++++++ examples/benchtool/config/txn.go | 32 +++++ examples/benchtool/go.mod | 2 +- examples/benchtool/workloads/rawkv/rawkv.go | 116 +++++++----------- examples/benchtool/workloads/txnkv/txn.go | 103 ++++++---------- 6 files changed, 167 insertions(+), 138 deletions(-) rename examples/benchtool/config/{config.go => global.go} (96%) create mode 100644 examples/benchtool/config/rawkv.go create mode 100644 examples/benchtool/config/txn.go diff --git a/examples/benchtool/config/config.go b/examples/benchtool/config/global.go similarity index 96% rename from examples/benchtool/config/config.go rename to examples/benchtool/config/global.go index dbda851f48..d7941f393d 100644 --- a/examples/benchtool/config/config.go +++ b/examples/benchtool/config/global.go @@ -23,6 +23,12 @@ import ( "github.com/tikv/client-go/v2/config" ) +const ( + WorkloadColumnFamilyDefault = "CF_DEFAULT" + WorkloadColumnFamilyWrite = "CF_WRITE" + WorkloadColumnFamilyLock = "CF_LOCK" +) + type GlobalConfig struct { hosts []string port int diff --git a/examples/benchtool/config/rawkv.go b/examples/benchtool/config/rawkv.go new file mode 100644 index 0000000000..384a37e484 --- /dev/null +++ b/examples/benchtool/config/rawkv.go @@ -0,0 +1,46 @@ +// Copyright 2024 TiKV Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "fmt" + "time" +) + +type RawKVConfig struct { + KeySize int + ValueSize int + BatchSize int + + ColumnFamily string + CommandType string + PrepareRetryCount int + PrepareRetryInterval time.Duration + Randomize bool + + Global *GlobalConfig +} + +func (c *RawKVConfig) Validate() error { + if c.KeySize <= 0 || c.ValueSize <= 0 { + return fmt.Errorf("key size or value size must be greater than 0") + } + if c.ColumnFamily != WorkloadColumnFamilyDefault && + c.ColumnFamily != WorkloadColumnFamilyWrite && + c.ColumnFamily != WorkloadColumnFamilyLock { + return fmt.Errorf("invalid column family: %s", c.ColumnFamily) + } + return c.Global.ParsePdAddrs() +} diff --git a/examples/benchtool/config/txn.go b/examples/benchtool/config/txn.go new file mode 100644 index 0000000000..6b0e88cd9d --- /dev/null +++ b/examples/benchtool/config/txn.go @@ -0,0 +1,32 @@ +package config + +import ( + "benchtool/utils" + "fmt" + "time" +) + +type TxnKVConfig struct { + KeySize int + ValueSize int + ColumnSize int + TxnSize int + + PrepareRetryCount int + PrepareRetryInterval time.Duration + ReadWriteRatio *utils.ReadWriteRatio + TxnMode string + LockTimeout int + + Global *GlobalConfig +} + +func (c *TxnKVConfig) Validate() error { + if c.KeySize <= 0 || c.ValueSize <= 0 { + return fmt.Errorf("key size or value size must be greater than 0") + } + if err := c.ReadWriteRatio.ParseRatio(); err != nil { + return fmt.Errorf("parse read-write-ratio failed: %v", err) + } + return c.Global.ParsePdAddrs() +} diff --git a/examples/benchtool/go.mod b/examples/benchtool/go.mod index e127555045..fd245537c0 100644 --- a/examples/benchtool/go.mod +++ b/examples/benchtool/go.mod @@ -8,6 +8,7 @@ require ( github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.9.0 github.com/tikv/client-go/v2 v2.0.7 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -58,5 +59,4 @@ require ( google.golang.org/grpc v1.54.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/examples/benchtool/workloads/rawkv/rawkv.go b/examples/benchtool/workloads/rawkv/rawkv.go index 762c8eac08..54be8fd1de 100644 --- a/examples/benchtool/workloads/rawkv/rawkv.go +++ b/examples/benchtool/workloads/rawkv/rawkv.go @@ -44,49 +44,19 @@ const ( WorkloadDefaultKey = "rawkv_key" WorkloadDefaultEndKey = "rawkv_key`" WorkloadDefaultValue = "rawkv_value" - - WorkloadColumnFamilyDefault = "CF_DEFAULT" - WorkloadColumnFamilyWrite = "CF_WRITE" - WorkloadColumnFamilyLock = "CF_LOCK" ) func isReadCommand(cmd string) bool { return cmd == WorkloadTypeGet || cmd == WorkloadTypeBatchGet } -type RawKVConfig struct { - keySize int - valueSize int - batchSize int - - columnFamily string - commandType string - prepareRetryCount int - prepareRetryInterval time.Duration - randomize bool - - global *config.GlobalConfig -} - -func (c *RawKVConfig) Validate() error { - if c.keySize <= 0 || c.valueSize <= 0 { - return fmt.Errorf("key size or value size must be greater than 0") - } - if c.columnFamily != WorkloadColumnFamilyDefault && - c.columnFamily != WorkloadColumnFamilyWrite && - c.columnFamily != WorkloadColumnFamilyLock { - return fmt.Errorf("invalid column family: %s", c.columnFamily) - } - return c.global.ParsePdAddrs() -} - // Register registers the workload to the command line parser -func Register(command *config.CommandLineParser) *RawKVConfig { +func Register(command *config.CommandLineParser) *config.RawKVConfig { if command == nil { return nil } - rawKVConfig := &RawKVConfig{ - global: command.GetConfig(), + rawKVConfig := &config.RawKVConfig{ + Global: command.GetConfig(), } cmd := &cobra.Command{ @@ -95,12 +65,12 @@ func Register(command *config.CommandLineParser) *RawKVConfig { workloads.GlobalContext = context.WithValue(workloads.GlobalContext, WorkloadImplName, rawKVConfig) }, } - cmd.PersistentFlags().StringVar(&rawKVConfig.columnFamily, "cf", "default", "Column family name (default|write|lock)") - cmd.PersistentFlags().StringVar(&rawKVConfig.commandType, "cmd", "put", "Type of command to execute (put|get|del|batch_put|batch_get|batch_del|scan|reserve_scan|cas)") - cmd.PersistentFlags().IntVar(&rawKVConfig.keySize, "key-size", 1, "Size of key in bytes") - cmd.PersistentFlags().IntVar(&rawKVConfig.valueSize, "value-size", 1, "Size of value in bytes") - cmd.PersistentFlags().IntVar(&rawKVConfig.batchSize, "batch-size", 1, "Size of batch for batch operations") - cmd.PersistentFlags().BoolVar(&rawKVConfig.randomize, "random", false, "Whether to randomize each value") + cmd.PersistentFlags().StringVar(&rawKVConfig.ColumnFamily, "cf", "default", "Column family name (default|write|lock)") + cmd.PersistentFlags().StringVar(&rawKVConfig.CommandType, "cmd", "put", "Type of command to execute (put|get|del|batch_put|batch_get|batch_del|scan|reserve_scan|cas)") + cmd.PersistentFlags().IntVar(&rawKVConfig.KeySize, "key-size", 1, "Size of key in bytes") + cmd.PersistentFlags().IntVar(&rawKVConfig.ValueSize, "value-size", 1, "Size of value in bytes") + cmd.PersistentFlags().IntVar(&rawKVConfig.BatchSize, "batch-size", 1, "Size of batch for batch operations") + cmd.PersistentFlags().BoolVar(&rawKVConfig.Randomize, "random", false, "Whether to randomize each value") var cmdPrepare = &cobra.Command{ Use: "prepare", @@ -109,8 +79,8 @@ func Register(command *config.CommandLineParser) *RawKVConfig { execRawKV("prepare") }, } - cmdPrepare.PersistentFlags().IntVar(&rawKVConfig.prepareRetryCount, "retry-count", 50, "Retry count when errors occur") - cmdPrepare.PersistentFlags().DurationVar(&rawKVConfig.prepareRetryInterval, "retry-interval", 10*time.Millisecond, "The interval for each retry") + cmdPrepare.PersistentFlags().IntVar(&rawKVConfig.PrepareRetryCount, "retry-count", 50, "Retry count when errors occur") + cmdPrepare.PersistentFlags().DurationVar(&rawKVConfig.PrepareRetryInterval, "retry-interval", 10*time.Millisecond, "The interval for each retry") var cmdRun = &cobra.Command{ Use: "run", @@ -143,13 +113,13 @@ func Register(command *config.CommandLineParser) *RawKVConfig { return rawKVConfig } -func getRawKvConfig(ctx context.Context) *RawKVConfig { - c := ctx.Value(WorkloadImplName).(*RawKVConfig) +func getRawKvConfig(ctx context.Context) *config.RawKVConfig { + c := ctx.Value(WorkloadImplName).(*config.RawKVConfig) return c } type WorkloadImpl struct { - cfg *RawKVConfig + cfg *config.RawKVConfig clients []*rawkv.Client wait sync.WaitGroup @@ -157,7 +127,7 @@ type WorkloadImpl struct { stats *statistics.PerfProfile } -func NewRawKVWorkload(cfg *RawKVConfig) (*WorkloadImpl, error) { +func NewRawKVWorkload(cfg *config.RawKVConfig) (*WorkloadImpl, error) { if err := cfg.Validate(); err != nil { return nil, err } @@ -166,9 +136,9 @@ func NewRawKVWorkload(cfg *RawKVConfig) (*WorkloadImpl, error) { stats: statistics.NewPerfProfile(), } - w.clients = make([]*rawkv.Client, 0, w.cfg.global.Threads) - for i := 0; i < w.cfg.global.Threads; i++ { - client, err := rawkv.NewClient(workloads.GlobalContext, w.cfg.global.Targets, w.cfg.global.Security) + w.clients = make([]*rawkv.Client, 0, w.cfg.Global.Threads) + for i := 0; i < w.cfg.Global.Threads; i++ { + client, err := rawkv.NewClient(workloads.GlobalContext, w.cfg.Global.Targets, w.cfg.Global.Security) if err != nil { return nil, err } @@ -182,7 +152,7 @@ func (w *WorkloadImpl) Name() string { } func (w *WorkloadImpl) isValid() bool { - return w.cfg != nil && w.cfg.global != nil && len(w.clients) > 0 + return w.cfg != nil && w.cfg.Global != nil && len(w.clients) > 0 } func (w *WorkloadImpl) isValidThread(threadID int) bool { @@ -196,8 +166,8 @@ func (w *WorkloadImpl) InitThread(ctx context.Context, threadID int) error { return fmt.Errorf("no valid RawKV clients") } client := w.clients[threadID] - client.SetAtomicForCAS(w.cfg.commandType == WorkloadTypeCompareAndSwap) - client.SetColumnFamily(w.cfg.columnFamily) + client.SetAtomicForCAS(w.cfg.CommandType == WorkloadTypeCompareAndSwap) + client.SetColumnFamily(w.cfg.ColumnFamily) return nil } @@ -244,25 +214,25 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { vals [][]byte err error ) - switch w.cfg.commandType { + switch w.cfg.CommandType { case WorkloadTypePut, WorkloadTypeGet, WorkloadTypeDel, WorkloadTypeCompareAndSwap, WorkloadTypeScan, WorkloadTypeReverseScan: - if w.cfg.randomize { - key = utils.GenRandomStr(WorkloadDefaultKey, w.cfg.keySize) - if !isReadCommand(w.cfg.commandType) { - val = utils.GenRandomStr(WorkloadDefaultValue, w.cfg.valueSize) + if w.cfg.Randomize { + key = utils.GenRandomStr(WorkloadDefaultKey, w.cfg.KeySize) + if !isReadCommand(w.cfg.CommandType) { + val = utils.GenRandomStr(WorkloadDefaultValue, w.cfg.ValueSize) } } case WorkloadTypeBatchPut, WorkloadTypeBatchGet, WorkloadTypeBatchDel: - if w.cfg.randomize { - keys = utils.GenRandomByteArrs(WorkloadDefaultKey, w.cfg.keySize, w.cfg.batchSize) - if !isReadCommand(w.cfg.commandType) { - vals = utils.GenRandomByteArrs(WorkloadDefaultValue, w.cfg.valueSize, w.cfg.batchSize) + if w.cfg.Randomize { + keys = utils.GenRandomByteArrs(WorkloadDefaultKey, w.cfg.KeySize, w.cfg.BatchSize) + if !isReadCommand(w.cfg.CommandType) { + vals = utils.GenRandomByteArrs(WorkloadDefaultValue, w.cfg.ValueSize, w.cfg.BatchSize) } } } start := time.Now() - switch w.cfg.commandType { + switch w.cfg.CommandType { case WorkloadTypePut: err = client.Put(ctx, []byte(key), []byte(val)) case WorkloadTypeGet: @@ -280,14 +250,14 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { oldVal, _ = client.Get(ctx, []byte(key)) _, _, err = client.CompareAndSwap(ctx, []byte(key), []byte(oldVal), []byte(val)) // Experimental case WorkloadTypeScan: - _, _, err = client.Scan(ctx, []byte(key), []byte(WorkloadDefaultEndKey), w.cfg.batchSize) + _, _, err = client.Scan(ctx, []byte(key), []byte(WorkloadDefaultEndKey), w.cfg.BatchSize) case WorkloadTypeReverseScan: - _, _, err = client.ReverseScan(ctx, []byte(key), []byte(WorkloadDefaultKey), w.cfg.batchSize) + _, _, err = client.ReverseScan(ctx, []byte(key), []byte(WorkloadDefaultKey), w.cfg.BatchSize) } - if err != nil && !w.cfg.global.IgnoreError { - return fmt.Errorf("execute %s failed: %v", w.cfg.commandType, err) + if err != nil && !w.cfg.Global.IgnoreError { + return fmt.Errorf("execute %s failed: %v", w.cfg.CommandType, err) } - w.stats.Record(w.cfg.commandType, time.Since(start)) + w.stats.Record(w.cfg.CommandType, time.Since(start)) return nil } @@ -321,16 +291,16 @@ func (w *WorkloadImpl) Cleanup(ctx context.Context, threadID int) error { } func (w *WorkloadImpl) OutputStats(ifSummaryReport bool) { - w.stats.PrintFmt(ifSummaryReport, w.cfg.global.OutputStyle, statistics.HistogramOutputFunc) + w.stats.PrintFmt(ifSummaryReport, w.cfg.Global.OutputStyle, statistics.HistogramOutputFunc) } func (w *WorkloadImpl) Execute(cmd string) { - w.wait.Add(w.cfg.global.Threads) + w.wait.Add(w.cfg.Global.Threads) ctx, cancel := context.WithCancel(workloads.GlobalContext) ch := make(chan struct{}, 1) go func() { - ticker := time.NewTicker(w.cfg.global.OutputInterval) + ticker := time.NewTicker(w.cfg.Global.OutputInterval) defer ticker.Stop() for { @@ -344,11 +314,11 @@ func (w *WorkloadImpl) Execute(cmd string) { } }() - count := w.cfg.global.TotalCount / w.cfg.global.Threads - for i := 0; i < w.cfg.global.Threads; i++ { + count := w.cfg.Global.TotalCount / w.cfg.Global.Threads + for i := 0; i < w.cfg.Global.Threads; i++ { go func(index int) { defer w.wait.Done() - if err := workloads.DispatchExecution(ctx, w, cmd, count, index, w.cfg.global.Silence, w.cfg.global.IgnoreError); err != nil { + if err := workloads.DispatchExecution(ctx, w, cmd, count, index, w.cfg.Global.Silence, w.cfg.Global.IgnoreError); err != nil { fmt.Printf("[%s] execute %s failed, err %v\n", time.Now().Format("2006-01-02 15:04:05"), cmd, err) return } @@ -373,7 +343,7 @@ func execRawKV(cmd string) { return } - timeoutCtx, cancel := context.WithTimeout(workloads.GlobalContext, rawKVConfig.global.TotalTime) + timeoutCtx, cancel := context.WithTimeout(workloads.GlobalContext, rawKVConfig.Global.TotalTime) workloads.GlobalContext = timeoutCtx defer cancel() diff --git a/examples/benchtool/workloads/txnkv/txn.go b/examples/benchtool/workloads/txnkv/txn.go index 8685aec37e..d5b1d62bee 100644 --- a/examples/benchtool/workloads/txnkv/txn.go +++ b/examples/benchtool/workloads/txnkv/txn.go @@ -46,39 +46,14 @@ const ( WorkloadTxnModeAsyncCommit = "async-commit" ) -type TxnKVConfig struct { - keySize int - valueSize int - columnSize int - txnSize int - - prepareRetryCount int - prepareRetryInterval time.Duration - readWriteRatio *utils.ReadWriteRatio - txnMode string - lockTimeout int - - global *config.GlobalConfig -} - -func (c *TxnKVConfig) Validate() error { - if c.keySize <= 0 || c.valueSize <= 0 { - return fmt.Errorf("key size or value size must be greater than 0") - } - if err := c.readWriteRatio.ParseRatio(); err != nil { - return fmt.Errorf("parse read-write-ratio failed: %v", err) - } - return c.global.ParsePdAddrs() -} - // Register registers the workload to the command line parser -func Register(command *config.CommandLineParser) *TxnKVConfig { +func Register(command *config.CommandLineParser) *config.TxnKVConfig { if command == nil { return nil } - txnKVConfig := &TxnKVConfig{ - global: command.GetConfig(), - readWriteRatio: utils.NewReadWriteRatio("1:1"), // TODO: generate workloads meeting the read-write ratio + txnKVConfig := &config.TxnKVConfig{ + Global: command.GetConfig(), + ReadWriteRatio: utils.NewReadWriteRatio("1:1"), // TODO: generate workloads meeting the read-write ratio } cmd := &cobra.Command{ @@ -87,13 +62,13 @@ func Register(command *config.CommandLineParser) *TxnKVConfig { workloads.GlobalContext = context.WithValue(workloads.GlobalContext, WorkloadImplName, txnKVConfig) }, } - cmd.PersistentFlags().StringVar(&txnKVConfig.readWriteRatio.Ratio, "read-write-ratio", "1:1", "Read write ratio") - cmd.PersistentFlags().IntVar(&txnKVConfig.keySize, "key-size", 1, "Size of key in bytes") - cmd.PersistentFlags().IntVar(&txnKVConfig.valueSize, "value-size", 1, "Size of value in bytes") - cmd.PersistentFlags().IntVar(&txnKVConfig.columnSize, "column-size", 1, "Size of column") - cmd.PersistentFlags().IntVar(&txnKVConfig.txnSize, "txn-size", 1, "Size of transaction (normally, the lines of kv pairs)") - cmd.PersistentFlags().StringVar(&txnKVConfig.txnMode, "txn-mode", "2PC", "Mode of transaction (2PC/1PC/async-commit)") - cmd.PersistentFlags().IntVar(&txnKVConfig.lockTimeout, "lock-timeout", 0, "Lock timeout for each key in txn (>0 means pessimistic mode, 0 means optimistic mode)") + cmd.PersistentFlags().StringVar(&txnKVConfig.ReadWriteRatio.Ratio, "read-write-ratio", "1:1", "Read write ratio") + cmd.PersistentFlags().IntVar(&txnKVConfig.KeySize, "key-size", 1, "Size of key in bytes") + cmd.PersistentFlags().IntVar(&txnKVConfig.ValueSize, "value-size", 1, "Size of value in bytes") + cmd.PersistentFlags().IntVar(&txnKVConfig.ColumnSize, "column-size", 1, "Size of column") + cmd.PersistentFlags().IntVar(&txnKVConfig.TxnSize, "txn-size", 1, "Size of transaction (normally, the lines of kv pairs)") + cmd.PersistentFlags().StringVar(&txnKVConfig.TxnMode, "txn-mode", "2PC", "Mode of transaction (2PC/1PC/async-commit)") + cmd.PersistentFlags().IntVar(&txnKVConfig.LockTimeout, "lock-timeout", 0, "Lock timeout for each key in txn (>0 means pessimistic mode, 0 means optimistic mode)") // TODO: add more flags on txn, such as pessimistic/optimistic lock, etc. var cmdPrepare = &cobra.Command{ @@ -103,8 +78,8 @@ func Register(command *config.CommandLineParser) *TxnKVConfig { execTxnKV("prepare") }, } - cmdPrepare.PersistentFlags().IntVar(&txnKVConfig.prepareRetryCount, "retry-count", 50, "Retry count when errors occur") - cmdPrepare.PersistentFlags().DurationVar(&txnKVConfig.prepareRetryInterval, "retry-interval", 10*time.Millisecond, "The interval for each retry") + cmdPrepare.PersistentFlags().IntVar(&txnKVConfig.PrepareRetryCount, "retry-count", 50, "Retry count when errors occur") + cmdPrepare.PersistentFlags().DurationVar(&txnKVConfig.PrepareRetryInterval, "retry-interval", 10*time.Millisecond, "The interval for each retry") var cmdRun = &cobra.Command{ Use: "run", @@ -137,8 +112,8 @@ func Register(command *config.CommandLineParser) *TxnKVConfig { return txnKVConfig } -func getTxnKVConfig(ctx context.Context) *TxnKVConfig { - c := ctx.Value(WorkloadImplName).(*TxnKVConfig) +func getTxnKVConfig(ctx context.Context) *config.TxnKVConfig { + c := ctx.Value(WorkloadImplName).(*config.TxnKVConfig) return c } @@ -152,7 +127,7 @@ func prepareLockKeyWithTimeout(ctx context.Context, txn *clientTxnKV.KVTxn, key // Workload is the implementation of WorkloadInterface type WorkloadImpl struct { - cfg *TxnKVConfig + cfg *config.TxnKVConfig clients []*clientTxnKV.Client wait sync.WaitGroup @@ -160,7 +135,7 @@ type WorkloadImpl struct { stats *statistics.PerfProfile } -func NewTxnKVWorkload(cfg *TxnKVConfig) (*WorkloadImpl, error) { +func NewTxnKVWorkload(cfg *config.TxnKVConfig) (*WorkloadImpl, error) { if err := cfg.Validate(); err != nil { return nil, err } @@ -170,7 +145,7 @@ func NewTxnKVWorkload(cfg *TxnKVConfig) (*WorkloadImpl, error) { } clientConfig.UpdateGlobal(func(conf *clientConfig.Config) { - conf.TiKVClient.MaxBatchSize = (uint)(cfg.txnSize + 10) + conf.TiKVClient.MaxBatchSize = (uint)(cfg.TxnSize + 10) }) // TODO: setting batch. // defer config.UpdateGlobal(func(conf *config.Config) { @@ -178,9 +153,9 @@ func NewTxnKVWorkload(cfg *TxnKVConfig) (*WorkloadImpl, error) { // conf.TiKVClient.GrpcConnectionCount = 1 // })() - w.clients = make([]*clientTxnKV.Client, 0, w.cfg.global.Threads) - for i := 0; i < w.cfg.global.Threads; i++ { - client, err := clientTxnKV.NewClient(w.cfg.global.Targets) + w.clients = make([]*clientTxnKV.Client, 0, w.cfg.Global.Threads) + for i := 0; i < w.cfg.Global.Threads; i++ { + client, err := clientTxnKV.NewClient(w.cfg.Global.Targets) if err != nil { return nil, err } @@ -194,7 +169,7 @@ func (w *WorkloadImpl) Name() string { } func (w *WorkloadImpl) isValid() bool { - return w.cfg != nil && w.cfg.global != nil && len(w.clients) > 0 + return w.cfg != nil && w.cfg.Global != nil && len(w.clients) > 0 } func (w *WorkloadImpl) isValidThread(threadID int) bool { @@ -240,15 +215,15 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { client := w.clients[threadID] key := WorkloadDefaultKey - val := utils.GenRandomStr(WorkloadDefaultValue, w.cfg.valueSize) - lockTimeout := int64(w.cfg.lockTimeout) + val := utils.GenRandomStr(WorkloadDefaultValue, w.cfg.ValueSize) + lockTimeout := int64(w.cfg.LockTimeout) // Constructs the txn client and sets the txn mode txn, err := client.Begin() if err != nil { return fmt.Errorf("txn begin failed, err %v", err) } - switch w.cfg.txnMode { + switch w.cfg.TxnMode { case WorkloadTxnMode1PC: txn.SetEnable1PC(true) case WorkloadTxnModeAsyncCommit: @@ -258,21 +233,21 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { txn.SetPessimistic(lockTimeout > 0) - sum := w.cfg.txnSize * w.cfg.columnSize - readCount := sum * w.cfg.readWriteRatio.GetPercent(utils.ReadPercent) / 100 + sum := w.cfg.TxnSize * w.cfg.ColumnSize + readCount := sum * w.cfg.ReadWriteRatio.GetPercent(utils.ReadPercent) / 100 writeCount := sum - readCount canRead := func(sum, readCount, writeCount int) bool { return readCount > 0 && (writeCount <= 0 || rand.Intn(sum)/2 == 0) } - for row := 0; row < w.cfg.txnSize; row++ { - key = fmt.Sprintf("%s@col_", utils.GenRandomStr(key, w.cfg.keySize)) + for row := 0; row < w.cfg.TxnSize; row++ { + key = fmt.Sprintf("%s@col_", utils.GenRandomStr(key, w.cfg.KeySize)) // Lock the key with timeout if necessary. if err = prepareLockKeyWithTimeout(ctx, txn, []byte(key), lockTimeout); err != nil { fmt.Printf("txn lock key failed, err %v", err) continue } - for col := 0; col < w.cfg.columnSize; col++ { + for col := 0; col < w.cfg.ColumnSize; col++ { colKey := fmt.Sprintf("%s%d", key, col) if canRead(sum, readCount, writeCount) { _, err = txn.Get(ctx, []byte(colKey)) @@ -295,7 +270,7 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { if err != nil { return fmt.Errorf("txn commit failed, err %v", err) } - w.stats.Record(w.cfg.txnMode, time.Since(start)) + w.stats.Record(w.cfg.TxnMode, time.Since(start)) return nil } @@ -311,22 +286,22 @@ func (w *WorkloadImpl) Cleanup(ctx context.Context, threadID int) error { } if threadID == 0 { client := w.clients[threadID] - client.DeleteRange(ctx, []byte(WorkloadDefaultKey), []byte(WorkloadDefaultEndKey), w.cfg.global.Threads) // delete all keys + client.DeleteRange(ctx, []byte(WorkloadDefaultKey), []byte(WorkloadDefaultEndKey), w.cfg.Global.Threads) // delete all keys } return nil } func (w *WorkloadImpl) OutputStats(ifSummaryReport bool) { - w.stats.PrintFmt(ifSummaryReport, w.cfg.global.OutputStyle, statistics.HistogramOutputFunc) + w.stats.PrintFmt(ifSummaryReport, w.cfg.Global.OutputStyle, statistics.HistogramOutputFunc) } func (w *WorkloadImpl) Execute(cmd string) { - w.wait.Add(w.cfg.global.Threads) + w.wait.Add(w.cfg.Global.Threads) ctx, cancel := context.WithCancel(workloads.GlobalContext) ch := make(chan struct{}, 1) go func() { - ticker := time.NewTicker(w.cfg.global.OutputInterval) + ticker := time.NewTicker(w.cfg.Global.OutputInterval) defer ticker.Stop() for { @@ -340,11 +315,11 @@ func (w *WorkloadImpl) Execute(cmd string) { } }() - count := w.cfg.global.TotalCount / w.cfg.global.Threads - for i := 0; i < w.cfg.global.Threads; i++ { + count := w.cfg.Global.TotalCount / w.cfg.Global.Threads + for i := 0; i < w.cfg.Global.Threads; i++ { go func(index int) { defer w.wait.Done() - if err := workloads.DispatchExecution(ctx, w, cmd, count, index, w.cfg.global.Silence, w.cfg.global.IgnoreError); err != nil { + if err := workloads.DispatchExecution(ctx, w, cmd, count, index, w.cfg.Global.Silence, w.cfg.Global.IgnoreError); err != nil { fmt.Printf("[%s] execute %s failed, err %v\n", time.Now().Format("2006-01-02 15:04:05"), cmd, err) return } @@ -369,7 +344,7 @@ func execTxnKV(cmd string) { return } - timeoutCtx, cancel := context.WithTimeout(workloads.GlobalContext, TxnKVConfig.global.TotalTime) + timeoutCtx, cancel := context.WithTimeout(workloads.GlobalContext, TxnKVConfig.Global.TotalTime) workloads.GlobalContext = timeoutCtx defer cancel() From 769d50a106267080da22040a50076ffced64ff17 Mon Sep 17 00:00:00 2001 From: lucasliang Date: Wed, 26 Jun 2024 19:14:28 +0800 Subject: [PATCH 17/30] Refactor and polish codes. Signed-off-by: lucasliang --- examples/benchtool/config/rawkv.go | 20 ++++++ examples/benchtool/config/txn.go | 17 +++++ examples/benchtool/go.mod | 2 + examples/benchtool/utils/statistics/misc.go | 27 ++++++-- examples/benchtool/workloads/rawkv/rawkv.go | 72 ++++++++------------- examples/benchtool/workloads/txnkv/txn.go | 33 +++------- 6 files changed, 98 insertions(+), 73 deletions(-) diff --git a/examples/benchtool/config/rawkv.go b/examples/benchtool/config/rawkv.go index 384a37e484..6572841cb0 100644 --- a/examples/benchtool/config/rawkv.go +++ b/examples/benchtool/config/rawkv.go @@ -19,6 +19,26 @@ import ( "time" ) +const ( + WorkloadTypeRawKV = "rawkv" +) + +const ( + RawKVCommandTypePut = "put" + RawKVCommandTypeGet = "get" + RawKVCommandTypeDel = "del" + RawKVCommandTypeBatchPut = "batch_put" + RawKVCommandTypeBatchGet = "batch_get" + RawKVCommandTypeBatchDel = "batch_del" + RawKVCommandTypeScan = "scan" + RawKVCommandTypeReverseScan = "reverse_scan" + RawKVCommandTypeCAS = "cas" + + RawKVCommandDefaultKey = "rawkv_key" + RawKVCommandDefaultEndKey = "rawkv_key`" + RawKVCommandDefaultValue = "rawkv_value" +) + type RawKVConfig struct { KeySize int ValueSize int diff --git a/examples/benchtool/config/txn.go b/examples/benchtool/config/txn.go index 6b0e88cd9d..ca8ac50877 100644 --- a/examples/benchtool/config/txn.go +++ b/examples/benchtool/config/txn.go @@ -6,6 +6,23 @@ import ( "time" ) +const ( + WorkloadTypeTxnKV = "txnkv" +) + +const ( + TxnKVCommandTypeWrite = "write" + TxnKVCommandTypeRead = "read" + + TxnKVCommandDefaultKey = "txnkv_key" + TxnKVCommandDefaultEndKey = "txnkv_key`" + TxnKVCommandDefaultValue = "txnkv_value" + + TxnKVModeDefault = "2PC" + TxnKVMode1PC = "1PC" + TxnKVModeAsyncCommit = "async-commit" +) + type TxnKVConfig struct { KeySize int ValueSize int diff --git a/examples/benchtool/go.mod b/examples/benchtool/go.mod index fd245537c0..f783f785e9 100644 --- a/examples/benchtool/go.mod +++ b/examples/benchtool/go.mod @@ -9,6 +9,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/tikv/client-go/v2 v2.0.7 gopkg.in/yaml.v3 v3.0.1 + gotest.tools/v3 v3.5.1 ) require ( @@ -24,6 +25,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.1.2 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/google/uuid v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/examples/benchtool/utils/statistics/misc.go b/examples/benchtool/utils/statistics/misc.go index 86ffdf8764..3357280360 100644 --- a/examples/benchtool/utils/statistics/misc.go +++ b/examples/benchtool/utils/statistics/misc.go @@ -20,8 +20,10 @@ import ( ) const ( - defaultMinLatency = 1 * time.Millisecond + DefaultMinLatency = 1 * time.Millisecond DefaultMaxLatency = 16 * time.Second + + DefaultHistogramSize = 16 ) type PerfProfile struct { @@ -36,11 +38,11 @@ type PerfProfile struct { func NewPerfProfile() *PerfProfile { return &PerfProfile{ - MinLatency: defaultMinLatency, + MinLatency: DefaultMinLatency, MaxLatency: DefaultMaxLatency, SigFigs: 1, - PeriodicalPerfHist: make(map[string]*PerfHistogram, 16), - SummaryPerfHist: make(map[string]*PerfHistogram, 16), + PeriodicalPerfHist: make(map[string]*PerfHistogram, DefaultHistogramSize), + SummaryPerfHist: make(map[string]*PerfHistogram, DefaultHistogramSize), } } @@ -100,3 +102,20 @@ func (p *PerfProfile) PrintFmt(ifSummaryReport bool, outputStyle string, outputF defer p.RUnlock() outputFunc(outputStyle, "[Current] ", periodicalHist) } + +func (p *PerfProfile) Clear() { + p.Lock() + defer p.Unlock() + + perfHist := p.PeriodicalPerfHist + for k := range perfHist { + delete(perfHist, k) + } + perfHist = p.SummaryPerfHist + for k := range perfHist { + delete(perfHist, k) + } + + p.PeriodicalPerfHist = make(map[string]*PerfHistogram, DefaultHistogramSize) + p.SummaryPerfHist = make(map[string]*PerfHistogram, DefaultHistogramSize) +} diff --git a/examples/benchtool/workloads/rawkv/rawkv.go b/examples/benchtool/workloads/rawkv/rawkv.go index 54be8fd1de..25b867a80a 100644 --- a/examples/benchtool/workloads/rawkv/rawkv.go +++ b/examples/benchtool/workloads/rawkv/rawkv.go @@ -28,26 +28,8 @@ import ( "github.com/tikv/client-go/v2/rawkv" ) -const ( - WorkloadImplName = "rawkv" - - WorkloadTypePut = "put" - WorkloadTypeGet = "get" - WorkloadTypeDel = "del" - WorkloadTypeBatchPut = "batch_put" - WorkloadTypeBatchGet = "batch_get" - WorkloadTypeBatchDel = "batch_del" - WorkloadTypeScan = "scan" - WorkloadTypeReverseScan = "reverse_scan" - WorkloadTypeCompareAndSwap = "cas" - - WorkloadDefaultKey = "rawkv_key" - WorkloadDefaultEndKey = "rawkv_key`" - WorkloadDefaultValue = "rawkv_value" -) - func isReadCommand(cmd string) bool { - return cmd == WorkloadTypeGet || cmd == WorkloadTypeBatchGet + return cmd == config.RawKVCommandTypeGet || cmd == config.RawKVCommandTypeBatchGet } // Register registers the workload to the command line parser @@ -60,9 +42,9 @@ func Register(command *config.CommandLineParser) *config.RawKVConfig { } cmd := &cobra.Command{ - Use: WorkloadImplName, + Use: config.WorkloadTypeRawKV, PersistentPreRun: func(cmd *cobra.Command, args []string) { - workloads.GlobalContext = context.WithValue(workloads.GlobalContext, WorkloadImplName, rawKVConfig) + workloads.GlobalContext = context.WithValue(workloads.GlobalContext, config.WorkloadTypeRawKV, rawKVConfig) }, } cmd.PersistentFlags().StringVar(&rawKVConfig.ColumnFamily, "cf", "default", "Column family name (default|write|lock)") @@ -114,7 +96,7 @@ func Register(command *config.CommandLineParser) *config.RawKVConfig { } func getRawKvConfig(ctx context.Context) *config.RawKVConfig { - c := ctx.Value(WorkloadImplName).(*config.RawKVConfig) + c := ctx.Value(config.WorkloadTypeRawKV).(*config.RawKVConfig) return c } @@ -148,7 +130,7 @@ func NewRawKVWorkload(cfg *config.RawKVConfig) (*WorkloadImpl, error) { } func (w *WorkloadImpl) Name() string { - return WorkloadImplName + return config.WorkloadTypeRawKV } func (w *WorkloadImpl) isValid() bool { @@ -166,7 +148,7 @@ func (w *WorkloadImpl) InitThread(ctx context.Context, threadID int) error { return fmt.Errorf("no valid RawKV clients") } client := w.clients[threadID] - client.SetAtomicForCAS(w.cfg.CommandType == WorkloadTypeCompareAndSwap) + client.SetAtomicForCAS(w.cfg.CommandType == config.RawKVCommandTypeCAS) client.SetColumnFamily(w.cfg.ColumnFamily) return nil } @@ -205,8 +187,8 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { client := w.clients[threadID] // For unary operations. - key := WorkloadDefaultKey - val := WorkloadDefaultValue + key := config.RawKVCommandDefaultKey + val := config.RawKVCommandDefaultValue // For batch operations. var ( @@ -215,44 +197,44 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { err error ) switch w.cfg.CommandType { - case WorkloadTypePut, WorkloadTypeGet, WorkloadTypeDel, WorkloadTypeCompareAndSwap, WorkloadTypeScan, WorkloadTypeReverseScan: + case config.RawKVCommandTypePut, config.RawKVCommandTypeGet, config.RawKVCommandTypeDel, config.RawKVCommandTypeCAS, config.RawKVCommandTypeScan, config.RawKVCommandTypeReverseScan: if w.cfg.Randomize { - key = utils.GenRandomStr(WorkloadDefaultKey, w.cfg.KeySize) + key = utils.GenRandomStr(config.RawKVCommandDefaultKey, w.cfg.KeySize) if !isReadCommand(w.cfg.CommandType) { - val = utils.GenRandomStr(WorkloadDefaultValue, w.cfg.ValueSize) + val = utils.GenRandomStr(config.RawKVCommandDefaultValue, w.cfg.ValueSize) } } - case WorkloadTypeBatchPut, WorkloadTypeBatchGet, WorkloadTypeBatchDel: + case config.RawKVCommandTypeBatchPut, config.RawKVCommandTypeBatchGet, config.RawKVCommandTypeBatchDel: if w.cfg.Randomize { - keys = utils.GenRandomByteArrs(WorkloadDefaultKey, w.cfg.KeySize, w.cfg.BatchSize) + keys = utils.GenRandomByteArrs(config.RawKVCommandDefaultKey, w.cfg.KeySize, w.cfg.BatchSize) if !isReadCommand(w.cfg.CommandType) { - vals = utils.GenRandomByteArrs(WorkloadDefaultValue, w.cfg.ValueSize, w.cfg.BatchSize) + vals = utils.GenRandomByteArrs(config.RawKVCommandDefaultValue, w.cfg.ValueSize, w.cfg.BatchSize) } } } start := time.Now() switch w.cfg.CommandType { - case WorkloadTypePut: + case config.RawKVCommandTypePut: err = client.Put(ctx, []byte(key), []byte(val)) - case WorkloadTypeGet: + case config.RawKVCommandTypeGet: _, err = client.Get(ctx, []byte(key)) - case WorkloadTypeDel: + case config.RawKVCommandTypeDel: err = client.Delete(ctx, []byte(key)) - case WorkloadTypeBatchPut: + case config.RawKVCommandTypeBatchPut: err = client.BatchPut(ctx, keys, vals) - case WorkloadTypeBatchGet: + case config.RawKVCommandTypeBatchGet: _, err = client.BatchGet(ctx, keys) - case WorkloadTypeBatchDel: + case config.RawKVCommandTypeBatchDel: err = client.BatchDelete(ctx, keys) - case WorkloadTypeCompareAndSwap: + case config.RawKVCommandTypeCAS: var oldVal []byte oldVal, _ = client.Get(ctx, []byte(key)) _, _, err = client.CompareAndSwap(ctx, []byte(key), []byte(oldVal), []byte(val)) // Experimental - case WorkloadTypeScan: - _, _, err = client.Scan(ctx, []byte(key), []byte(WorkloadDefaultEndKey), w.cfg.BatchSize) - case WorkloadTypeReverseScan: - _, _, err = client.ReverseScan(ctx, []byte(key), []byte(WorkloadDefaultKey), w.cfg.BatchSize) + case config.RawKVCommandTypeScan: + _, _, err = client.Scan(ctx, []byte(key), []byte(config.RawKVCommandDefaultEndKey), w.cfg.BatchSize) + case config.RawKVCommandTypeReverseScan: + _, _, err = client.ReverseScan(ctx, []byte(key), []byte(config.RawKVCommandDefaultKey), w.cfg.BatchSize) } if err != nil && !w.cfg.Global.IgnoreError { return fmt.Errorf("execute %s failed: %v", w.cfg.CommandType, err) @@ -268,7 +250,7 @@ func (w *WorkloadImpl) Check(ctx context.Context, threadID int) error { } if threadID == 0 { client := w.clients[threadID] - checksum, err := client.Checksum(ctx, []byte(WorkloadDefaultKey), []byte(WorkloadDefaultEndKey)) + checksum, err := client.Checksum(ctx, []byte(config.RawKVCommandDefaultKey), []byte(config.RawKVCommandDefaultEndKey)) if err != nil { return nil } else { @@ -285,7 +267,7 @@ func (w *WorkloadImpl) Cleanup(ctx context.Context, threadID int) error { } if threadID == 0 { client := w.clients[threadID] - client.DeleteRange(ctx, []byte(WorkloadDefaultKey), []byte(WorkloadDefaultEndKey)) // delete all keys + client.DeleteRange(ctx, []byte(config.RawKVCommandDefaultKey), []byte(config.RawKVCommandDefaultEndKey)) // delete all keys } return nil } diff --git a/examples/benchtool/workloads/txnkv/txn.go b/examples/benchtool/workloads/txnkv/txn.go index d5b1d62bee..63cbc0e5f5 100644 --- a/examples/benchtool/workloads/txnkv/txn.go +++ b/examples/benchtool/workloads/txnkv/txn.go @@ -31,21 +31,6 @@ import ( clientTxnKV "github.com/tikv/client-go/v2/txnkv" ) -const ( - WorkloadImplName = "txnkv" - - WorkloadTypeWrite = "write" - WorkloadTypeRead = "read" - - WorkloadDefaultKey = "txnkv_key" - WorkloadDefaultEndKey = "txnkv_key`" - WorkloadDefaultValue = "txnkv_value" - - WorkloadTxnModeDefault = "2PC" - WorkloadTxnMode1PC = "1PC" - WorkloadTxnModeAsyncCommit = "async-commit" -) - // Register registers the workload to the command line parser func Register(command *config.CommandLineParser) *config.TxnKVConfig { if command == nil { @@ -57,9 +42,9 @@ func Register(command *config.CommandLineParser) *config.TxnKVConfig { } cmd := &cobra.Command{ - Use: WorkloadImplName, + Use: config.WorkloadTypeTxnKV, PersistentPreRun: func(cmd *cobra.Command, args []string) { - workloads.GlobalContext = context.WithValue(workloads.GlobalContext, WorkloadImplName, txnKVConfig) + workloads.GlobalContext = context.WithValue(workloads.GlobalContext, config.WorkloadTypeTxnKV, txnKVConfig) }, } cmd.PersistentFlags().StringVar(&txnKVConfig.ReadWriteRatio.Ratio, "read-write-ratio", "1:1", "Read write ratio") @@ -113,7 +98,7 @@ func Register(command *config.CommandLineParser) *config.TxnKVConfig { } func getTxnKVConfig(ctx context.Context) *config.TxnKVConfig { - c := ctx.Value(WorkloadImplName).(*config.TxnKVConfig) + c := ctx.Value(config.WorkloadTypeTxnKV).(*config.TxnKVConfig) return c } @@ -165,7 +150,7 @@ func NewTxnKVWorkload(cfg *config.TxnKVConfig) (*WorkloadImpl, error) { } func (w *WorkloadImpl) Name() string { - return WorkloadImplName + return config.WorkloadTypeTxnKV } func (w *WorkloadImpl) isValid() bool { @@ -214,8 +199,8 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { } client := w.clients[threadID] - key := WorkloadDefaultKey - val := utils.GenRandomStr(WorkloadDefaultValue, w.cfg.ValueSize) + key := config.TxnKVCommandDefaultKey + val := utils.GenRandomStr(config.TxnKVCommandDefaultValue, w.cfg.ValueSize) lockTimeout := int64(w.cfg.LockTimeout) // Constructs the txn client and sets the txn mode @@ -224,9 +209,9 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { return fmt.Errorf("txn begin failed, err %v", err) } switch w.cfg.TxnMode { - case WorkloadTxnMode1PC: + case config.TxnKVMode1PC: txn.SetEnable1PC(true) - case WorkloadTxnModeAsyncCommit: + case config.TxnKVModeAsyncCommit: txn.SetEnableAsyncCommit(true) } // Default is optimistic lock mode. @@ -286,7 +271,7 @@ func (w *WorkloadImpl) Cleanup(ctx context.Context, threadID int) error { } if threadID == 0 { client := w.clients[threadID] - client.DeleteRange(ctx, []byte(WorkloadDefaultKey), []byte(WorkloadDefaultEndKey), w.cfg.Global.Threads) // delete all keys + client.DeleteRange(ctx, []byte(config.TxnKVCommandDefaultKey), []byte(config.TxnKVCommandDefaultEndKey), w.cfg.Global.Threads) // delete all keys } return nil } From 2733847fa6999e97599603ebc54a1e0e6044a7ea Mon Sep 17 00:00:00 2001 From: lucasliang Date: Thu, 27 Jun 2024 10:28:23 +0800 Subject: [PATCH 18/30] Add basical framwork for supporting loading manual specifying workloads pattern. Signed-off-by: lucasliang --- examples/benchtool/config/pattern.go | 154 ++++++++ examples/benchtool/main.go | 2 + examples/benchtool/test.yaml | 29 ++ .../benchtool/workloads/patterns/pattern.go | 333 ++++++++++++++++++ .../workloads/patterns/pattern_test.go | 43 +++ 5 files changed, 561 insertions(+) create mode 100644 examples/benchtool/config/pattern.go create mode 100644 examples/benchtool/test.yaml create mode 100644 examples/benchtool/workloads/patterns/pattern.go create mode 100644 examples/benchtool/workloads/patterns/pattern_test.go diff --git a/examples/benchtool/config/pattern.go b/examples/benchtool/config/pattern.go new file mode 100644 index 0000000000..1877386d1a --- /dev/null +++ b/examples/benchtool/config/pattern.go @@ -0,0 +1,154 @@ +// Copyright 2024 TiKV Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +const ( + WorkloadTypeHybrid = "hybrid" +) + +// TODO: convert the txnConfig and rawkvConfig to interfaces +type SubPatternConfig struct { + txnConfig *TxnKVConfig + rawkvConfig *RawKVConfig + workloads []string + name string +} + +func (s *SubPatternConfig) GetName() string { + return s.name +} + +func (s *SubPatternConfig) GetWorkloads() []string { + return s.workloads +} + +func (s *SubPatternConfig) GetTxnKVConfig() *TxnKVConfig { + return s.txnConfig +} + +func (s *SubPatternConfig) GetRawKVConfig() *RawKVConfig { + return s.rawkvConfig +} + +type SubPattern struct { + Name string `yaml:"name,omitempty"` + WorkloadType string `yaml:"workload_type,omitempty"` + Workloads []string `yaml:"workloads,omitempty"` + + // for txnkv + Mode string `yaml:"mode,omitempty"` + LockTimeout int `yaml:"lock_timeout"` + ColumnSize int `yaml:"column_size"` + TxnSize int `yaml:"txn_size"` + // common + Count int `yaml:"count"` + KeyPrefix string `yaml:"key_prefix,omitempty"` + KeySize int `yaml:"key_size"` + ValueSize int `yaml:"value_size"` + BatchSize int `yaml:"batch_size"` + Threads int `yaml:"threads"` + Randomize bool `yaml:"random"` +} + +func (s *SubPattern) ConvertBasedOn(global *GlobalConfig) *SubPatternConfig { + // Invalid workloads + if s.Workloads == nil { + return nil + } + + globalCfg := &GlobalConfig{} + if global != nil { + globalCfg = global + } + globalCfg.TotalCount = s.Count + globalCfg.Threads = s.Threads + + switch s.WorkloadType { + case WorkloadTypeTxnKV: + config := &TxnKVConfig{ + TxnMode: s.Mode, + LockTimeout: s.LockTimeout, + // KeyPrefix: s.key_prefix, + KeySize: s.KeySize, + ValueSize: s.ValueSize, + ColumnSize: s.ColumnSize, + TxnSize: s.TxnSize, + } + config.Global = globalCfg + return &SubPatternConfig{ + txnConfig: config, + workloads: s.Workloads, + name: s.Name, + } + case WorkloadTypeRawKV: + config := &RawKVConfig{ + // KeyPrefix: s.key_prefix, + KeySize: s.KeySize, + BatchSize: s.BatchSize, + ValueSize: s.ValueSize, + Randomize: s.Randomize, + } + config.Global = globalCfg + return &SubPatternConfig{ + rawkvConfig: config, + workloads: s.Workloads, + name: s.Name, + } + } + return nil +} + +type PatternsConfig struct { + Items []*SubPattern `yaml:"patterns"` + + FilePath string + + Plans []*SubPatternConfig + Global *GlobalConfig +} + +// Parse parses the yaml file. +func (p *PatternsConfig) Parse() error { + data, err := os.ReadFile(p.FilePath) + if err != nil { + return err + } + err = yaml.Unmarshal(data, p) + if err != nil { + return err + } + p.Plans = make([]*SubPatternConfig, 0, len(p.Items)) + for _, item := range p.Items { + p.Plans = append(p.Plans, item.ConvertBasedOn(p.Global)) + } + return nil +} + +func (p *PatternsConfig) Validate() error { + if p.Global == nil { + return fmt.Errorf("global config is missing") + } + if p.Items == nil { + return fmt.Errorf("patterns config is missing") + } + return p.Global.ParsePdAddrs() +} diff --git a/examples/benchtool/main.go b/examples/benchtool/main.go index 7f68e4cce7..ff5875af1d 100644 --- a/examples/benchtool/main.go +++ b/examples/benchtool/main.go @@ -17,6 +17,7 @@ package main import ( "benchtool/config" "benchtool/workloads" + "benchtool/workloads/patterns" "benchtool/workloads/rawkv" "benchtool/workloads/txnkv" "context" @@ -39,6 +40,7 @@ func main() { // TODO: add more workloads rawkv.Register(commandLineParser) txnkv.Register(commandLineParser) + patterns.Register(commandLineParser) var cancel context.CancelFunc workloads.GlobalContext, cancel = context.WithCancel(context.Background()) diff --git a/examples/benchtool/test.yaml b/examples/benchtool/test.yaml new file mode 100644 index 0000000000..a7829be217 --- /dev/null +++ b/examples/benchtool/test.yaml @@ -0,0 +1,29 @@ +patterns: + - name: codecov + workload_type: rawkv + workloads: + - batch_get + - batch_write + - get + - put + - scan + - update + key_prefix: "codecov" # default is ${name} + count: 10000 + key_size: 1024 + value_size: 1024 + threads: 10 + random: true # default is false + - name: txn + workload_type: txnkv + workloads: + - begin + - commit + - rollback + mode: 2PC # (async_commit | 1PC | 2PC) + lock: pessimistic # (optimistic | pessimistic) + count: 10000 + key_size: 1024 + value_size: 1024 + threads: 10 + random: true # default is false \ No newline at end of file diff --git a/examples/benchtool/workloads/patterns/pattern.go b/examples/benchtool/workloads/patterns/pattern.go new file mode 100644 index 0000000000..b694c85745 --- /dev/null +++ b/examples/benchtool/workloads/patterns/pattern.go @@ -0,0 +1,333 @@ +// Copyright 2024 TiKV Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package patterns + +import ( + "benchtool/config" + "benchtool/utils/statistics" + "benchtool/workloads" + "context" + "fmt" + "sync" + "time" + + "github.com/spf13/cobra" + clientConfig "github.com/tikv/client-go/v2/config" + clientRawKV "github.com/tikv/client-go/v2/rawkv" + clientTxnKV "github.com/tikv/client-go/v2/txnkv" +) + +// Register registers the workload to the command line parser +func Register(command *config.CommandLineParser) *config.PatternsConfig { + if command == nil { + return nil + } + patternsConfig := &config.PatternsConfig{ + Global: command.GetConfig(), + } + + cmd := &cobra.Command{ + Use: config.WorkloadTypeHybrid, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + workloads.GlobalContext = context.WithValue(workloads.GlobalContext, config.WorkloadTypeHybrid, patternsConfig) + }, + } + cmd.PersistentFlags().StringVar(&patternsConfig.FilePath, "file-path", "", "The path of the patterns file") + + var cmdPrepare = &cobra.Command{ + Use: "prepare", + Short: "Prepare data for TxnKV workload", + Run: func(cmd *cobra.Command, _ []string) { + execTxnKV("prepare") + }, + } + var cmdRun = &cobra.Command{ + Use: "run", + Short: "Run workload", + Run: func(cmd *cobra.Command, _ []string) { + execTxnKV("run") + }, + } + var cmdCleanup = &cobra.Command{ + Use: "cleanup", + Short: "Cleanup data for the workload", + Run: func(cmd *cobra.Command, _ []string) { + execTxnKV("cleanup") + }, + } + var cmdCheck = &cobra.Command{ + Use: "check", + Short: "Check data consistency for the workload", + Run: func(cmd *cobra.Command, _ []string) { + execTxnKV("check") + }, + } + cmd.AddCommand(cmdRun, cmdPrepare, cmdCleanup, cmdCheck) + + command.GetCommand().AddCommand(cmd) + + return patternsConfig +} + +func getPatternsConfig(ctx context.Context) *config.PatternsConfig { + c := ctx.Value(config.WorkloadTypeHybrid).(*config.PatternsConfig) + return c +} + +// Assistants for TxnKV workload +func prepareLockKeyWithTimeout(ctx context.Context, txn *clientTxnKV.KVTxn, key []byte, timeout int64) error { + if timeout > 0 { + return txn.LockKeysWithWaitTime(ctx, timeout, key) + } + return nil +} + +// Workload is the implementation of WorkloadInterface +type WorkloadImpl struct { + // Pointer to the next execution plan + patternIdx int + // workload pattern + config *config.PatternsConfig + + rawClients []*clientRawKV.Client + txnClients []*clientTxnKV.Client + + stats *statistics.PerfProfile + + wait sync.WaitGroup +} + +func NewPatternWorkload(cfg *config.PatternsConfig) (*WorkloadImpl, error) { + if err := cfg.Validate(); err != nil { + return nil, err + } + w := &WorkloadImpl{ + patternIdx: 0, // start from 0 + config: cfg, + stats: statistics.NewPerfProfile(), + } + return w, nil +} + +func (w *WorkloadImpl) Name() string { + return config.WorkloadTypeHybrid +} + +func (w *WorkloadImpl) isValid() bool { + return w.config != nil && w.config.Global != nil && (len(w.rawClients) > 0 || len(w.txnClients) > 0) +} + +func (w *WorkloadImpl) isValidThread(threadID int) bool { + return w.isValid() && threadID < max(len(w.rawClients), len(w.txnClients)) +} + +// InitThread implements WorkloadInterface +func (w *WorkloadImpl) InitThread(ctx context.Context, threadID int) error { + // Nothing to do + return nil +} + +// CleanupThread implements WorkloadInterface +func (w *WorkloadImpl) CleanupThread(ctx context.Context, threadID int) { + if w.isValidThread(threadID) { + if len(w.rawClients) > 0 { + client := w.rawClients[threadID] + if client != nil { + client.Close() + } + } else { + client := w.txnClients[threadID] + if client != nil { + client.Close() + } + } + } +} + +// Prepare implements WorkloadInterface +func (w *WorkloadImpl) Prepare(ctx context.Context, threadID int) error { + if !w.isValidThread(threadID) { + return fmt.Errorf("no valid TxnKV clients") + } + + // return prepareWorkloadImpl(ctx, w, w.cfg.Threads, w.cfg.Warehouses, threadID) + // TODO: add prepare stage + return nil +} + +// CheckPrepare implements WorkloadInterface +func (w *WorkloadImpl) CheckPrepare(ctx context.Context, threadID int) error { + return nil +} + +func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { + if !w.isValidThread(threadID) { + return fmt.Errorf("no valid TxnKV clients") + } + return nil +} + +// Check implements WorkloadInterface +func (w *WorkloadImpl) Check(ctx context.Context, threadID int) error { + return nil +} + +// Cleanup implements WorkloadInterface +func (w *WorkloadImpl) Cleanup(ctx context.Context, threadID int) error { + if !w.isValidThread(threadID) { + return fmt.Errorf("no valid TxnKV clients") + } + // delete all keys + if threadID == 0 { + if len(w.rawClients) > 0 { + client := w.rawClients[threadID] + client.DeleteRange(ctx, []byte(config.RawKVCommandDefaultKey), []byte(config.RawKVCommandDefaultEndKey)) + } else { + client := w.txnClients[threadID] + client.DeleteRange(ctx, []byte(config.TxnKVCommandDefaultKey), []byte(config.TxnKVCommandDefaultEndKey), len(w.txnClients)) + } + } + return nil +} + +func (w *WorkloadImpl) OutputStats(ifSummaryReport bool) { + w.stats.PrintFmt(ifSummaryReport, w.config.Global.OutputStyle, statistics.HistogramOutputFunc) +} + +func (w *WorkloadImpl) IsTxnKVPattern() bool { + plan := w.config.Plans[w.patternIdx] + return plan.GetTxnKVConfig() != nil +} + +func (w *WorkloadImpl) ContinueToExecute() bool { + return w.patternIdx < len(w.config.Plans) +} + +func (w *WorkloadImpl) BeforeExecute() error { + plan := w.config.Plans[w.patternIdx] + txnConfig := plan.GetTxnKVConfig() + rawConfig := plan.GetRawKVConfig() + if txnConfig != nil { + clientConfig.UpdateGlobal(func(conf *clientConfig.Config) { + conf.TiKVClient.MaxBatchSize = (uint)(txnConfig.TxnSize + 10) + }) + w.txnClients = make([]*clientTxnKV.Client, 0, txnConfig.Global.Threads) + for i := 0; i < txnConfig.Global.Threads; i++ { + client, err := clientTxnKV.NewClient(txnConfig.Global.Targets) + if err != nil { + return err + } + w.txnClients = append(w.txnClients, client) + } + } else if rawConfig != nil { + w.rawClients = make([]*clientRawKV.Client, 0, rawConfig.Global.Threads) + for i := 0; i < txnConfig.Global.Threads; i++ { + client, err := clientRawKV.NewClient(workloads.GlobalContext, rawConfig.Global.Targets, rawConfig.Global.Security) + if err != nil { + return err + } + w.rawClients = append(w.rawClients, client) + } + } + fmt.Println("Start to execute pattern", plan.GetName()) + return nil +} + +func (w *WorkloadImpl) AfterExecute() { + plan := w.config.Plans[w.patternIdx] + w.OutputStats(true) + fmt.Println("Finish executing pattern", plan.GetName()) + // Release the resources + w.rawClients = nil + w.txnClients = nil + w.patternIdx += 1 + w.stats.Clear() +} + +func (w *WorkloadImpl) Execute(cmd string) { + plan := w.config.Plans[w.patternIdx] + txnConfig := plan.GetTxnKVConfig() + rawConfig := plan.GetRawKVConfig() + var globalConfig *config.GlobalConfig + if txnConfig != nil { + globalConfig = txnConfig.Global + } else { + globalConfig = rawConfig.Global + } + + w.wait.Add(globalConfig.Threads) + + ctx, cancel := context.WithCancel(workloads.GlobalContext) + ch := make(chan struct{}, 1) + go func() { + ticker := time.NewTicker(globalConfig.OutputInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + ch <- struct{}{} + return + case <-ticker.C: + w.OutputStats(false) + } + } + }() + + count := globalConfig.TotalCount / globalConfig.Threads + for i := 0; i < globalConfig.Threads; i++ { + go func(index int) { + defer w.wait.Done() + if err := workloads.DispatchExecution(ctx, w, cmd, count, index, globalConfig.Silence, globalConfig.IgnoreError); err != nil { + fmt.Printf("[%s] execute %s failed, err %v\n", time.Now().Format("2006-01-02 15:04:05"), cmd, err) + return + } + }(i) + } + + w.wait.Wait() + cancel() + <-ch +} + +func execTxnKV(cmd string) { + if cmd == "" { + return + } + patternsConfig := getPatternsConfig(workloads.GlobalContext) + + var workload *WorkloadImpl + var err error + if workload, err = NewPatternWorkload(patternsConfig); err != nil { + fmt.Printf("create Patterns workload failed: %v\n", err) + return + } + + timeoutCtx, cancel := context.WithTimeout(workloads.GlobalContext, patternsConfig.Global.TotalTime) + workloads.GlobalContext = timeoutCtx + defer cancel() + + for { + if !workload.ContinueToExecute() { + break + } + if err = workload.BeforeExecute(); err != nil { + fmt.Println("BeforeExecute failed:", err) + break + } + workload.Execute(cmd) + workload.AfterExecute() + } +} diff --git a/examples/benchtool/workloads/patterns/pattern_test.go b/examples/benchtool/workloads/patterns/pattern_test.go new file mode 100644 index 0000000000..872cb8e366 --- /dev/null +++ b/examples/benchtool/workloads/patterns/pattern_test.go @@ -0,0 +1,43 @@ +// Copyright 2024 TiKV Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package patterns + +import ( + "testing" + + "benchtool/config" + + "gotest.tools/v3/assert" +) + +func TestPatterns(t *testing.T) { + // Read the YAML file + filePath := "/Users/lucasliang/Workspace/client-go/examples/benchtool/test.yaml" + patternsConfig := &config.PatternsConfig{ + FilePath: filePath, + } + err := patternsConfig.Parse() + assert.Equal(t, err == nil, true) + + for _, pattern := range patternsConfig.Plans { + if pattern.GetRawKVConfig() == nil { + assert.Equal(t, pattern.GetTxnKVConfig() == nil, false) + } else { + assert.Equal(t, pattern.GetRawKVConfig() == nil, false) + } + workloads := pattern.GetWorkloads() + assert.Equal(t, len(workloads) > 0, true) + } +} From b63c244431bccbf6f19e4798fc9b78d735fb417f Mon Sep 17 00:00:00 2001 From: lucasliang Date: Wed, 7 Aug 2024 16:25:30 +0800 Subject: [PATCH 19/30] Support pattern workloads. Signed-off-by: lucasliang --- .../benchtool/config/{txn.go => txnkv.go} | 9 +- examples/benchtool/main.go | 3 +- .../benchtool/workloads/patterns/pattern.go | 218 ++++++++++++++---- .../workloads/rawkv/{rawkv.go => raw.go} | 167 +++++++------- examples/benchtool/workloads/txnkv/txn.go | 72 +++--- 5 files changed, 295 insertions(+), 174 deletions(-) rename examples/benchtool/config/{txn.go => txnkv.go} (78%) rename examples/benchtool/workloads/rawkv/{rawkv.go => raw.go} (91%) diff --git a/examples/benchtool/config/txn.go b/examples/benchtool/config/txnkv.go similarity index 78% rename from examples/benchtool/config/txn.go rename to examples/benchtool/config/txnkv.go index ca8ac50877..86d8e4c0fc 100644 --- a/examples/benchtool/config/txn.go +++ b/examples/benchtool/config/txnkv.go @@ -11,8 +11,13 @@ const ( ) const ( - TxnKVCommandTypeWrite = "write" - TxnKVCommandTypeRead = "read" + TxnKVCommandTypeBegin = "begin" + TxnKVCommandTypeCommit = "commit" + TxnKVCommandTypeRollback = "rollback" + TxnKVCommandTypeWrite = "write" + TxnKVCommandTypeSet = "set" + TxnKVCommandTypeDel = "delete" + TxnKVCommandTypeRead = "read" TxnKVCommandDefaultKey = "txnkv_key" TxnKVCommandDefaultEndKey = "txnkv_key`" diff --git a/examples/benchtool/main.go b/examples/benchtool/main.go index ff5875af1d..7691ad2bb0 100644 --- a/examples/benchtool/main.go +++ b/examples/benchtool/main.go @@ -36,8 +36,7 @@ func main() { commandLineParser := config.NewCommandLineParser() commandLineParser.Initialize() - // register all workloads - // TODO: add more workloads + // Register all workloads rawkv.Register(commandLineParser) txnkv.Register(commandLineParser) patterns.Register(commandLineParser) diff --git a/examples/benchtool/workloads/patterns/pattern.go b/examples/benchtool/workloads/patterns/pattern.go index b694c85745..32c7fe4370 100644 --- a/examples/benchtool/workloads/patterns/pattern.go +++ b/examples/benchtool/workloads/patterns/pattern.go @@ -16,8 +16,10 @@ package patterns import ( "benchtool/config" + "benchtool/utils" "benchtool/utils/statistics" "benchtool/workloads" + "benchtool/workloads/rawkv" "context" "fmt" "sync" @@ -29,6 +31,49 @@ import ( clientTxnKV "github.com/tikv/client-go/v2/txnkv" ) +func getPatternsConfig(ctx context.Context) *config.PatternsConfig { + c := ctx.Value(config.WorkloadTypeHybrid).(*config.PatternsConfig) + return c +} + +// Assistants for TxnKV workload +func prepareLockKeyWithTimeout(ctx context.Context, txn *clientTxnKV.KVTxn, key []byte, timeout int64) error { + if timeout > 0 { + return txn.LockKeysWithWaitTime(ctx, timeout, key) + } + return nil +} + +func execPatternsWorkloads(cmd string) { + if cmd == "" { + return + } + patternsConfig := getPatternsConfig(workloads.GlobalContext) + + var workload *WorkloadImpl + var err error + if workload, err = NewPatternWorkload(patternsConfig); err != nil { + fmt.Printf("create Patterns workload failed: %v\n", err) + return + } + + timeoutCtx, cancel := context.WithTimeout(workloads.GlobalContext, patternsConfig.Global.TotalTime) + workloads.GlobalContext = timeoutCtx + defer cancel() + + for { + if !workload.ContinueToExecute() { + break + } + if err = workload.BeforeExecute(); err != nil { + fmt.Println("BeforeExecute failed:", err) + break + } + workload.Execute(cmd) + workload.AfterExecute() + } +} + // Register registers the workload to the command line parser func Register(command *config.CommandLineParser) *config.PatternsConfig { if command == nil { @@ -44,34 +89,34 @@ func Register(command *config.CommandLineParser) *config.PatternsConfig { workloads.GlobalContext = context.WithValue(workloads.GlobalContext, config.WorkloadTypeHybrid, patternsConfig) }, } - cmd.PersistentFlags().StringVar(&patternsConfig.FilePath, "file-path", "", "The path of the patterns file") + cmd.PersistentFlags().StringVar(&patternsConfig.FilePath, "file-path", "", "The path of the pattern file") var cmdPrepare = &cobra.Command{ Use: "prepare", - Short: "Prepare data for TxnKV workload", + Short: "Prepare data for workload", Run: func(cmd *cobra.Command, _ []string) { - execTxnKV("prepare") + execPatternsWorkloads("prepare") }, } var cmdRun = &cobra.Command{ Use: "run", Short: "Run workload", Run: func(cmd *cobra.Command, _ []string) { - execTxnKV("run") + execPatternsWorkloads("run") }, } var cmdCleanup = &cobra.Command{ Use: "cleanup", Short: "Cleanup data for the workload", Run: func(cmd *cobra.Command, _ []string) { - execTxnKV("cleanup") + execPatternsWorkloads("cleanup") }, } var cmdCheck = &cobra.Command{ Use: "check", Short: "Check data consistency for the workload", Run: func(cmd *cobra.Command, _ []string) { - execTxnKV("check") + execPatternsWorkloads("check") }, } cmd.AddCommand(cmdRun, cmdPrepare, cmdCleanup, cmdCheck) @@ -81,19 +126,6 @@ func Register(command *config.CommandLineParser) *config.PatternsConfig { return patternsConfig } -func getPatternsConfig(ctx context.Context) *config.PatternsConfig { - c := ctx.Value(config.WorkloadTypeHybrid).(*config.PatternsConfig) - return c -} - -// Assistants for TxnKV workload -func prepareLockKeyWithTimeout(ctx context.Context, txn *clientTxnKV.KVTxn, key []byte, timeout int64) error { - if timeout > 0 { - return txn.LockKeysWithWaitTime(ctx, timeout, key) - } - return nil -} - // Workload is the implementation of WorkloadInterface type WorkloadImpl struct { // Pointer to the next execution plan @@ -113,6 +145,9 @@ func NewPatternWorkload(cfg *config.PatternsConfig) (*WorkloadImpl, error) { if err := cfg.Validate(); err != nil { return nil, err } + if err := cfg.Parse(); err != nil { + return nil, err + } w := &WorkloadImpl{ patternIdx: 0, // start from 0 config: cfg, @@ -159,7 +194,7 @@ func (w *WorkloadImpl) CleanupThread(ctx context.Context, threadID int) { // Prepare implements WorkloadInterface func (w *WorkloadImpl) Prepare(ctx context.Context, threadID int) error { if !w.isValidThread(threadID) { - return fmt.Errorf("no valid TxnKV clients") + return fmt.Errorf("no valid clients for patterns workloads") } // return prepareWorkloadImpl(ctx, w, w.cfg.Threads, w.cfg.Warehouses, threadID) @@ -173,9 +208,120 @@ func (w *WorkloadImpl) CheckPrepare(ctx context.Context, threadID int) error { } func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { + if !w.isValidThread(threadID) { + return fmt.Errorf("no valid clients for pattern workload") + } + + if len(w.rawClients) > 0 { + return w.RunRawkKvWorkloads(ctx, threadID) + } else if len(w.txnClients) > 0 { + return w.RunTxnKvWorkloads(ctx, threadID) + } + return fmt.Errorf("invalid pattern workload") +} + +// RunRawkKvWorkloads implements the executing details on RawKV part. +func (w *WorkloadImpl) RunRawkKvWorkloads(ctx context.Context, threadID int) error { + if !w.isValidThread(threadID) { + return fmt.Errorf("no valid RawKV clients") + } + + plan := w.config.Plans[w.patternIdx] + rawConfig := plan.GetRawKVConfig() + + for _, workload := range plan.GetWorkloads() { + rawkv.RunRawKVCommand(ctx, w.rawClients[threadID], workload, rawConfig.KeySize, rawConfig.ValueSize, rawConfig.BatchSize, rawConfig.Randomize, w.stats, w.config.Global.IgnoreError) + } + return nil +} + +// RunTxnKvWorkloads implements the executing details on TxnKV part. +func (w *WorkloadImpl) RunTxnKvWorkloads(ctx context.Context, threadID int) error { if !w.isValidThread(threadID) { return fmt.Errorf("no valid TxnKV clients") } + + plan := w.config.Plans[w.patternIdx] + { + // Check the current plan is valid or not + workloads := plan.GetWorkloads() + if len(workloads) < 2 || workloads[0] != config.TxnKVCommandTypeBegin { + return fmt.Errorf("invalid plan, idx %d", w.patternIdx) + } + } + txnConfig := plan.GetTxnKVConfig() + // Prepare the key value pairs + key := config.TxnKVCommandDefaultKey + val := utils.GenRandomStr(config.TxnKVCommandDefaultValue, txnConfig.ValueSize) + lockTimeout := int64(txnConfig.LockTimeout) + // Constructs the txn client and sets the txn mode + client := w.txnClients[threadID] + txn, err := client.Begin() + if err != nil { + return fmt.Errorf("txn begin failed, err %v", err) + } + switch txnConfig.TxnMode { + case config.TxnKVMode1PC: + txn.SetEnable1PC(true) + case config.TxnKVModeAsyncCommit: + txn.SetEnableAsyncCommit(true) + } + // Default is optimistic lock mode. + txn.SetPessimistic(lockTimeout > 0) + // Tranverse each command + hasUncommitted := true // mark the previous txn has been committed or not + for idx, workload := range plan.GetWorkloads() { + if (workload == config.TxnKVCommandTypeCommit) || (workload == config.TxnKVCommandTypeBegin && idx > 0) { + hasUncommitted = false + start := time.Now() + if txnErr := txn.Commit(ctx); txnErr != nil { + return fmt.Errorf("txn commit failed, err %v", txnErr) + } + w.stats.Record(txnConfig.TxnMode, time.Since(start)) + // Create a new txn. + txn, err = client.Begin() + if err != nil { + return fmt.Errorf("txn begin failed, err %v", err) + } + continue + } else if workload == config.TxnKVCommandTypeRollback { + hasUncommitted = true + if err = txn.Rollback(); err != nil { + return fmt.Errorf("txn rollback failed, err %v", err) + } + continue + } + hasUncommitted = true + for row := 0; row < txnConfig.TxnSize; row++ { + key = fmt.Sprintf("%s@col_", utils.GenRandomStr(key, txnConfig.KeySize)) + // Lock the key with timeout if necessary. + if err = prepareLockKeyWithTimeout(ctx, txn, []byte(key), lockTimeout); err != nil { + fmt.Printf("txn lock key failed, err %v", err) + continue + } + for col := 0; col < txnConfig.ColumnSize; col++ { + colKey := fmt.Sprintf("%s%d", key, col) + if workload == config.TxnKVCommandTypeRead { + _, err = txn.Get(ctx, []byte(colKey)) + } else if workload == config.TxnKVCommandTypeWrite || workload == config.TxnKVCommandTypeSet { + err = txn.Set([]byte(colKey), []byte(val)) + } else if workload == config.TxnKVCommandTypeDel { + err = txn.Delete([]byte(colKey)) + } + if err != nil { + return fmt.Errorf("txn set / get failed, err %v", err) + } + } + } + } + // If the previous txn is not committed, commit it. + if hasUncommitted { + start := time.Now() + if txnErr := txn.Commit(ctx); txnErr != nil { + return fmt.Errorf("txn commit failed, err %v", txnErr) + } + w.stats.Record(txnConfig.TxnMode, time.Since(start)) + } return nil } @@ -187,7 +333,7 @@ func (w *WorkloadImpl) Check(ctx context.Context, threadID int) error { // Cleanup implements WorkloadInterface func (w *WorkloadImpl) Cleanup(ctx context.Context, threadID int) error { if !w.isValidThread(threadID) { - return fmt.Errorf("no valid TxnKV clients") + return fmt.Errorf("no valid clients for pattern workload") } // delete all keys if threadID == 0 { @@ -301,33 +447,3 @@ func (w *WorkloadImpl) Execute(cmd string) { cancel() <-ch } - -func execTxnKV(cmd string) { - if cmd == "" { - return - } - patternsConfig := getPatternsConfig(workloads.GlobalContext) - - var workload *WorkloadImpl - var err error - if workload, err = NewPatternWorkload(patternsConfig); err != nil { - fmt.Printf("create Patterns workload failed: %v\n", err) - return - } - - timeoutCtx, cancel := context.WithTimeout(workloads.GlobalContext, patternsConfig.Global.TotalTime) - workloads.GlobalContext = timeoutCtx - defer cancel() - - for { - if !workload.ContinueToExecute() { - break - } - if err = workload.BeforeExecute(); err != nil { - fmt.Println("BeforeExecute failed:", err) - break - } - workload.Execute(cmd) - workload.AfterExecute() - } -} diff --git a/examples/benchtool/workloads/rawkv/rawkv.go b/examples/benchtool/workloads/rawkv/raw.go similarity index 91% rename from examples/benchtool/workloads/rawkv/rawkv.go rename to examples/benchtool/workloads/rawkv/raw.go index 25b867a80a..29afcd405d 100644 --- a/examples/benchtool/workloads/rawkv/rawkv.go +++ b/examples/benchtool/workloads/rawkv/raw.go @@ -32,6 +32,33 @@ func isReadCommand(cmd string) bool { return cmd == config.RawKVCommandTypeGet || cmd == config.RawKVCommandTypeBatchGet } +func getRawKvConfig(ctx context.Context) *config.RawKVConfig { + c := ctx.Value(config.WorkloadTypeRawKV).(*config.RawKVConfig) + return c +} + +func execRawKV(cmd string) { + if cmd == "" { + return + } + rawKVConfig := getRawKvConfig(workloads.GlobalContext) + + var workload *WorkloadImpl + var err error + if workload, err = NewRawKVWorkload(rawKVConfig); err != nil { + fmt.Printf("create RawKV workload failed: %v\n", err) + return + } + + timeoutCtx, cancel := context.WithTimeout(workloads.GlobalContext, rawKVConfig.Global.TotalTime) + workloads.GlobalContext = timeoutCtx + defer cancel() + + workload.Execute(cmd) + fmt.Println("RawKV workload finished") + workload.OutputStats(true) +} + // Register registers the workload to the command line parser func Register(command *config.CommandLineParser) *config.RawKVConfig { if command == nil { @@ -95,9 +122,62 @@ func Register(command *config.CommandLineParser) *config.RawKVConfig { return rawKVConfig } -func getRawKvConfig(ctx context.Context) *config.RawKVConfig { - c := ctx.Value(config.WorkloadTypeRawKV).(*config.RawKVConfig) - return c +func RunRawKVCommand(ctx context.Context, client *rawkv.Client, commandType string, keySize int, valueSize int, batchSize int, randomize bool, stats *statistics.PerfProfile, ignoreErr bool) error { + // For unary operations. + key := config.RawKVCommandDefaultKey + val := config.RawKVCommandDefaultValue + + // For batch operations. + var ( + keys [][]byte + vals [][]byte + err error + ) + switch commandType { + case config.RawKVCommandTypePut, config.RawKVCommandTypeGet, config.RawKVCommandTypeDel, config.RawKVCommandTypeCAS, config.RawKVCommandTypeScan, config.RawKVCommandTypeReverseScan: + if randomize { + key = utils.GenRandomStr(config.RawKVCommandDefaultKey, keySize) + if !isReadCommand(commandType) { + val = utils.GenRandomStr(config.RawKVCommandDefaultValue, valueSize) + } + } + case config.RawKVCommandTypeBatchPut, config.RawKVCommandTypeBatchGet, config.RawKVCommandTypeBatchDel: + if randomize { + keys = utils.GenRandomByteArrs(config.RawKVCommandDefaultKey, keySize, batchSize) + if !isReadCommand(commandType) { + vals = utils.GenRandomByteArrs(config.RawKVCommandDefaultValue, valueSize, batchSize) + } + } + } + + start := time.Now() + switch commandType { + case config.RawKVCommandTypePut: + err = client.Put(ctx, []byte(key), []byte(val)) + case config.RawKVCommandTypeGet: + _, err = client.Get(ctx, []byte(key)) + case config.RawKVCommandTypeDel: + err = client.Delete(ctx, []byte(key)) + case config.RawKVCommandTypeBatchPut: + err = client.BatchPut(ctx, keys, vals) + case config.RawKVCommandTypeBatchGet: + _, err = client.BatchGet(ctx, keys) + case config.RawKVCommandTypeBatchDel: + err = client.BatchDelete(ctx, keys) + case config.RawKVCommandTypeCAS: + var oldVal []byte + oldVal, _ = client.Get(ctx, []byte(key)) + _, _, err = client.CompareAndSwap(ctx, []byte(key), []byte(oldVal), []byte(val)) // Experimental + case config.RawKVCommandTypeScan: + _, _, err = client.Scan(ctx, []byte(key), []byte(config.RawKVCommandDefaultEndKey), batchSize) + case config.RawKVCommandTypeReverseScan: + _, _, err = client.ReverseScan(ctx, []byte(key), []byte(config.RawKVCommandDefaultKey), batchSize) + } + if err != nil && !ignoreErr { + return fmt.Errorf("execute %s failed: %v", commandType, err) + } + stats.Record(commandType, time.Since(start)) + return nil } type WorkloadImpl struct { @@ -183,64 +263,7 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { if !w.isValidThread(threadID) { return fmt.Errorf("no valid RawKV clients") } - - client := w.clients[threadID] - - // For unary operations. - key := config.RawKVCommandDefaultKey - val := config.RawKVCommandDefaultValue - - // For batch operations. - var ( - keys [][]byte - vals [][]byte - err error - ) - switch w.cfg.CommandType { - case config.RawKVCommandTypePut, config.RawKVCommandTypeGet, config.RawKVCommandTypeDel, config.RawKVCommandTypeCAS, config.RawKVCommandTypeScan, config.RawKVCommandTypeReverseScan: - if w.cfg.Randomize { - key = utils.GenRandomStr(config.RawKVCommandDefaultKey, w.cfg.KeySize) - if !isReadCommand(w.cfg.CommandType) { - val = utils.GenRandomStr(config.RawKVCommandDefaultValue, w.cfg.ValueSize) - } - } - case config.RawKVCommandTypeBatchPut, config.RawKVCommandTypeBatchGet, config.RawKVCommandTypeBatchDel: - if w.cfg.Randomize { - keys = utils.GenRandomByteArrs(config.RawKVCommandDefaultKey, w.cfg.KeySize, w.cfg.BatchSize) - if !isReadCommand(w.cfg.CommandType) { - vals = utils.GenRandomByteArrs(config.RawKVCommandDefaultValue, w.cfg.ValueSize, w.cfg.BatchSize) - } - } - } - - start := time.Now() - switch w.cfg.CommandType { - case config.RawKVCommandTypePut: - err = client.Put(ctx, []byte(key), []byte(val)) - case config.RawKVCommandTypeGet: - _, err = client.Get(ctx, []byte(key)) - case config.RawKVCommandTypeDel: - err = client.Delete(ctx, []byte(key)) - case config.RawKVCommandTypeBatchPut: - err = client.BatchPut(ctx, keys, vals) - case config.RawKVCommandTypeBatchGet: - _, err = client.BatchGet(ctx, keys) - case config.RawKVCommandTypeBatchDel: - err = client.BatchDelete(ctx, keys) - case config.RawKVCommandTypeCAS: - var oldVal []byte - oldVal, _ = client.Get(ctx, []byte(key)) - _, _, err = client.CompareAndSwap(ctx, []byte(key), []byte(oldVal), []byte(val)) // Experimental - case config.RawKVCommandTypeScan: - _, _, err = client.Scan(ctx, []byte(key), []byte(config.RawKVCommandDefaultEndKey), w.cfg.BatchSize) - case config.RawKVCommandTypeReverseScan: - _, _, err = client.ReverseScan(ctx, []byte(key), []byte(config.RawKVCommandDefaultKey), w.cfg.BatchSize) - } - if err != nil && !w.cfg.Global.IgnoreError { - return fmt.Errorf("execute %s failed: %v", w.cfg.CommandType, err) - } - w.stats.Record(w.cfg.CommandType, time.Since(start)) - return nil + return RunRawKVCommand(ctx, w.clients[threadID], w.cfg.CommandType, w.cfg.KeySize, w.cfg.ValueSize, w.cfg.BatchSize, w.cfg.Randomize, w.stats, w.cfg.Global.IgnoreError) } // Check implements WorkloadInterface @@ -311,25 +334,3 @@ func (w *WorkloadImpl) Execute(cmd string) { cancel() <-ch } - -func execRawKV(cmd string) { - if cmd == "" { - return - } - rawKVConfig := getRawKvConfig(workloads.GlobalContext) - - var workload *WorkloadImpl - var err error - if workload, err = NewRawKVWorkload(rawKVConfig); err != nil { - fmt.Printf("create RawKV workload failed: %v\n", err) - return - } - - timeoutCtx, cancel := context.WithTimeout(workloads.GlobalContext, rawKVConfig.Global.TotalTime) - workloads.GlobalContext = timeoutCtx - defer cancel() - - workload.Execute(cmd) - fmt.Println("RawKV workload finished") - workload.OutputStats(true) -} diff --git a/examples/benchtool/workloads/txnkv/txn.go b/examples/benchtool/workloads/txnkv/txn.go index 63cbc0e5f5..e18c058758 100644 --- a/examples/benchtool/workloads/txnkv/txn.go +++ b/examples/benchtool/workloads/txnkv/txn.go @@ -31,6 +31,41 @@ import ( clientTxnKV "github.com/tikv/client-go/v2/txnkv" ) +func getTxnKVConfig(ctx context.Context) *config.TxnKVConfig { + c := ctx.Value(config.WorkloadTypeTxnKV).(*config.TxnKVConfig) + return c +} + +// Assistants for TxnKV workload +func prepareLockKeyWithTimeout(ctx context.Context, txn *clientTxnKV.KVTxn, key []byte, timeout int64) error { + if timeout > 0 { + return txn.LockKeysWithWaitTime(ctx, timeout, key) + } + return nil +} + +func execTxnKV(cmd string) { + if cmd == "" { + return + } + TxnKVConfig := getTxnKVConfig(workloads.GlobalContext) + + var workload *WorkloadImpl + var err error + if workload, err = NewTxnKVWorkload(TxnKVConfig); err != nil { + fmt.Printf("create TxnKV workload failed: %v\n", err) + return + } + + timeoutCtx, cancel := context.WithTimeout(workloads.GlobalContext, TxnKVConfig.Global.TotalTime) + workloads.GlobalContext = timeoutCtx + defer cancel() + + workload.Execute(cmd) + fmt.Println("TxnKV workload finished") + workload.OutputStats(true) +} + // Register registers the workload to the command line parser func Register(command *config.CommandLineParser) *config.TxnKVConfig { if command == nil { @@ -97,19 +132,6 @@ func Register(command *config.CommandLineParser) *config.TxnKVConfig { return txnKVConfig } -func getTxnKVConfig(ctx context.Context) *config.TxnKVConfig { - c := ctx.Value(config.WorkloadTypeTxnKV).(*config.TxnKVConfig) - return c -} - -// Assistants for TxnKV workload -func prepareLockKeyWithTimeout(ctx context.Context, txn *clientTxnKV.KVTxn, key []byte, timeout int64) error { - if timeout > 0 { - return txn.LockKeysWithWaitTime(ctx, timeout, key) - } - return nil -} - // Workload is the implementation of WorkloadInterface type WorkloadImpl struct { cfg *config.TxnKVConfig @@ -214,8 +236,8 @@ func (w *WorkloadImpl) Run(ctx context.Context, threadID int) error { case config.TxnKVModeAsyncCommit: txn.SetEnableAsyncCommit(true) } - // Default is optimistic lock mode. + // Default is optimistic lock mode. txn.SetPessimistic(lockTimeout > 0) sum := w.cfg.TxnSize * w.cfg.ColumnSize @@ -315,25 +337,3 @@ func (w *WorkloadImpl) Execute(cmd string) { cancel() <-ch } - -func execTxnKV(cmd string) { - if cmd == "" { - return - } - TxnKVConfig := getTxnKVConfig(workloads.GlobalContext) - - var workload *WorkloadImpl - var err error - if workload, err = NewTxnKVWorkload(TxnKVConfig); err != nil { - fmt.Printf("create TxnKV workload failed: %v\n", err) - return - } - - timeoutCtx, cancel := context.WithTimeout(workloads.GlobalContext, TxnKVConfig.Global.TotalTime) - workloads.GlobalContext = timeoutCtx - defer cancel() - - workload.Execute(cmd) - fmt.Println("TxnKV workload finished") - workload.OutputStats(true) -} From 769f5e50bdfad6e1bd51330d241c00a92198d5be Mon Sep 17 00:00:00 2001 From: lucasliang Date: Thu, 22 Aug 2024 11:14:05 +0800 Subject: [PATCH 20/30] Bugfix. Signed-off-by: lucasliang --- examples/benchtool/workloads/patterns/pattern.go | 6 +++--- .../benchtool/workloads/patterns/pattern_test.go | 3 +++ examples/benchtool/workloads/rawkv/raw.go | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/examples/benchtool/workloads/patterns/pattern.go b/examples/benchtool/workloads/patterns/pattern.go index 32c7fe4370..de397e6e40 100644 --- a/examples/benchtool/workloads/patterns/pattern.go +++ b/examples/benchtool/workloads/patterns/pattern.go @@ -142,10 +142,10 @@ type WorkloadImpl struct { } func NewPatternWorkload(cfg *config.PatternsConfig) (*WorkloadImpl, error) { - if err := cfg.Validate(); err != nil { + if err := cfg.Parse(); err != nil { return nil, err } - if err := cfg.Parse(); err != nil { + if err := cfg.Validate(); err != nil { return nil, err } w := &WorkloadImpl{ @@ -379,7 +379,7 @@ func (w *WorkloadImpl) BeforeExecute() error { } } else if rawConfig != nil { w.rawClients = make([]*clientRawKV.Client, 0, rawConfig.Global.Threads) - for i := 0; i < txnConfig.Global.Threads; i++ { + for i := 0; i < rawConfig.Global.Threads; i++ { client, err := clientRawKV.NewClient(workloads.GlobalContext, rawConfig.Global.Targets, rawConfig.Global.Security) if err != nil { return err diff --git a/examples/benchtool/workloads/patterns/pattern_test.go b/examples/benchtool/workloads/patterns/pattern_test.go index 872cb8e366..086b748e07 100644 --- a/examples/benchtool/workloads/patterns/pattern_test.go +++ b/examples/benchtool/workloads/patterns/pattern_test.go @@ -31,6 +31,9 @@ func TestPatterns(t *testing.T) { err := patternsConfig.Parse() assert.Equal(t, err == nil, true) + err = patternsConfig.Validate() + assert.Equal(t, err != nil, true) // PdAddrs is empty + for _, pattern := range patternsConfig.Plans { if pattern.GetRawKVConfig() == nil { assert.Equal(t, pattern.GetTxnKVConfig() == nil, false) diff --git a/examples/benchtool/workloads/rawkv/raw.go b/examples/benchtool/workloads/rawkv/raw.go index 29afcd405d..1bd6211e1a 100644 --- a/examples/benchtool/workloads/rawkv/raw.go +++ b/examples/benchtool/workloads/rawkv/raw.go @@ -37,6 +37,19 @@ func getRawKvConfig(ctx context.Context) *config.RawKVConfig { return c } +func convertCfName(cf string) string { + switch cf { + case "default": + return config.WorkloadColumnFamilyDefault + case "write": + return config.WorkloadColumnFamilyWrite + case "lock": + return config.WorkloadColumnFamilyLock + default: + return "default" + } +} + func execRawKV(cmd string) { if cmd == "" { return @@ -71,6 +84,7 @@ func Register(command *config.CommandLineParser) *config.RawKVConfig { cmd := &cobra.Command{ Use: config.WorkloadTypeRawKV, PersistentPreRun: func(cmd *cobra.Command, args []string) { + rawKVConfig.ColumnFamily = convertCfName(rawKVConfig.ColumnFamily) workloads.GlobalContext = context.WithValue(workloads.GlobalContext, config.WorkloadTypeRawKV, rawKVConfig) }, } From 38e82a5c219c16a705bfa7924227a7b075e49537 Mon Sep 17 00:00:00 2001 From: lucasliang Date: Thu, 22 Aug 2024 11:22:46 +0800 Subject: [PATCH 21/30] Polish codes. Signed-off-by: lucasliang --- .../benchtool/workloads/patterns/pattern_test.go | 2 +- .../benchtool/workloads/patterns/template.yaml | 15 +++++++++++++++ .../benchtool/{ => workloads/patterns}/test.yaml | 0 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 examples/benchtool/workloads/patterns/template.yaml rename examples/benchtool/{ => workloads/patterns}/test.yaml (100%) diff --git a/examples/benchtool/workloads/patterns/pattern_test.go b/examples/benchtool/workloads/patterns/pattern_test.go index 086b748e07..7078379a0f 100644 --- a/examples/benchtool/workloads/patterns/pattern_test.go +++ b/examples/benchtool/workloads/patterns/pattern_test.go @@ -24,7 +24,7 @@ import ( func TestPatterns(t *testing.T) { // Read the YAML file - filePath := "/Users/lucasliang/Workspace/client-go/examples/benchtool/test.yaml" + filePath := "./test.yaml" patternsConfig := &config.PatternsConfig{ FilePath: filePath, } diff --git a/examples/benchtool/workloads/patterns/template.yaml b/examples/benchtool/workloads/patterns/template.yaml new file mode 100644 index 0000000000..be926a9d47 --- /dev/null +++ b/examples/benchtool/workloads/patterns/template.yaml @@ -0,0 +1,15 @@ +patterns: + - name: # sub pattern name 1 + workload_type: # workload type, [rawkv | txnkv] + workloads: # list of workloads [batch_get | batch_write | get | put | scan | update | begin | commit | rollback] + - # workload name 1 + - # workload name 2 + mode: # transaction mode, only valid for txnkv, [async_commit | 1PC | 2PC] + lock: # lock type, only valid for txnkv, [optimistic | pessimistic] + key_prefix: # prefix of the key, default is ${name} + count: # number of operations + key_size: # size of the key + value_size: # size of the value + threads: # number of threads + random: # whether to use random key, default is false + - name: # sub pattern name 2 \ No newline at end of file diff --git a/examples/benchtool/test.yaml b/examples/benchtool/workloads/patterns/test.yaml similarity index 100% rename from examples/benchtool/test.yaml rename to examples/benchtool/workloads/patterns/test.yaml From cc845c4c802d7be1f975b98a5996c2ad87dc619a Mon Sep 17 00:00:00 2001 From: lucasliang Date: Thu, 22 Aug 2024 16:12:41 +0800 Subject: [PATCH 22/30] Add logger. Signed-off-by: lucasliang --- examples/benchtool/config/global.go | 48 +++++++++++++++++-- examples/benchtool/go.mod | 6 +-- .../benchtool/workloads/patterns/pattern.go | 5 ++ examples/benchtool/workloads/rawkv/raw.go | 5 ++ examples/benchtool/workloads/txnkv/txn.go | 5 ++ 5 files changed, 63 insertions(+), 6 deletions(-) diff --git a/examples/benchtool/config/global.go b/examples/benchtool/config/global.go index d7941f393d..8155544737 100644 --- a/examples/benchtool/config/global.go +++ b/examples/benchtool/config/global.go @@ -16,17 +16,24 @@ package config import ( "fmt" + "os" "strconv" + "sync" "time" + "github.com/pingcap/errors" + "github.com/pingcap/log" "github.com/spf13/cobra" "github.com/tikv/client-go/v2/config" + "go.uber.org/zap" ) +var initOnce = sync.Once{} + const ( - WorkloadColumnFamilyDefault = "CF_DEFAULT" - WorkloadColumnFamilyWrite = "CF_WRITE" - WorkloadColumnFamilyLock = "CF_LOCK" + WorkloadColumnFamilyDefault = "default" + WorkloadColumnFamilyWrite = "write" + WorkloadColumnFamilyLock = "lock" ) type GlobalConfig struct { @@ -45,6 +52,10 @@ type GlobalConfig struct { Targets []string Security config.Security + + // for log + LogLevel string + LogFile string } func (c *GlobalConfig) ParsePdAddrs() error { @@ -64,6 +75,26 @@ func (c *GlobalConfig) Format() string { c.hosts, c.port, c.StatusPort, c.Threads, c.TotalTime, c.TotalCount, c.DropData, c.IgnoreError, c.OutputInterval, c.Silence, c.OutputStyle) } +func (c *GlobalConfig) InitLogger() (err error) { + initOnce.Do(func() { + // Initialize the logger. + conf := &log.Config{ + Level: c.LogLevel, + File: log.FileLogConfig{ + Filename: c.LogFile, + MaxSize: 256, + }, + } + lg, p, e := log.InitLogger(conf) + if e != nil { + err = e + return + } + log.ReplaceGlobals(lg, p) + }) + return errors.Trace(err) +} + type CommandLineParser struct { command *cobra.Command config *GlobalConfig @@ -78,7 +109,13 @@ func (p *CommandLineParser) Initialize() { var rootCmd = &cobra.Command{ Use: "bench-tool", Short: "Benchmark tikv with different workloads", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + if err := globalCfg.InitLogger(); err != nil { + log.Error("InitLogger failed", zap.Error(err)) + } + }, } + rootCmd.PersistentFlags().StringSliceVarP(&globalCfg.hosts, "host", "H", []string{"127.0.0.1"}, "PD host") rootCmd.PersistentFlags().IntVarP(&globalCfg.port, "port", "P", 4000, "PD port") rootCmd.PersistentFlags().IntVarP(&globalCfg.StatusPort, "statusPort", "S", 10080, "PD status port") @@ -92,6 +129,11 @@ func (p *CommandLineParser) Initialize() { rootCmd.PersistentFlags().DurationVar(&globalCfg.OutputInterval, "interval", 10*time.Second, "Output interval time") rootCmd.PersistentFlags().StringVar(&globalCfg.OutputStyle, "output", "plain", "output style, valid values can be { plain | table | json }") + rootCmd.PersistentFlags().StringVar(&globalCfg.LogFile, "log-file", "record.log", "filename of the log file") + rootCmd.PersistentFlags().StringVar(&globalCfg.LogLevel, "log-level", "info", "log level { debug | info | warn | error | fatal }") + + rootCmd.SetOut(os.Stdout) + cobra.EnablePrefixMatching = true p.command = rootCmd diff --git a/examples/benchtool/go.mod b/examples/benchtool/go.mod index f783f785e9..ddcbebd62c 100644 --- a/examples/benchtool/go.mod +++ b/examples/benchtool/go.mod @@ -5,9 +5,12 @@ go 1.21.0 require ( github.com/HdrHistogram/hdrhistogram-go v1.1.2 github.com/olekukonko/tablewriter v0.0.5 + github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c + github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.9.0 github.com/tikv/client-go/v2 v2.0.7 + go.uber.org/zap v1.24.0 gopkg.in/yaml.v3 v3.0.1 gotest.tools/v3 v3.5.1 ) @@ -32,10 +35,8 @@ require ( github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect - github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c // indirect github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c // indirect github.com/pingcap/kvproto v0.0.0-20230403051650-e166ae588106 // indirect - github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect @@ -52,7 +53,6 @@ require ( go.etcd.io/etcd/client/v3 v3.5.2 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.9.0 // indirect - go.uber.org/zap v1.24.0 // indirect golang.org/x/net v0.8.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.6.0 // indirect diff --git a/examples/benchtool/workloads/patterns/pattern.go b/examples/benchtool/workloads/patterns/pattern.go index de397e6e40..3842c57c9b 100644 --- a/examples/benchtool/workloads/patterns/pattern.go +++ b/examples/benchtool/workloads/patterns/pattern.go @@ -25,10 +25,12 @@ import ( "sync" "time" + "github.com/pingcap/log" "github.com/spf13/cobra" clientConfig "github.com/tikv/client-go/v2/config" clientRawKV "github.com/tikv/client-go/v2/rawkv" clientTxnKV "github.com/tikv/client-go/v2/txnkv" + "go.uber.org/zap" ) func getPatternsConfig(ctx context.Context) *config.PatternsConfig { @@ -86,6 +88,9 @@ func Register(command *config.CommandLineParser) *config.PatternsConfig { cmd := &cobra.Command{ Use: config.WorkloadTypeHybrid, PersistentPreRun: func(cmd *cobra.Command, args []string) { + if err := patternsConfig.Global.InitLogger(); err != nil { + log.Error("InitLogger failed", zap.Error(err)) + } workloads.GlobalContext = context.WithValue(workloads.GlobalContext, config.WorkloadTypeHybrid, patternsConfig) }, } diff --git a/examples/benchtool/workloads/rawkv/raw.go b/examples/benchtool/workloads/rawkv/raw.go index 1bd6211e1a..476967b9b2 100644 --- a/examples/benchtool/workloads/rawkv/raw.go +++ b/examples/benchtool/workloads/rawkv/raw.go @@ -24,8 +24,10 @@ import ( "sync" "time" + "github.com/pingcap/log" "github.com/spf13/cobra" "github.com/tikv/client-go/v2/rawkv" + "go.uber.org/zap" ) func isReadCommand(cmd string) bool { @@ -84,6 +86,9 @@ func Register(command *config.CommandLineParser) *config.RawKVConfig { cmd := &cobra.Command{ Use: config.WorkloadTypeRawKV, PersistentPreRun: func(cmd *cobra.Command, args []string) { + if err := rawKVConfig.Global.InitLogger(); err != nil { + log.Error("InitLogger failed", zap.Error(err)) + } rawKVConfig.ColumnFamily = convertCfName(rawKVConfig.ColumnFamily) workloads.GlobalContext = context.WithValue(workloads.GlobalContext, config.WorkloadTypeRawKV, rawKVConfig) }, diff --git a/examples/benchtool/workloads/txnkv/txn.go b/examples/benchtool/workloads/txnkv/txn.go index e18c058758..3555a08fc5 100644 --- a/examples/benchtool/workloads/txnkv/txn.go +++ b/examples/benchtool/workloads/txnkv/txn.go @@ -25,10 +25,12 @@ import ( "sync" "time" + "github.com/pingcap/log" "github.com/spf13/cobra" clientConfig "github.com/tikv/client-go/v2/config" tikverr "github.com/tikv/client-go/v2/error" clientTxnKV "github.com/tikv/client-go/v2/txnkv" + "go.uber.org/zap" ) func getTxnKVConfig(ctx context.Context) *config.TxnKVConfig { @@ -79,6 +81,9 @@ func Register(command *config.CommandLineParser) *config.TxnKVConfig { cmd := &cobra.Command{ Use: config.WorkloadTypeTxnKV, PersistentPreRun: func(cmd *cobra.Command, args []string) { + if err := txnKVConfig.Global.InitLogger(); err != nil { + log.Error("InitLogger failed", zap.Error(err)) + } workloads.GlobalContext = context.WithValue(workloads.GlobalContext, config.WorkloadTypeTxnKV, txnKVConfig) }, } From 3ae05a42acadf4916d8dced9bfc79880c1d2a217 Mon Sep 17 00:00:00 2001 From: lucasliang Date: Thu, 22 Aug 2024 16:19:25 +0800 Subject: [PATCH 23/30] Polish codes. Signed-off-by: lucasliang --- examples/benchtool/workloads/patterns/test.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/benchtool/workloads/patterns/test.yaml b/examples/benchtool/workloads/patterns/test.yaml index a7829be217..b5a7c6be9c 100644 --- a/examples/benchtool/workloads/patterns/test.yaml +++ b/examples/benchtool/workloads/patterns/test.yaml @@ -19,7 +19,6 @@ patterns: workloads: - begin - commit - - rollback mode: 2PC # (async_commit | 1PC | 2PC) lock: pessimistic # (optimistic | pessimistic) count: 10000 From 31e4d8e28dac0812099224189ad48d4325aa3fa3 Mon Sep 17 00:00:00 2001 From: lucasliang Date: Thu, 22 Aug 2024 16:30:18 +0800 Subject: [PATCH 24/30] Fix the test example. Signed-off-by: lucasliang --- examples/benchtool/workloads/patterns/test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/benchtool/workloads/patterns/test.yaml b/examples/benchtool/workloads/patterns/test.yaml index b5a7c6be9c..089b9ad53b 100644 --- a/examples/benchtool/workloads/patterns/test.yaml +++ b/examples/benchtool/workloads/patterns/test.yaml @@ -18,6 +18,7 @@ patterns: workload_type: txnkv workloads: - begin + - write - commit mode: 2PC # (async_commit | 1PC | 2PC) lock: pessimistic # (optimistic | pessimistic) From 07b849e23601e02b216f20c494db8a692dd280ab Mon Sep 17 00:00:00 2001 From: lucasliang Date: Mon, 26 Aug 2024 18:43:09 +0800 Subject: [PATCH 25/30] Polish codes. Signed-off-by: lucasliang --- examples/benchtool/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/benchtool/main.go b/examples/benchtool/main.go index 7691ad2bb0..44425164b1 100644 --- a/examples/benchtool/main.go +++ b/examples/benchtool/main.go @@ -51,7 +51,7 @@ func main() { syscall.SIGTERM, syscall.SIGQUIT) - closeDone := make(chan struct{}, 1) + // Capture signals to cancel the context. go func() { sig := <-sc fmt.Printf("\nGot signal [%v] to exit.\n", sig) @@ -65,7 +65,7 @@ func main() { case <-time.After(10 * time.Second): fmt.Print("\nWait 10s for closed, force exit\n") os.Exit(1) - case <-closeDone: + default: return } }() From 7865a2fc0bbcb3845fc89754208ee7e3afc9c86f Mon Sep 17 00:00:00 2001 From: lucasliang Date: Mon, 26 Aug 2024 19:02:29 +0800 Subject: [PATCH 26/30] Add some extra annotations. Signed-off-by: lucasliang --- examples/benchtool/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/benchtool/main.go b/examples/benchtool/main.go index 44425164b1..05384305a3 100644 --- a/examples/benchtool/main.go +++ b/examples/benchtool/main.go @@ -51,7 +51,7 @@ func main() { syscall.SIGTERM, syscall.SIGQUIT) - // Capture signals to cancel the context. + // Capture signals to cancel the context go func() { sig := <-sc fmt.Printf("\nGot signal [%v] to exit.\n", sig) From 6aef6c36bd634cc52837a29c248890e4ead2893b Mon Sep 17 00:00:00 2001 From: lucasliang Date: Tue, 3 Sep 2024 16:41:24 +0800 Subject: [PATCH 27/30] Polish codes. Signed-off-by: lucasliang --- examples/benchtool/config/global.go | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/examples/benchtool/config/global.go b/examples/benchtool/config/global.go index 8155544737..c668f719ab 100644 --- a/examples/benchtool/config/global.go +++ b/examples/benchtool/config/global.go @@ -37,9 +37,9 @@ const ( ) type GlobalConfig struct { - hosts []string - port int - StatusPort int + ips []string + port int + host string Threads int TotalTime time.Duration @@ -59,20 +59,23 @@ type GlobalConfig struct { } func (c *GlobalConfig) ParsePdAddrs() error { - if len(c.hosts) == 0 { + if len(c.ips) == 0 && c.host == "" { return fmt.Errorf("PD address is empty") } - targets := make([]string, 0, len(c.hosts)) - for _, host := range c.hosts { + targets := make([]string, 0, len(c.ips)) + for _, host := range c.ips { targets = append(targets, host+":"+strconv.Itoa(c.port)) } + if c.host != "" { + targets = append(targets, c.host) + } c.Targets = targets return nil } func (c *GlobalConfig) Format() string { - return fmt.Sprintf("Hosts: %v, Port: %d, StatusPort: %d, Threads: %d, TotalTime: %v, TotalCount: %d, DropData: %t, IgnoreError: %t, OutputInterval: %v, Silence: %t, OutputStyle: %s", - c.hosts, c.port, c.StatusPort, c.Threads, c.TotalTime, c.TotalCount, c.DropData, c.IgnoreError, c.OutputInterval, c.Silence, c.OutputStyle) + return fmt.Sprintf("Host: %s, IPs: %v, Port: %d, Threads: %d, TotalTime: %v, TotalCount: %d, DropData: %t, IgnoreError: %t, OutputInterval: %v, Silence: %t, OutputStyle: %s", + c.host, c.ips, c.port, c.Threads, c.TotalTime, c.TotalCount, c.DropData, c.IgnoreError, c.OutputInterval, c.Silence, c.OutputStyle) } func (c *GlobalConfig) InitLogger() (err error) { @@ -116,9 +119,9 @@ func (p *CommandLineParser) Initialize() { }, } - rootCmd.PersistentFlags().StringSliceVarP(&globalCfg.hosts, "host", "H", []string{"127.0.0.1"}, "PD host") - rootCmd.PersistentFlags().IntVarP(&globalCfg.port, "port", "P", 4000, "PD port") - rootCmd.PersistentFlags().IntVarP(&globalCfg.StatusPort, "statusPort", "S", 10080, "PD status port") + rootCmd.PersistentFlags().StringSliceVarP(&globalCfg.ips, "ip", "I", []string{"127.0.0.1"}, "PD ips") + rootCmd.PersistentFlags().IntVarP(&globalCfg.port, "port", "P", 2379, "PD port") + rootCmd.PersistentFlags().StringVar(&globalCfg.host, "host", "127.0.0.1:2379", "PD address") rootCmd.PersistentFlags().IntVarP(&globalCfg.Threads, "threads", "T", 1, "Thread concurrency") rootCmd.PersistentFlags().DurationVar(&globalCfg.TotalTime, "time", 1<<63-1, "Total execution time") From e8d95547e72086394db43a096dfdbef952846de6 Mon Sep 17 00:00:00 2001 From: lucasliang Date: Tue, 3 Sep 2024 18:35:03 +0800 Subject: [PATCH 28/30] Add extra annotations for the performance results. Signed-off-by: lucasliang --- examples/benchtool/utils/statistics/histogram.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/benchtool/utils/statistics/histogram.go b/examples/benchtool/utils/statistics/histogram.go index f3dc617ffc..1dcbf77e2b 100644 --- a/examples/benchtool/utils/statistics/histogram.go +++ b/examples/benchtool/utils/statistics/histogram.go @@ -89,6 +89,8 @@ func (h *PerfHistogram) Empty() bool { func (h *PerfHistogram) Format() []string { res := h.GetRuntimeStatistics() + // Define the regular expression pattern + // pattern => `([\w\s]+)\s+-\sElapsed\(s\):\s([\d.]+),\sSum:\s([\d.]+),\sCount:\s(\d+),\sOps:\s([\d.]+),\sAvg\(ms\):\s([\d.]+),\s50th\(ms\):\s([\d.]+),\s90th\(ms\):\s([\d.]+),\s95th\(ms\):\s([\d.]+),\s99th\(ms\):\s([\d.]+),\s99.9th\(ms\):\s([\d.]+),\s99.99th\(ms\):\s([\d.]+),\sMin\(ms\):\s([\d.]+),\sMax\(ms\):\s([\d.]+)` // Format: "Elapsed(s)" - "Sum" - "Count" - "Ops" - "Avg" - "P50" - "P90" - "P95" - "P99" - "P999" - "P9999" - "Min" - "Max return []string{ utils.FloatToString(res.elapsed), From 0fc92e6a3c1a79aceb1c243d53afccee75366c47 Mon Sep 17 00:00:00 2001 From: lucasliang Date: Fri, 13 Sep 2024 11:41:18 +0800 Subject: [PATCH 29/30] Fix cf setting bugs. Signed-off-by: lucasliang --- examples/benchtool/workloads/rawkv/raw.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/benchtool/workloads/rawkv/raw.go b/examples/benchtool/workloads/rawkv/raw.go index 476967b9b2..1fdf768cc8 100644 --- a/examples/benchtool/workloads/rawkv/raw.go +++ b/examples/benchtool/workloads/rawkv/raw.go @@ -44,11 +44,11 @@ func convertCfName(cf string) string { case "default": return config.WorkloadColumnFamilyDefault case "write": - return config.WorkloadColumnFamilyWrite case "lock": - return config.WorkloadColumnFamilyLock + fmt.Printf("Column family %s is not supported, use default instead\n", cf) + return config.WorkloadColumnFamilyDefault default: - return "default" + return cf } } From 20183337fa8c8efea182857b50420f07959c9dc6 Mon Sep 17 00:00:00 2001 From: lucasliang Date: Wed, 23 Oct 2024 18:46:32 +0800 Subject: [PATCH 30/30] Upload missing prs. Signed-off-by: lucasliang --- examples/benchtool/workloads/rawkv/raw.go | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/benchtool/workloads/rawkv/raw.go b/examples/benchtool/workloads/rawkv/raw.go index 1fdf768cc8..5bc271d9ea 100644 --- a/examples/benchtool/workloads/rawkv/raw.go +++ b/examples/benchtool/workloads/rawkv/raw.go @@ -50,6 +50,7 @@ func convertCfName(cf string) string { default: return cf } + return config.WorkloadColumnFamilyDefault } func execRawKV(cmd string) {