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  }