github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/bench/ycsb.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package bench 21 22 import ( 23 "context" 24 "fmt" 25 "strings" 26 27 "github.com/spf13/cobra" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/cli-runtime/pkg/genericiooptions" 32 cmdutil "k8s.io/kubectl/pkg/cmd/util" 33 "k8s.io/kubectl/pkg/util/templates" 34 35 "github.com/apecloud/kubebench/api/v1alpha1" 36 37 "github.com/1aal/kubeblocks/pkg/cli/cluster" 38 "github.com/1aal/kubeblocks/pkg/cli/types" 39 ) 40 41 var ( 42 ycsbDriverMap = map[string]string{ 43 "mongodb": "mongodb", 44 "mysql": "mysql", 45 "postgresql": "postgresql", 46 "redis": "redis", 47 } 48 ycsbSupportedDrivers = []string{"mongodb", "mysql", "postgresql", "redis"} 49 ) 50 51 var ycsbExample = templates.Examples(` 52 # ycsb on a cluster, that will exec for all steps, cleanup, prepare and run 53 kbcli bench ycsb mytest --cluster mycluster --user xxx --password xxx --database mydb 54 55 # ycsb on a cluster with cleanup, only cleanup by deleting the testdata 56 kbcli bench ycsb cleanup mytest --cluster mycluster --user xxx --password xxx --database mydb 57 58 # ycsb on a cluster with prepare, just prepare by creating the testdata 59 kbcli bench ycsb prepare mytest --cluster mycluster --user xxx --password xxx --database mydb 60 61 # ycsb on a cluster with run, just run by running the test 62 kbcli bench ycsb run mytest --cluster mycluster --user xxx --password xxx --database mydb 63 64 # ycsb on a cluster with thread counts 65 kbcli bench ycsb mytest --cluster mycluster --user xxx --password xxx --database mydb --threads 4,8 66 67 # ycsb on a cluster with record number and operation number 68 kbcli bench ycsb mytest --cluster mycluster --user xxx --password xxx --database mydb --record-count 10000 --operation-count 10000 69 70 # ycsb on a cluster mixed read/write 71 kbcli bench ycsb mytest --cluster mycluster --user xxx --password xxx --database mydb --read-proportion 50 --update-proportion 50 72 `) 73 74 type YcsbOptions struct { 75 Threads []int // the number of threads to use 76 RecordCount int // the number of records to use 77 OperationCount int // the number of operations to use during the run phase 78 ReadProportion int // the proportion of operations that are reads 79 UpdateProportion int // the proportion of operations that are updates 80 InsertProportion int // the proportion of operations that are inserts 81 ScanProportion int // the proportion of operations that are scans 82 ReadModifyWriteProportion int // the proportion of operations that are read then modify a record 83 84 BenchBaseOptions 85 } 86 87 func NewYcsbCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { 88 o := &YcsbOptions{ 89 BenchBaseOptions: BenchBaseOptions{ 90 IOStreams: streams, 91 factory: f, 92 }, 93 } 94 95 cmd := &cobra.Command{ 96 Use: "ycsb [Step] [BenchmarkName]", 97 Short: "Run YCSB benchmark on a cluster", 98 Example: ycsbExample, 99 Run: func(cmd *cobra.Command, args []string) { 100 cmdutil.CheckErr(o.Complete(args)) 101 cmdutil.CheckErr(o.Validate()) 102 cmdutil.CheckErr(o.Run()) 103 }, 104 } 105 106 o.BenchBaseOptions.AddFlags(cmd) 107 cmd.Flags().IntSliceVar(&o.Threads, "threads", []int{1}, "the number of threads to use") 108 cmd.Flags().IntVar(&o.RecordCount, "record-count", 1000, "the number of records to use") 109 cmd.Flags().IntVar(&o.OperationCount, "operation-count", 1000, "the number of operations to use during the run phase") 110 cmd.Flags().IntVar(&o.ReadProportion, "read-proportion", 0, "the percentage of read operations in benchmark") 111 cmd.Flags().IntVar(&o.UpdateProportion, "update-proportion", 0, "the percentage of update operations in benchmark") 112 cmd.Flags().IntVar(&o.InsertProportion, "insert-proportion", 0, "the percentage of insert operations in benchmark") 113 cmd.Flags().IntVar(&o.ScanProportion, "scan-proportion", 0, "the percentage of scan operations in benchmark") 114 cmd.Flags().IntVar(&o.ReadModifyWriteProportion, "read-modify-write-proportion", 0, "the percentage of read-modify-write operations in benchmark, which read a record, modify it, and write it back") 115 116 return cmd 117 } 118 119 func (o *YcsbOptions) Complete(args []string) error { 120 var err error 121 var driver string 122 var host string 123 var port int 124 125 if err = o.BenchBaseOptions.BaseComplete(); err != nil { 126 return err 127 } 128 129 o.namespace, _, err = o.factory.ToRawKubeConfigLoader().Namespace() 130 if err != nil { 131 return err 132 } 133 134 if o.dynamic, err = o.factory.DynamicClient(); err != nil { 135 return err 136 } 137 138 if o.client, err = o.factory.KubernetesClientSet(); err != nil { 139 return err 140 } 141 142 o.Step, o.name = parseStepAndName(args, "ycsb") 143 if o.ClusterName != "" { 144 clusterGetter := cluster.ObjectsGetter{ 145 Client: o.client, 146 Dynamic: o.dynamic, 147 Name: o.ClusterName, 148 Namespace: o.namespace, 149 GetOptions: cluster.GetOptions{ 150 WithClusterDef: true, 151 WithService: true, 152 WithPod: true, 153 WithEvent: true, 154 WithPVC: true, 155 WithDataProtection: true, 156 }, 157 } 158 if o.ClusterObjects, err = clusterGetter.Get(); err != nil { 159 return err 160 } 161 driver, host, port, err = getDriverAndHostAndPort(o.Cluster, o.Services) 162 if err != nil { 163 return err 164 } 165 166 } 167 168 // don't overwrite the driver if it's already set 169 if v, ok := ycsbDriverMap[driver]; ok && o.Driver == "" { 170 o.Driver = v 171 } 172 173 // don't overwrite the host and port if they are already set 174 if o.Host == "" && o.Port == 0 { 175 o.Host = host 176 o.Port = port 177 } 178 179 return nil 180 } 181 182 func (o *YcsbOptions) Validate() error { 183 if err := o.BaseValidate(); err != nil { 184 return err 185 } 186 187 var supported bool 188 for _, v := range ycsbDriverMap { 189 if v == o.Driver { 190 supported = true 191 break 192 } 193 } 194 if !supported { 195 return fmt.Errorf("ycsb now only supports drivers in [%s], current cluster driver is %s", strings.Join(ycsbSupportedDrivers, ","), o.Driver) 196 } 197 198 if o.RecordCount < 0 { 199 return fmt.Errorf("record count should be positive") 200 } 201 if o.OperationCount < 0 { 202 return fmt.Errorf("operation count should be positive") 203 } 204 205 // constraint the proportion in [0, 100] 206 if o.ReadProportion < 0 || o.ReadProportion > 100 { 207 return fmt.Errorf("read proportion should be in [0, 100]") 208 } 209 if o.UpdateProportion < 0 || o.UpdateProportion > 100 { 210 return fmt.Errorf("update proportion should be in [0, 100]") 211 } 212 if o.InsertProportion < 0 || o.InsertProportion > 100 { 213 return fmt.Errorf("insert proportion should be in [0, 100]") 214 } 215 if o.ScanProportion < 0 || o.ScanProportion > 100 { 216 return fmt.Errorf("scan proportion should be in [0, 100]") 217 } 218 if o.ReadModifyWriteProportion < 0 || o.ReadModifyWriteProportion > 100 { 219 return fmt.Errorf("read-modify-write proportion should be in [0, 100]") 220 } 221 222 return nil 223 } 224 225 func (o *YcsbOptions) Run() error { 226 ycsb := v1alpha1.Ycsb{ 227 TypeMeta: metav1.TypeMeta{ 228 Kind: "Ycsb", 229 APIVersion: types.YcsbGVR().GroupVersion().String(), 230 }, 231 ObjectMeta: metav1.ObjectMeta{ 232 Name: o.name, 233 Namespace: o.namespace, 234 }, 235 Spec: v1alpha1.YcsbSpec{ 236 RecordCount: o.RecordCount, 237 OperationCount: o.OperationCount, 238 Threads: o.Threads, 239 ReadProportion: o.ReadProportion, 240 UpdateProportion: o.UpdateProportion, 241 InsertProportion: o.InsertProportion, 242 ScanProportion: o.ScanProportion, 243 ReadModifyWriteProportion: o.ReadModifyWriteProportion, 244 BenchCommon: v1alpha1.BenchCommon{ 245 ExtraArgs: o.ExtraArgs, 246 Step: o.Step, 247 Tolerations: o.Tolerations, 248 Target: v1alpha1.Target{ 249 Driver: o.Driver, 250 Host: o.Host, 251 Port: o.Port, 252 User: o.User, 253 Password: o.Password, 254 Database: o.Database, 255 }, 256 }, 257 }, 258 } 259 260 obj := &unstructured.Unstructured{ 261 Object: map[string]interface{}{}, 262 } 263 data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&ycsb) 264 if err != nil { 265 return err 266 } 267 obj.SetUnstructuredContent(data) 268 269 obj, err = o.dynamic.Resource(types.YcsbGVR()).Namespace(o.namespace).Create(context.TODO(), obj, metav1.CreateOptions{}) 270 if err != nil { 271 return err 272 } 273 274 fmt.Fprintf(o.Out, "%s %s created\n", obj.GetKind(), obj.GetName()) 275 return nil 276 }