From 3632a1b9b12f2f113bc18809d67916e22cff1bb6 Mon Sep 17 00:00:00 2001 From: Yicheng Qin Date: Sat, 22 Aug 2015 23:02:22 -0700 Subject: [PATCH] *: add initial read benchmark for etcd v3 It includes the initial read benchmark for etcd v3. This is the first step to give some rough thoughts. I haven't digged deeper to answer some questions, including why its performance is not better than HTTP + json, why one put will cause performance downgrade. --- .../benchmarks/etcd-3-0-demo-benchmarks.md | 40 +++++ tools/v3benchmark/main.go | 102 ++++++++++++ tools/v3benchmark/report.go | 156 ++++++++++++++++++ 3 files changed, 298 insertions(+) create mode 100644 Documentation/benchmarks/etcd-3-0-demo-benchmarks.md create mode 100644 tools/v3benchmark/main.go create mode 100644 tools/v3benchmark/report.go diff --git a/Documentation/benchmarks/etcd-3-0-demo-benchmarks.md b/Documentation/benchmarks/etcd-3-0-demo-benchmarks.md new file mode 100644 index 000000000..05c8fe0a7 --- /dev/null +++ b/Documentation/benchmarks/etcd-3-0-demo-benchmarks.md @@ -0,0 +1,40 @@ +## Physical machines + +GCE n1-highcpu-2 machine type + +- 1x dedicated local SSD mounted under /var/lib/etcd +- 1x dedicated slow disk for the OS +- 1.8 GB memory +- 2x CPUs +- etcd version 2.2.0 + +## etcd Cluster + +1 etcd member running in v3 demo mode + +## Testing + +Use [etcd v3 benchmark tool](../../hack/v3benchmark/). + +## Performance + +### reading one single key + +| key size in bytes | number of clients | read QPS | 90th Percentile Latency (ms) | +|-------------------|-------------------|----------|---------------| +| 256 | 1 | 2716 | 0.4 | +| 256 | 64 | 16623 | 6.1 | +| 256 | 256 | 16622 | 21.7 | + +The performance is nearly the same as the one with empty server handler. + +### reading one single key after putting + +| key size in bytes | number of clients | read QPS | 90th Percentile Latency (ms) | +|-------------------|-------------------|----------|---------------| +| 256 | 1 | 2269 | 0.5 | +| 256 | 64 | 13582 | 8.6 | +| 256 | 256 | 13262 | 47.5 | + +The performance with empty server handler is not affected by one put. So the +performance downgrade should be caused by storage package. diff --git a/tools/v3benchmark/main.go b/tools/v3benchmark/main.go new file mode 100644 index 000000000..ab5c78a35 --- /dev/null +++ b/tools/v3benchmark/main.go @@ -0,0 +1,102 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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 ( + "flag" + "fmt" + "os" + "sync" + "time" + + "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" + "github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc" + "github.com/coreos/etcd/etcdserver/etcdserverpb" + "github.com/rakyll/pb" +) + +func main() { + var c, n int + var url string + flag.IntVar(&c, "c", 50, "number of connections") + flag.IntVar(&n, "n", 200, "number of requests") + // TODO: config the number of concurrency in each connection + flag.StringVar(&url, "u", "127.0.0.1:12379", "etcd server endpoint") + flag.Parse() + if flag.NArg() < 1 { + flag.Usage() + os.Exit(1) + } + + if act := flag.Args()[0]; act != "get" { + fmt.Errorf("unsupported action %v", act) + os.Exit(1) + } + var rangeEnd []byte + key := []byte(flag.Args()[1]) + if len(flag.Args()) > 2 { + rangeEnd = []byte(flag.Args()[2]) + } + + results := make(chan *result, n) + bar := pb.New(n) + bar.Format("Bom !") + bar.Start() + start := time.Now() + defer func() { + bar.Finish() + printReport(n, results, time.Now().Sub(start)) + }() + + var wg sync.WaitGroup + wg.Add(c) + jobs := make(chan struct{}, n) + for i := 0; i < c; i++ { + go func() { + defer wg.Done() + + conn, err := grpc.Dial(url) + if err != nil { + fmt.Errorf("dial error: %v", err) + os.Exit(1) + } + etcd := etcdserverpb.NewEtcdClient(conn) + req := &etcdserverpb.RangeRequest{Key: key, RangeEnd: rangeEnd} + + for _ = range jobs { + st := time.Now() + resp, err := etcd.Range(context.Background(), req) + + var errStr string + if err != nil { + errStr = err.Error() + } else { + errStr = resp.Header.Error + } + results <- &result{ + errStr: errStr, + duration: time.Now().Sub(st), + } + bar.Increment() + } + }() + } + for i := 0; i < n; i++ { + jobs <- struct{}{} + } + close(jobs) + + wg.Wait() +} diff --git a/tools/v3benchmark/report.go b/tools/v3benchmark/report.go new file mode 100644 index 000000000..785ea440a --- /dev/null +++ b/tools/v3benchmark/report.go @@ -0,0 +1,156 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// 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. + +// the file is borrowed from github.com/rakyll/boom/boomer/print.go + +package main + +import ( + "fmt" + "sort" + "strings" + "time" +) + +const ( + barChar = "∎" +) + +type result struct { + errStr string + duration time.Duration +} + +type report struct { + avgTotal float64 + fastest float64 + slowest float64 + average float64 + rps float64 + + results chan *result + total time.Duration + + errorDist map[string]int + lats []float64 +} + +func printReport(size int, results chan *result, total time.Duration) { + r := &report{ + results: results, + total: total, + errorDist: make(map[string]int), + } + r.finalize() +} + +func (r *report) finalize() { + for { + select { + case res := <-r.results: + if res.errStr != "" { + r.errorDist[res.errStr]++ + } else { + r.lats = append(r.lats, res.duration.Seconds()) + r.avgTotal += res.duration.Seconds() + } + default: + r.rps = float64(len(r.lats)) / r.total.Seconds() + r.average = r.avgTotal / float64(len(r.lats)) + r.print() + return + } + } +} + +func (r *report) print() { + sort.Float64s(r.lats) + + if len(r.lats) > 0 { + r.fastest = r.lats[0] + r.slowest = r.lats[len(r.lats)-1] + fmt.Printf("\nSummary:\n") + fmt.Printf(" Total:\t%4.4f secs.\n", r.total.Seconds()) + fmt.Printf(" Slowest:\t%4.4f secs.\n", r.slowest) + fmt.Printf(" Fastest:\t%4.4f secs.\n", r.fastest) + fmt.Printf(" Average:\t%4.4f secs.\n", r.average) + fmt.Printf(" Requests/sec:\t%4.4f\n", r.rps) + r.printHistogram() + r.printLatencies() + } + + if len(r.errorDist) > 0 { + r.printErrors() + } +} + +// Prints percentile latencies. +func (r *report) printLatencies() { + pctls := []int{10, 25, 50, 75, 90, 95, 99} + data := make([]float64, len(pctls)) + j := 0 + for i := 0; i < len(r.lats) && j < len(pctls); i++ { + current := i * 100 / len(r.lats) + if current >= pctls[j] { + data[j] = r.lats[i] + j++ + } + } + fmt.Printf("\nLatency distribution:\n") + for i := 0; i < len(pctls); i++ { + if data[i] > 0 { + fmt.Printf(" %v%% in %4.4f secs.\n", pctls[i], data[i]) + } + } +} + +func (r *report) printHistogram() { + bc := 10 + buckets := make([]float64, bc+1) + counts := make([]int, bc+1) + bs := (r.slowest - r.fastest) / float64(bc) + for i := 0; i < bc; i++ { + buckets[i] = r.fastest + bs*float64(i) + } + buckets[bc] = r.slowest + var bi int + var max int + for i := 0; i < len(r.lats); { + if r.lats[i] <= buckets[bi] { + i++ + counts[bi]++ + if max < counts[bi] { + max = counts[bi] + } + } else if bi < len(buckets)-1 { + bi++ + } + } + fmt.Printf("\nResponse time histogram:\n") + for i := 0; i < len(buckets); i++ { + // Normalize bar lengths. + var barLen int + if max > 0 { + barLen = counts[i] * 40 / max + } + fmt.Printf(" %4.3f [%v]\t|%v\n", buckets[i], counts[i], strings.Repeat(barChar, barLen)) + } +} + +func (r *report) printErrors() { + fmt.Printf("\nError distribution:\n") + for err, num := range r.errorDist { + fmt.Printf(" [%d]\t%s\n", num, err) + } +}