vitess.io/vitess@v0.16.2/go/vtbench/vtbench.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package vtbench 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 "sync" 24 "time" 25 26 "vitess.io/vitess/go/stats" 27 "vitess.io/vitess/go/vt/log" 28 29 querypb "vitess.io/vitess/go/vt/proto/query" 30 ) 31 32 // ClientProtocol indicates how to connect 33 type ClientProtocol int 34 35 const ( 36 // MySQL uses the mysql wire protocol 37 MySQL ClientProtocol = iota 38 39 // GRPCVtgate uses the grpc wire protocol to vttablet 40 GRPCVtgate 41 42 // GRPCVttablet uses the grpc wire protocol to vttablet 43 GRPCVttablet 44 ) 45 46 // ProtocolString returns a string representation of the protocol 47 func (cp ClientProtocol) String() string { 48 switch cp { 49 case MySQL: 50 return "mysql" 51 case GRPCVtgate: 52 return "grpc-vtgate" 53 case GRPCVttablet: 54 return "grpc-vttablet" 55 default: 56 return fmt.Sprintf("unknown-protocol-%d", cp) 57 } 58 } 59 60 // ConnParams specifies how to connect to the vtgate(s) 61 type ConnParams struct { 62 Hosts []string 63 Port int 64 DB string 65 Username string 66 Password string 67 UnixSocket string 68 Protocol ClientProtocol 69 } 70 71 // Bench controls the test 72 type Bench struct { 73 ConnParams ConnParams 74 Threads int 75 Count int 76 Query string 77 78 threads []benchThread 79 80 // synchronization waitgroup for startup / completion 81 wg sync.WaitGroup 82 83 // starting gate used to block all threads on startup 84 lock sync.RWMutex 85 86 Rows *stats.Counter 87 Bytes *stats.Counter 88 Timings *stats.Timings 89 90 TotalTime time.Duration 91 } 92 93 type benchThread struct { 94 b *Bench 95 i int 96 conn clientConn 97 query string 98 bindVars map[string]*querypb.BindVariable 99 } 100 101 // NewBench creates a new bench test 102 func NewBench(threads, count int, cp ConnParams, query string) *Bench { 103 bench := Bench{ 104 Threads: threads, 105 Count: count, 106 ConnParams: cp, 107 Query: query, 108 Rows: stats.NewCounter("", ""), 109 Timings: stats.NewTimings("", "", ""), 110 } 111 return &bench 112 } 113 114 // Run executes the test 115 func (b *Bench) Run(ctx context.Context) error { 116 err := b.createConns(ctx) 117 if err != nil { 118 return err 119 } 120 121 b.createThreads(ctx) 122 if err := b.runTest(ctx); err != nil { 123 return err 124 } 125 return nil 126 } 127 128 func (b *Bench) createConns(ctx context.Context) error { 129 log.V(10).Infof("creating %d client connections...", b.Threads) 130 start := time.Now() 131 reportInterval := 2 * time.Second 132 report := start.Add(reportInterval) 133 for i := 0; i < b.Threads; i++ { 134 host := b.ConnParams.Hosts[i%len(b.ConnParams.Hosts)] 135 cp := b.ConnParams 136 cp.Hosts = []string{host} 137 138 var conn clientConn 139 var err error 140 141 switch b.ConnParams.Protocol { 142 case MySQL: 143 log.V(5).Infof("connecting to %s using mysql protocol...", host) 144 conn = &mysqlClientConn{} 145 err = conn.connect(ctx, cp) 146 case GRPCVtgate: 147 log.V(5).Infof("connecting to %s using grpc vtgate protocol...", host) 148 conn = &grpcVtgateConn{} 149 err = conn.connect(ctx, cp) 150 case GRPCVttablet: 151 log.V(5).Infof("connecting to %s using grpc vttablet protocol...", host) 152 conn = &grpcVttabletConn{} 153 err = conn.connect(ctx, cp) 154 default: 155 return fmt.Errorf("unimplemented connection protocol %s", b.ConnParams.Protocol.String()) 156 } 157 158 if err != nil { 159 return fmt.Errorf("error connecting to %s using %v protocol: %v", host, cp.Protocol.String(), err) 160 } 161 162 // XXX handle normalization and per-thread query templating 163 query, bindVars := b.getQuery(i) 164 b.threads = append(b.threads, benchThread{ 165 b: b, 166 i: i, 167 conn: conn, 168 query: query, 169 bindVars: bindVars, 170 }) 171 172 if time.Now().After(report) { 173 fmt.Printf("Created %d/%d connections after %v\n", i, b.Threads, time.Since(start)) 174 report = time.Now().Add(reportInterval) 175 } 176 } 177 178 return nil 179 } 180 181 func (b *Bench) getQuery(i int) (string, map[string]*querypb.BindVariable) { 182 query := strings.Replace(b.Query, ":thread", fmt.Sprintf("%d", i), -1) 183 bindVars := make(map[string]*querypb.BindVariable) 184 return query, bindVars 185 } 186 187 func (b *Bench) createThreads(ctx context.Context) { 188 // Create a barrier so all the threads start at the same time 189 b.lock.Lock() 190 191 log.V(10).Infof("starting %d threads", b.Threads) 192 for i := 0; i < b.Threads; i++ { 193 b.wg.Add(1) 194 go b.threads[i].clientLoop(ctx) 195 } 196 197 log.V(10).Infof("waiting for %d threads to start", b.Threads) 198 b.wg.Wait() 199 200 b.wg.Add(b.Threads) 201 } 202 203 func (b *Bench) runTest(ctx context.Context) error { 204 start := time.Now() 205 fmt.Printf("Starting test threads\n") 206 b.lock.Unlock() 207 208 // Then wait for them all to finish looping 209 log.V(10).Infof("waiting for %d threads to finish", b.Threads) 210 b.wg.Wait() 211 b.TotalTime = time.Since(start) 212 213 return nil 214 } 215 216 func (bt *benchThread) clientLoop(ctx context.Context) { 217 b := bt.b 218 219 // Declare that startup is finished and wait for 220 // the barrier 221 b.wg.Done() 222 log.V(10).Infof("thread %d waiting for startup barrier", bt.i) 223 b.lock.RLock() 224 log.V(10).Infof("thread %d starting loop", bt.i) 225 226 for i := 0; i < b.Count; i++ { 227 start := time.Now() 228 result, err := bt.conn.execute(ctx, bt.query, bt.bindVars) 229 b.Timings.Record("query", start) 230 if err != nil { 231 log.Errorf("query error: %v", err) 232 break 233 } else { 234 b.Rows.Add(int64(len(result.Rows))) 235 } 236 237 } 238 239 b.wg.Done() 240 }