github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/bench/sysbench.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  	sysbenchDriverMap = map[string]string{
    43  		"mysql":      "mysql",
    44  		"postgresql": "pgsql",
    45  	}
    46  	sysbenchSupportedDrivers = []string{"mysql", "pgsql"}
    47  )
    48  
    49  var sysbenchExample = templates.Examples(`
    50  		# sysbench on a cluster, that will exec for all steps, cleanup, prepare and run
    51  		kbcli bench sysbench mytest --cluster mycluster --user xxx --password xxx --database mydb
    52  
    53  		# sysbench run on a cluster with cleanup, only cleanup by deleting the testdata
    54  		kbcli bench sysbench cleanup mytest --cluster mycluster --user xxx --password xxx --database mydb
    55  
    56  		# sysbench run on a cluster with prepare, just prepare by creating the testdata
    57  		kbcli bench sysbench prepare mytest --cluster mycluster --user xxx --password xxx --database mydb
    58  
    59  		# sysbench run on a cluster with run, just run by running the test
    60  		kbcli bench sysbench run mytest --cluster mycluster --user xxx --password xxx --database mydb
    61  
    62  		# sysbench on a cluster with thread counts
    63  		kbcli bench sysbench mytest --cluster mycluster --user xxx --password xxx --database mydb --threads 4,8
    64  
    65  		# sysbench on a cluster with type
    66  		kbcli bench sysbench mytest --cluster mycluster --user xxx --password xxx --database mydb --type oltp_read_only,oltp_read_write
    67  
    68  		# sysbench on a cluster with specified read/write ratio
    69  		kbcli bench sysbench mytest --cluster mycluster --user xxx --password xxx  --database mydb --type oltp_read_write_pct --read-percent 80 --write-percent 20
    70  
    71  		# sysbench on a cluster with specified tables and size
    72  		kbcli bench sysbench mytest --cluster mycluster --user xxx --password xxx --database mydb --tables 10 --size 25000
    73  `)
    74  
    75  type SysBenchOptions struct {
    76  	Threads      []int // the number of threads
    77  	Tables       int   // the number of tables
    78  	Size         int   // the data size of per table
    79  	Duration     int
    80  	Type         []string
    81  	ReadPercent  int
    82  	WritePercent int
    83  
    84  	BenchBaseOptions
    85  }
    86  
    87  func NewSysBenchCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
    88  	o := &SysBenchOptions{
    89  		BenchBaseOptions: BenchBaseOptions{
    90  			IOStreams: streams,
    91  			factory:   f,
    92  		},
    93  	}
    94  
    95  	cmd := &cobra.Command{
    96  		Use:     "sysbench [Step] [BenchmarkName]",
    97  		Short:   "run a SysBench benchmark",
    98  		Example: sysbenchExample,
    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  	cmd.Flags().StringSliceVar(&o.Type, "type", []string{"oltp_read_write"}, "sysbench type, you can set multiple values")
   107  	cmd.Flags().IntVar(&o.Size, "size", 25000, "the data size of per table")
   108  	cmd.Flags().IntVar(&o.Tables, "tables", 10, "the number of tables")
   109  	cmd.Flags().IntVar(&o.Duration, "duration", 60, "the seconds of running sysbench")
   110  	cmd.Flags().IntSliceVar(&o.Threads, "threads", []int{4}, "the number of threads, you can set multiple values, like 4,8")
   111  	cmd.Flags().IntVar(&o.ReadPercent, "read-percent", 0, "the percent of read, only useful when type is oltp_read_write_pct")
   112  	cmd.Flags().IntVar(&o.WritePercent, "write-percent", 0, "the percent of write, only useful when type is oltp_read_write_pct")
   113  	o.BenchBaseOptions.AddFlags(cmd)
   114  
   115  	return cmd
   116  }
   117  
   118  func (o *SysBenchOptions) Complete(args []string) error {
   119  	var err error
   120  	var driver string
   121  	var host string
   122  	var port int
   123  
   124  	if err = o.BenchBaseOptions.BaseComplete(); err != nil {
   125  		return err
   126  	}
   127  
   128  	o.Step, o.name = parseStepAndName(args, "sysbench")
   129  
   130  	o.namespace, _, err = o.factory.ToRawKubeConfigLoader().Namespace()
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	if o.dynamic, err = o.factory.DynamicClient(); err != nil {
   136  		return err
   137  	}
   138  
   139  	if o.client, err = o.factory.KubernetesClientSet(); err != nil {
   140  		return err
   141  	}
   142  
   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  	if v, ok := sysbenchDriverMap[driver]; ok && o.Driver == "" {
   168  		o.Driver = v
   169  	}
   170  
   171  	if o.Host == "" && o.Port == 0 {
   172  		o.Host = host
   173  		o.Port = port
   174  	}
   175  
   176  	// if user just give readPercent or writePercent, we will calculate the other one
   177  	if o.ReadPercent != 0 && o.WritePercent == 0 {
   178  		o.WritePercent = 100 - o.ReadPercent
   179  	}
   180  	if o.ReadPercent == 0 && o.WritePercent != 0 {
   181  		o.ReadPercent = 100 - o.WritePercent
   182  	}
   183  
   184  	return nil
   185  }
   186  
   187  func (o *SysBenchOptions) Validate() error {
   188  	if err := o.BaseValidate(); err != nil {
   189  		return err
   190  	}
   191  
   192  	var supported bool
   193  	for _, v := range sysbenchDriverMap {
   194  		if v == o.Driver {
   195  			supported = true
   196  			break
   197  		}
   198  	}
   199  	if !supported {
   200  		return fmt.Errorf("sysbench now only supports drivers in [%s], current cluster driver is %s",
   201  			strings.Join(sysbenchSupportedDrivers, ","), o.Driver)
   202  	}
   203  
   204  	if o.User == "" {
   205  		return fmt.Errorf("user is required")
   206  	}
   207  
   208  	if o.Database == "" {
   209  		return fmt.Errorf("database is required")
   210  	}
   211  
   212  	if len(o.Type) == 0 {
   213  		return fmt.Errorf("type is required")
   214  	}
   215  
   216  	if o.Tables <= 0 {
   217  		return fmt.Errorf("tables must be greater than 0")
   218  	}
   219  
   220  	if o.Duration <= 0 {
   221  		return fmt.Errorf("duration must be greater than 0")
   222  	}
   223  
   224  	if o.ReadPercent < 0 || o.ReadPercent > 100 {
   225  		return fmt.Errorf("readPercent must be between 0 and 100")
   226  	}
   227  	if o.WritePercent < 0 || o.WritePercent > 100 {
   228  		return fmt.Errorf("writePercent must be between 0 and 100")
   229  	}
   230  
   231  	return nil
   232  }
   233  
   234  func (o *SysBenchOptions) Run() error {
   235  	if o.ReadPercent > 0 {
   236  		o.ExtraArgs = append(o.ExtraArgs, fmt.Sprintf("--read-percent=%d", o.ReadPercent))
   237  	}
   238  	if o.WritePercent > 0 {
   239  		o.ExtraArgs = append(o.ExtraArgs, fmt.Sprintf("--write-percent=%d", o.WritePercent))
   240  	}
   241  
   242  	sysbench := v1alpha1.Sysbench{
   243  		TypeMeta: metav1.TypeMeta{
   244  			Kind:       "Sysbench",
   245  			APIVersion: types.SysbenchGVR().GroupVersion().String(),
   246  		},
   247  		ObjectMeta: metav1.ObjectMeta{
   248  			Name:      o.name,
   249  			Namespace: o.namespace,
   250  		},
   251  		Spec: v1alpha1.SysbenchSpec{
   252  			Tables:   o.Tables,
   253  			Size:     o.Size,
   254  			Threads:  o.Threads,
   255  			Types:    o.Type,
   256  			Duration: o.Duration,
   257  			BenchCommon: v1alpha1.BenchCommon{
   258  				ExtraArgs:   o.ExtraArgs,
   259  				Step:        o.Step,
   260  				Tolerations: o.Tolerations,
   261  				Target: v1alpha1.Target{
   262  					Driver:   o.Driver,
   263  					Host:     o.Host,
   264  					Port:     o.Port,
   265  					User:     o.User,
   266  					Password: o.Password,
   267  					Database: o.Database,
   268  				},
   269  			},
   270  		},
   271  	}
   272  
   273  	obj := &unstructured.Unstructured{
   274  		Object: map[string]interface{}{},
   275  	}
   276  	data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&sysbench)
   277  	if err != nil {
   278  		return err
   279  	}
   280  	obj.SetUnstructuredContent(data)
   281  
   282  	obj, err = o.dynamic.Resource(types.SysbenchGVR()).Namespace(o.namespace).Create(context.TODO(), obj, metav1.CreateOptions{})
   283  	if err != nil {
   284  		return err
   285  	}
   286  
   287  	fmt.Fprintf(o.Out, "%s %s created\n", obj.GetKind(), obj.GetName())
   288  	return nil
   289  }