From c54b9470a282c88ad735b26b29976b4825fbb53b Mon Sep 17 00:00:00 2001 From: Sara Wei Date: Sat, 23 Dec 2023 06:29:28 +0000 Subject: [PATCH 1/2] add multiple connections and support protobuf --- request/client.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/request/client.go b/request/client.go index 45b1c73..5231513 100644 --- a/request/client.go +++ b/request/client.go @@ -2,6 +2,7 @@ package request import ( "math" + "net/http" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -17,6 +18,7 @@ import ( // 3. Support Protobuf as accepted content func NewClients(kubeCfgPath string, num int, userAgent string, qps int) ([]rest.Interface, error) { restCfg, err := clientcmd.BuildConfigFromFlags("", kubeCfgPath) + if err != nil { return nil, err } @@ -24,13 +26,22 @@ func NewClients(kubeCfgPath string, num int, userAgent string, qps int) ([]rest. if qps == 0 { qps = math.MaxInt32 } + restCfg.QPS = float32(qps) restCfg.NegotiatedSerializer = scheme.Codecs.WithoutConversion() restCfg.UserAgent = userAgent + if restCfg.UserAgent == "" { restCfg.UserAgent = rest.DefaultKubernetesUserAgent() } + //set number of connections per host + restCfg.Transport = &http.Transport{ + MaxConnsPerHost: num, + } + + //set Protobuf as accepted content + restCfg.ContentType = "application/vnd.kubernetes.protobuf" restClients := make([]rest.Interface, 0, num) for i := 0; i < num; i++ { From 42be2de982f8e508044e19abc637d7b25bdd58d6 Mon Sep 17 00:00:00 2001 From: Sara Wei Date: Thu, 4 Jan 2024 02:08:08 +0000 Subject: [PATCH 2/2] add code to reuse connection and accept protobuf content type --- .gitignore | 5 +-- api/types/load_traffic.go | 2 + cmd/kperf/commands/runner/runner.go | 61 +++++++++++++++++++++++++++++ request/client.go | 5 --- request/schedule.go | 6 +-- 5 files changed, 67 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 1306df1..342c536 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,4 @@ bin/ # vendor/ # Go workspace file -go.work - -#tmp folder which contains .yaml files -tmp/ \ No newline at end of file +go.work \ No newline at end of file diff --git a/api/types/load_traffic.go b/api/types/load_traffic.go index 4abf8e8..628d6aa 100644 --- a/api/types/load_traffic.go +++ b/api/types/load_traffic.go @@ -20,6 +20,8 @@ type LoadProfileSpec struct { Total int `json:"total" yaml:"total"` // Conns defines total number of long connections used for traffic. Conns int `json:"conns" yaml:"conns"` + // Client defines total number of HTTP clients. + Client int `json:"client" yaml:"client"` // Requests defines the different kinds of requests with weights. // The executor should randomly pick by weight. Requests []*WeightedRequest diff --git a/cmd/kperf/commands/runner/runner.go b/cmd/kperf/commands/runner/runner.go index fb29939..64bea85 100644 --- a/cmd/kperf/commands/runner/runner.go +++ b/cmd/kperf/commands/runner/runner.go @@ -1,11 +1,17 @@ package runner import ( + "context" "context" "fmt" "os" "sort" + "github.com/Azure/kperf/api/types" + "github.com/Azure/kperf/request" + "os" + "sort" + "github.com/Azure/kperf/api/types" "github.com/Azure/kperf/request" @@ -13,6 +19,10 @@ import ( "gopkg.in/yaml.v2" ) +// Command represents runner subcommand. + "gopkg.in/yaml.v2" +) + // Command represents runner subcommand. var Command = cli.Command{ Name: "runner", @@ -22,6 +32,54 @@ var Command = cli.Command{ }, } +var runCommand = cli.Command{ + Name: "run", + Usage: "run a benchmark test to kube-apiserver", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "kubeconfig", + Usage: "Path to the kubeconfig file", + }, + cli.IntFlag{ + Name: "client", + Usage: "Total number of HTTP clients", + Value: 1, + }, + cli.StringFlag{ + Name: "config", + Usage: "Path to the configuration file", + Required: true, + }, + cli.IntFlag{ + Name: "conns", + Usage: "Total number of connections. It can override corresponding value defined by --config", + Value: 1, + }, + cli.StringFlag{ + Name: "content-type", + Usage: "Content type (json or protobuf)", + Value: "json", + }, + cli.IntFlag{ + Name: "rate", + Usage: "Maximum requests per second (Zero means no limitation). It can override corresponding value defined by --config", + }, + cli.IntFlag{ + Name: "total", + Usage: "Total number of requests. It can override corresponding value defined by --config", + Value: 1000, + }, + cli.StringFlag{ + Name: "user-agent", + Usage: "User Agent", + }, + }, + Usage: "Setup benchmark to kube-apiserver from one endpoint", + Subcommands: []cli.Command{ + runCommand, + }, +} + var runCommand = cli.Command{ Name: "run", Usage: "run a benchmark test to kube-apiserver", @@ -114,6 +172,9 @@ func loadConfig(cliCtx *cli.Context) (*types.LoadProfile, error) { if v := "conns"; cliCtx.IsSet(v) || profileCfg.Spec.Conns == 0 { profileCfg.Spec.Conns = cliCtx.Int(v) } + if v := "client"; cliCtx.IsSet(v) || profileCfg.Spec.Client == 0 { + profileCfg.Spec.Client = cliCtx.Int(v) + } if v := "total"; cliCtx.IsSet(v) || profileCfg.Spec.Total == 0 { profileCfg.Spec.Total = cliCtx.Int(v) } diff --git a/request/client.go b/request/client.go index cab0618..f60aa90 100644 --- a/request/client.go +++ b/request/client.go @@ -15,10 +15,8 @@ import ( // // 1. Is it possible to build one http2 client with multiple connections? // 2. How to monitor HTTP2 GOAWAY frame? -// 3. Support Protobuf as accepted content func NewClients(kubeCfgPath string, ConnsNum int, userAgent string, qps int, contentType string) ([]rest.Interface, error) { restCfg, err := clientcmd.BuildConfigFromFlags("", kubeCfgPath) - if err != nil { return nil, err } @@ -26,12 +24,10 @@ func NewClients(kubeCfgPath string, ConnsNum int, userAgent string, qps int, con if qps == 0 { qps = math.MaxInt32 } - restCfg.QPS = float32(qps) restCfg.NegotiatedSerializer = scheme.Codecs.WithoutConversion() restCfg.UserAgent = userAgent - if restCfg.UserAgent == "" { restCfg.UserAgent = rest.DefaultKubernetesUserAgent() } @@ -52,7 +48,6 @@ func NewClients(kubeCfgPath string, ConnsNum int, userAgent string, qps int, con restCli, err := rest.UnversionedRESTClientFor(&cfgShallowCopy) if err != nil { - fmt.Printf("Failed to create rest client: %v\n", err) return nil, err } restClients = append(restClients, restCli) diff --git a/request/schedule.go b/request/schedule.go index df315aa..c6b7324 100644 --- a/request/schedule.go +++ b/request/schedule.go @@ -17,7 +17,7 @@ import ( const defaultTimeout = 60 * time.Second // Schedule files requests to apiserver based on LoadProfileSpec. -func Schedule(ctx context.Context, clientNum int, spec *types.LoadProfileSpec, restCli []rest.Interface) (*types.ResponseStats, error) { +func Schedule(ctx context.Context, spec *types.LoadProfileSpec, restCli []rest.Interface) (*types.ResponseStats, error) { ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -36,8 +36,8 @@ func Schedule(ctx context.Context, clientNum int, spec *types.LoadProfileSpec, r var wg sync.WaitGroup respMetric := metrics.NewResponseMetric() - for i := 0; i < clientNum; i++ { - //reuse connection if client > conns + for i := 0; i < spec.Client; i++ { + // reuse connection if clients > conns cli := restCli[i%len(restCli)] wg.Add(1) go func(cli rest.Interface) {