github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/bench/tpcc.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  	tpccDriverMap = map[string]string{
    43  		"mysql":      "mysql",
    44  		"postgresql": "postgres",
    45  	}
    46  	tpccSupportedDrivers = []string{"mysql", "postgres"}
    47  )
    48  
    49  var tpccExample = templates.Examples(`
    50  	# tpcc on a cluster, that will exec for all steps, cleanup, prepare and run
    51  	kbcli bench tpcc mytest --cluster mycluster --user xxx --password xxx --database mydb
    52  	
    53  	# tpcc on a cluster with cleanup, only cleanup by deleting the testdata
    54  	kbcli bench tpcc cleanup mytest --cluster mycluster --user xxx --password xxx --database mydb
    55  	
    56  	# tpcc on a cluster with prepare, just prepare by creating the testdata
    57  	kbcli bench tpcc prepare mytest --cluster mycluster --user xxx --password xxx --database mydb
    58  	
    59  	# tpcc on a cluster with run, just run by running the test
    60  	kbcli bench tpcc run mytest --cluster mycluster --user xxx --password xxx --database mydb
    61  	
    62  	# tpcc on a cluster with warehouse counts, which is the overall database size scaling parameter
    63  	kbcli bench tpcc mytest --cluster mycluster --user xxx --password xxx --database mydb --warehouses 100
    64  	
    65  	# tpcc on a cluster with thread counts
    66  	kbcli bench tpcc mytest --cluster mycluster --user xxx --password xxx --database mydb --threads 4,8
    67  	
    68  	# tpcc on a cluster with transactions counts
    69  	kbcli bench tpcc mytest --cluster mycluster --user xxx --password xxx --database mydb --transactions 1000
    70  	
    71  	# tpcc on a cluster with duration 10 minutes
    72  	kbcli bench tpcc mytest --cluster mycluster --user xxx --password xxx --database mydb --duration 10
    73  `)
    74  
    75  type TpccOptions struct {
    76  	WareHouses    int   // specify the overall database size scaling parameter
    77  	Threads       []int // specify the number of threads to use
    78  	Transactions  int   // specify the number of transactions that each thread should run
    79  	Duration      int   // specify the number of minutes to run
    80  	LimitTxPerMin int   // limit the number of transactions to run per minute, 0 means no limit
    81  	NewOrder      int   // specify the percentage of transactions that should be new orders
    82  	Payment       int   // specify the percentage of transactions that should be payments
    83  	OrderStatus   int   // specify the percentage of transactions that should be order status
    84  	Delivery      int   // specify the percentage of transactions that should be delivery
    85  	StockLevel    int   // specify the percentage of transactions that should be stock level
    86  
    87  	BenchBaseOptions
    88  }
    89  
    90  func NewTpccCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
    91  	o := &TpccOptions{
    92  		BenchBaseOptions: BenchBaseOptions{
    93  			factory:   f,
    94  			IOStreams: streams,
    95  		},
    96  	}
    97  	cmd := &cobra.Command{
    98  		Use:     "tpcc [Step] [BenchmarkName]",
    99  		Short:   "Run tpcc benchmark",
   100  		Example: tpccExample,
   101  		Run: func(cmd *cobra.Command, args []string) {
   102  			cmdutil.CheckErr(o.Complete(args))
   103  			cmdutil.CheckErr(o.Validate())
   104  			cmdutil.CheckErr(o.Run())
   105  		},
   106  	}
   107  
   108  	o.AddFlags(cmd)
   109  	cmd.Flags().IntVar(&o.WareHouses, "warehouses", 1, "specify the overall database size scaling parameter")
   110  	cmd.Flags().IntSliceVar(&o.Threads, "threads", []int{1}, "specify the number of threads to use")
   111  	cmd.Flags().IntVar(&o.Transactions, "transactions", 0, "specify the number of transactions that each thread should run")
   112  	cmd.Flags().IntVar(&o.Duration, "duration", 1, "specify the number of minutes to run")
   113  	cmd.Flags().IntVar(&o.LimitTxPerMin, "limit-tx-per-min", 0, "limit the number of transactions to run per minute, 0 means no limit")
   114  	cmd.Flags().IntVar(&o.NewOrder, "new-order", 45, "specify the percentage of transactions that should be new orders")
   115  	cmd.Flags().IntVar(&o.Payment, "payment", 43, "specify the percentage of transactions that should be payments")
   116  	cmd.Flags().IntVar(&o.OrderStatus, "order-status", 4, "specify the percentage of transactions that should be order status")
   117  	cmd.Flags().IntVar(&o.Delivery, "delivery", 4, "specify the percentage of transactions that should be delivery")
   118  	cmd.Flags().IntVar(&o.StockLevel, "stock-level", 4, "specify the percentage of transactions that should be stock level")
   119  
   120  	return cmd
   121  }
   122  
   123  func (o *TpccOptions) Complete(args []string) error {
   124  	var err error
   125  	var driver string
   126  	var host string
   127  	var port int
   128  
   129  	if err = o.BenchBaseOptions.BaseComplete(); err != nil {
   130  		return err
   131  	}
   132  
   133  	o.Step, o.name = parseStepAndName(args, "tpcc")
   134  
   135  	o.namespace, _, err = o.factory.ToRawKubeConfigLoader().Namespace()
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	if o.dynamic, err = o.factory.DynamicClient(); err != nil {
   141  		return err
   142  	}
   143  
   144  	if o.client, err = o.factory.KubernetesClientSet(); err != nil {
   145  		return err
   146  	}
   147  
   148  	if o.ClusterName != "" {
   149  		clusterGetter := cluster.ObjectsGetter{
   150  			Client:    o.client,
   151  			Dynamic:   o.dynamic,
   152  			Name:      o.ClusterName,
   153  			Namespace: o.namespace,
   154  			GetOptions: cluster.GetOptions{
   155  				WithClusterDef:     true,
   156  				WithService:        true,
   157  				WithPod:            true,
   158  				WithEvent:          true,
   159  				WithPVC:            true,
   160  				WithDataProtection: true,
   161  			},
   162  		}
   163  		if o.ClusterObjects, err = clusterGetter.Get(); err != nil {
   164  			return err
   165  		}
   166  		driver, host, port, err = getDriverAndHostAndPort(o.Cluster, o.Services)
   167  		if err != nil {
   168  			return err
   169  		}
   170  	}
   171  
   172  	// don't overwrite the driver if it's already set
   173  	if v, ok := tpccDriverMap[driver]; ok && o.Driver == "" {
   174  		o.Driver = v
   175  	}
   176  
   177  	// don't overwrite the host and port if they are already set
   178  	if o.Host == "" && o.Port == 0 {
   179  		o.Host = host
   180  		o.Port = port
   181  	}
   182  
   183  	return nil
   184  }
   185  
   186  func (o *TpccOptions) Validate() error {
   187  	if err := o.BaseValidate(); err != nil {
   188  		return err
   189  	}
   190  
   191  	var supported bool
   192  	for _, v := range tpccDriverMap {
   193  		if o.Driver == v {
   194  			supported = true
   195  			break
   196  		}
   197  	}
   198  	if !supported {
   199  		return fmt.Errorf("tpcc now only supports drivers in [%s], current cluster driver is %s",
   200  			strings.Join(tpccSupportedDrivers, ","), o.Driver)
   201  	}
   202  
   203  	if o.User == "" {
   204  		return fmt.Errorf("user is required")
   205  	}
   206  
   207  	if o.Database == "" {
   208  		return fmt.Errorf("database is required")
   209  	}
   210  
   211  	if o.WareHouses < 1 {
   212  		return fmt.Errorf("warehouses must be greater than 0")
   213  	}
   214  
   215  	if o.Duration <= 0 {
   216  		return fmt.Errorf("duration must be greater than 0")
   217  	}
   218  
   219  	if o.NewOrder+o.Payment+o.OrderStatus+o.Delivery+o.StockLevel != 100 {
   220  		return fmt.Errorf("the sum of new-order, payment, order-status, delivery and stock-level must be 100")
   221  	}
   222  
   223  	return nil
   224  }
   225  
   226  func (o *TpccOptions) Run() error {
   227  	tpcc := v1alpha1.Tpcc{
   228  		TypeMeta: metav1.TypeMeta{
   229  			Kind:       "Tpcc",
   230  			APIVersion: types.TpccGVR().GroupVersion().String(),
   231  		},
   232  		ObjectMeta: metav1.ObjectMeta{
   233  			Name:      o.name,
   234  			Namespace: o.namespace,
   235  		},
   236  		Spec: v1alpha1.TpccSpec{
   237  			WareHouses:    o.WareHouses,
   238  			Threads:       o.Threads,
   239  			Transactions:  o.Transactions,
   240  			Duration:      o.Duration,
   241  			LimitTxPerMin: o.LimitTxPerMin,
   242  			NewOrder:      o.NewOrder,
   243  			Payment:       o.Payment,
   244  			OrderStatus:   o.OrderStatus,
   245  			Delivery:      o.Delivery,
   246  			StockLevel:    o.StockLevel,
   247  			BenchCommon: v1alpha1.BenchCommon{
   248  				ExtraArgs:   o.ExtraArgs,
   249  				Step:        o.Step,
   250  				Tolerations: o.Tolerations,
   251  				Target: v1alpha1.Target{
   252  					Driver:   o.Driver,
   253  					Host:     o.Host,
   254  					Port:     o.Port,
   255  					User:     o.User,
   256  					Password: o.Password,
   257  					Database: o.Database,
   258  				},
   259  			},
   260  		},
   261  	}
   262  
   263  	obj := &unstructured.Unstructured{
   264  		Object: map[string]interface{}{},
   265  	}
   266  	data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&tpcc)
   267  	if err != nil {
   268  		return err
   269  	}
   270  	obj.SetUnstructuredContent(data)
   271  
   272  	obj, err = o.dynamic.Resource(types.TpccGVR()).Namespace(o.namespace).Create(context.TODO(), obj, metav1.CreateOptions{})
   273  	if err != nil {
   274  		return err
   275  	}
   276  
   277  	fmt.Fprintf(o.Out, "%s %s created\n", obj.GetKind(), obj.GetName())
   278  	return nil
   279  }