From 5ae6f055e02881211ad7439aa05942bbb55c8db2 Mon Sep 17 00:00:00 2001 From: Liyu Ma Date: Tue, 17 Mar 2026 10:31:11 +1000 Subject: [PATCH 01/12] Add commands for event and secret data ingestion --- .../runkperf/commands/data/events/event.go | 410 ++++++++++++++++++ .../runkperf/commands/data/secrets/secret.go | 389 +++++++++++++++++ contrib/cmd/runkperf/commands/root.go | 4 + 3 files changed, 803 insertions(+) create mode 100644 contrib/cmd/runkperf/commands/data/events/event.go create mode 100644 contrib/cmd/runkperf/commands/data/secrets/secret.go diff --git a/contrib/cmd/runkperf/commands/data/events/event.go b/contrib/cmd/runkperf/commands/data/events/event.go new file mode 100644 index 0000000..ccdc068 --- /dev/null +++ b/contrib/cmd/runkperf/commands/data/events/event.go @@ -0,0 +1,410 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package events + +import ( + "context" + "crypto/rand" + "fmt" + "math/big" + "os" + "strings" + "text/tabwriter" + "time" + + "golang.org/x/sync/errgroup" + + "github.com/Azure/kperf/cmd/kperf/commands/utils" + "github.com/Azure/kperf/contrib/cmd/runkperf/commands/data" + + "github.com/urfave/cli" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +var appLabel = "runkperf" + +var Command = cli.Command{ + Name: "event", + ShortName: "ev", + Usage: "Manage events", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "kubeconfig", + Usage: "Path to the kubeconfig file", + Value: utils.DefaultKubeConfigPath, + }, + cli.StringFlag{ + Name: "namespace", + Usage: "Namespace to use with commands. If the namespace does not exist, it will be created.", + Value: "default", + }, + }, + Subcommands: []cli.Command{ + eventAddCommand, + eventDelCommand, + eventListCommand, + }, +} + +var eventAddCommand = cli.Command{ + Name: "add", + Usage: "Add events set", + ArgsUsage: "NAME of the events set", + Flags: []cli.Flag{ + cli.IntFlag{ + Name: "total", + Usage: "Total number of events to create", + Value: 10, + }, + cli.IntFlag{ + Name: "group-size", + Usage: "Number of events to create in parallel per batch", + Value: 30, + }, + cli.StringFlag{ + Name: "reason", + Usage: "The reason string for the events (e.g. ScaleUp, FailedScheduling)", + Value: "BenchmarkEvent", + }, + cli.StringFlag{ + Name: "type", + Usage: "The type of the events: Normal or Warning", + Value: "Normal", + }, + cli.IntFlag{ + Name: "message-size", + Usage: "The size of the event message in bytes (default targets ~5KB total event size; k8s metadata overhead is ~500-700 bytes)", + Value: 5000, + }, + cli.StringFlag{ + Name: "involved-object-kind", + Usage: "The kind of the involved object (e.g. Pod, Node, Deployment)", + Value: "Pod", + }, + cli.StringFlag{ + Name: "involved-object-name", + Usage: "The name of the involved object. If empty, a name will be generated.", + Value: "", + }, + cli.Float64Flag{ + Name: "qps", + Usage: "QPS for the Kubernetes client rate limiter to control event operations", + Value: 100, + }, + cli.IntFlag{ + Name: "burst", + Usage: "Burst for the Kubernetes client rate limiter to control event operations", + Value: 200, + }, + }, + Action: func(cliCtx *cli.Context) error { + if cliCtx.NArg() != 1 { + return fmt.Errorf("required only one argument as events set name: %v", cliCtx.Args()) + } + evName := strings.TrimSpace(cliCtx.Args().Get(0)) + if len(evName) == 0 { + return fmt.Errorf("required non-empty event set name") + } + + kubeCfgPath := cliCtx.GlobalString("kubeconfig") + namespace := cliCtx.GlobalString("namespace") + total := cliCtx.Int("total") + groupSize := cliCtx.Int("group-size") + reason := cliCtx.String("reason") + eventType := cliCtx.String("type") + messageSize := cliCtx.Int("message-size") + involvedObjectKind := cliCtx.String("involved-object-kind") + involvedObjectName := cliCtx.String("involved-object-name") + qps := float32(cliCtx.Float64("qps")) + burst := cliCtx.Int("burst") + + if total <= 0 { + return fmt.Errorf("total must be greater than 0") + } + if groupSize <= 0 { + return fmt.Errorf("group-size must be greater than 0") + } + if groupSize > total { + return fmt.Errorf("group-size must be less than or equal to total") + } + if eventType != "Normal" && eventType != "Warning" { + return fmt.Errorf("type must be either Normal or Warning") + } + if messageSize <= 0 { + return fmt.Errorf("message-size must be greater than 0") + } + + clientset, err := data.NewClientsetWithRateLimiter(kubeCfgPath, qps, burst) + if err != nil { + return err + } + + err = prepareNamespace(clientset, namespace) + if err != nil { + return err + } + + err = createEvents(clientset, namespace, evName, total, groupSize, reason, eventType, messageSize, involvedObjectKind, involvedObjectName) + if err != nil { + return err + } + fmt.Printf("Created %d events (set=%s, reason=%s, type=%s) in namespace %s\n", + total, evName, reason, eventType, namespace) + return nil + }, +} + +var eventDelCommand = cli.Command{ + Name: "delete", + ShortName: "del", + ArgsUsage: "NAME", + Usage: "Delete an events set", + Action: func(cliCtx *cli.Context) error { + if cliCtx.NArg() != 1 { + return fmt.Errorf("required only one events set name") + } + evName := strings.TrimSpace(cliCtx.Args().Get(0)) + if len(evName) == 0 { + return fmt.Errorf("required non-empty events set name") + } + + namespace := cliCtx.GlobalString("namespace") + kubeCfgPath := cliCtx.GlobalString("kubeconfig") + labelSelector := fmt.Sprintf("app=%s,evName=%s", appLabel, evName) + + clientset, err := data.NewClientset(kubeCfgPath) + if err != nil { + return err + } + + err = deleteEvents(clientset, labelSelector, namespace) + if err != nil { + return err + } + + fmt.Printf("Deleted events set %s in %s namespace\n", evName, namespace) + return nil + }, +} + +var eventListCommand = cli.Command{ + Name: "list", + Usage: "List generated event sets. Lists all if no arguments are given; otherwise, provide event set names separated by spaces.", + Action: func(cliCtx *cli.Context) error { + namespace := cliCtx.GlobalString("namespace") + kubeCfgPath := cliCtx.GlobalString("kubeconfig") + clientset, err := data.NewClientset(kubeCfgPath) + if err != nil { + return err + } + + const ( + minWidth = 1 + tabWidth = 12 + padding = 3 + padChar = ' ' + flags = 0 + ) + tw := tabwriter.NewWriter(os.Stdout, minWidth, tabWidth, padding, padChar, flags) + fmt.Fprintln(tw, "NAME\tTYPE\tREASON\tTOTAL\t") + + var labelSelector string + if cliCtx.NArg() == 0 { + labelSelector = fmt.Sprintf("app=%s,evName", appLabel) + } else { + args := cliCtx.Args() + namesStr := strings.Join(args, ",") + labelSelector = fmt.Sprintf("app=%s, evName in (%s)", appLabel, namesStr) + } + + evMap, err := listEventsByName(clientset, labelSelector, namespace) + if err != nil { + return err + } + + for name, info := range evMap { + fmt.Fprintf(tw, "%s\t%s\t%s\t%d\n", + name, + info.eventType, + info.reason, + info.total, + ) + } + return tw.Flush() + }, +} + +type eventSetInfo struct { + eventType string + reason string + total int +} + +func prepareNamespace(clientset *kubernetes.Clientset, namespace string) error { + if namespace == "" { + return fmt.Errorf("namespace cannot be empty") + } + + if namespace == "default" { + return nil + } + + _, err := clientset.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + }, metav1.CreateOptions{}) + if err != nil { + if errors.IsAlreadyExists(err) { + return nil + } + return fmt.Errorf("failed to create namespace %s: %v", namespace, err) + } + return nil +} + +var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + +func randString(n int) (string, error) { + if n <= 0 { + return "", fmt.Errorf("length must be positive") + } + + b := make([]rune, n) + for i := range b { + random, err := rand.Int(rand.Reader, big.NewInt(int64(len(letterRunes)))) + if err != nil { + return "", fmt.Errorf("error generating random number: %w", err) + } + b[i] = letterRunes[int(random.Int64())] + } + return string(b), nil +} + +func createEvents(clientset *kubernetes.Clientset, namespace, evName string, total, groupSize int, reason, eventType string, messageSize int, involvedObjectKind, involvedObjectName string) error { + now := time.Now() + + for i := 0; i < total; i += groupSize { + g := new(errgroup.Group) + for j := i; j < i+groupSize && j < total; j++ { + idx := j + g.Go(func() error { + name := fmt.Sprintf("%s-ev-%s-%d", appLabel, evName, idx) + + message, err := randString(messageSize) + if err != nil { + return fmt.Errorf("failed to generate random message for event %s: %v", name, err) + } + + objName := involvedObjectName + if objName == "" { + objName = fmt.Sprintf("%s-obj-%d", evName, idx) + } + + event := &corev1.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: map[string]string{ + "app": appLabel, + "evName": evName, + }, + }, + InvolvedObject: corev1.ObjectReference{ + Kind: involvedObjectKind, + Name: objName, + Namespace: namespace, + }, + Reason: reason, + Message: message, + Type: eventType, + Count: 1, + FirstTimestamp: metav1.NewTime(now), + LastTimestamp: metav1.NewTime(now), + Source: corev1.EventSource{ + Component: "runkperf-bench", + }, + } + + _, err = clientset.CoreV1().Events(namespace).Create(context.TODO(), event, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to create event %s: %v", name, err) + } + return nil + }) + } + if err := g.Wait(); err != nil { + return err + } + } + return nil +} + +func deleteEvents(clientset *kubernetes.Clientset, labelSelector, namespace string) error { + eventList, err := listEvents(clientset, labelSelector, namespace) + if err != nil { + return err + } + + if len(eventList.Items) == 0 { + return fmt.Errorf("no events set found in namespace: %s", namespace) + } + + n, batch := len(eventList.Items), 300 + for i := 0; i < n; i += batch { + g := new(errgroup.Group) + for j := i; j < i+batch && j < n; j++ { + idx := j + g.Go(func() error { + err := clientset.CoreV1().Events(namespace).Delete(context.TODO(), eventList.Items[idx].Name, metav1.DeleteOptions{}) + if err != nil && !errors.IsNotFound(err) { + return fmt.Errorf("failed to delete event %s: %v", eventList.Items[idx].Name, err) + } + return nil + }) + } + if err := g.Wait(); err != nil { + return err + } + } + return nil +} + +func listEvents(clientset *kubernetes.Clientset, labelSelector, namespace string) (*corev1.EventList, error) { + eventList, err := clientset.CoreV1().Events(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector}) + if err != nil { + return nil, fmt.Errorf("failed to list events: %v", err) + } + return eventList, nil +} + +func listEventsByName(clientset *kubernetes.Clientset, labelSelector, namespace string) (map[string]*eventSetInfo, error) { + eventList, err := listEvents(clientset, labelSelector, namespace) + if err != nil { + return nil, err + } + + evMap := make(map[string]*eventSetInfo) + for _, ev := range eventList.Items { + name, ok := ev.Labels["evName"] + if !ok { + continue + } + + info, ok := evMap[name] + if !ok { + info = &eventSetInfo{ + eventType: ev.Type, + reason: ev.Reason, + } + evMap[name] = info + } + info.total++ + } + return evMap, nil +} diff --git a/contrib/cmd/runkperf/commands/data/secrets/secret.go b/contrib/cmd/runkperf/commands/data/secrets/secret.go new file mode 100644 index 0000000..dff497b --- /dev/null +++ b/contrib/cmd/runkperf/commands/data/secrets/secret.go @@ -0,0 +1,389 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package secrets + +import ( + "context" + "crypto/rand" + "fmt" + "math/big" + "os" + "strconv" + "strings" + "text/tabwriter" + + "golang.org/x/sync/errgroup" + + "github.com/Azure/kperf/cmd/kperf/commands/utils" + "github.com/Azure/kperf/contrib/cmd/runkperf/commands/data" + + "github.com/urfave/cli" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +var appLabel = "runkperf" + +var Command = cli.Command{ + Name: "secret", + ShortName: "sec", + Usage: "Manage secrets", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "kubeconfig", + Usage: "Path to the kubeconfig file", + Value: utils.DefaultKubeConfigPath, + }, + cli.StringFlag{ + Name: "namespace", + Usage: "Namespace to use with commands. If the namespace does not exist, it will be created.", + Value: "default", + }, + }, + Subcommands: []cli.Command{ + secretAddCommand, + secretDelCommand, + secretListCommand, + }, +} + +var secretAddCommand = cli.Command{ + Name: "add", + Usage: "Add secrets set", + ArgsUsage: "NAME of the secrets set", + Flags: []cli.Flag{ + cli.IntFlag{ + Name: "size", + Usage: "The size of each secret value (Unit: Byte)", + Value: 100, + }, + cli.IntFlag{ + Name: "group-size", + Usage: "The number of secrets to create in parallel per batch", + Value: 30, + }, + cli.IntFlag{ + Name: "total", + Usage: "Total number of secrets to create", + Value: 10, + }, + cli.Float64Flag{ + Name: "qps", + Usage: "QPS for the Kubernetes client rate limiter to control secret operations", + Value: 100, + }, + cli.IntFlag{ + Name: "burst", + Usage: "Burst for the Kubernetes client rate limiter to control secret operations", + Value: 200, + }, + }, + Action: func(cliCtx *cli.Context) error { + if cliCtx.NArg() != 1 { + return fmt.Errorf("required only one argument as secrets set name: %v", cliCtx.Args()) + } + secName := strings.TrimSpace(cliCtx.Args().Get(0)) + if len(secName) == 0 { + return fmt.Errorf("required non-empty secret set name") + } + + kubeCfgPath := cliCtx.GlobalString("kubeconfig") + size := cliCtx.Int("size") + groupSize := cliCtx.Int("group-size") + total := cliCtx.Int("total") + + err := checkSecretParams(size, groupSize, total) + if err != nil { + return err + } + + namespace := cliCtx.GlobalString("namespace") + qps := float32(cliCtx.Float64("qps")) + burst := cliCtx.Int("burst") + + clientset, err := data.NewClientsetWithRateLimiter(kubeCfgPath, qps, burst) + if err != nil { + return err + } + + err = prepareNamespace(clientset, namespace) + if err != nil { + return err + } + + err = createSecrets(clientset, namespace, secName, size, groupSize, total) + if err != nil { + return err + } + fmt.Printf("Created secret %s with size %d B, group-size %d, total %d\n", secName, size, groupSize, total) + return nil + }, +} + +var secretDelCommand = cli.Command{ + Name: "delete", + ShortName: "del", + ArgsUsage: "NAME", + Usage: "Delete a secrets set", + Action: func(cliCtx *cli.Context) error { + if cliCtx.NArg() != 1 { + return fmt.Errorf("required only one secrets set name") + } + secName := strings.TrimSpace(cliCtx.Args().Get(0)) + if len(secName) == 0 { + return fmt.Errorf("required non-empty secrets set name") + } + + namespace := cliCtx.GlobalString("namespace") + kubeCfgPath := cliCtx.GlobalString("kubeconfig") + labelSelector := fmt.Sprintf("app=%s,secName=%s", appLabel, secName) + + clientset, err := data.NewClientset(kubeCfgPath) + if err != nil { + return err + } + + err = deleteSecrets(clientset, labelSelector, namespace) + if err != nil { + return err + } + + fmt.Printf("Deleted secrets set %s in %s namespace\n", secName, namespace) + return nil + }, +} + +var secretListCommand = cli.Command{ + Name: "list", + Usage: "List generated secrets. Lists all if no arguments are given; otherwise, provide secret set names separated by spaces.", + ArgsUsage: "NAME", + Action: func(cliCtx *cli.Context) error { + namespace := cliCtx.GlobalString("namespace") + kubeCfgPath := cliCtx.GlobalString("kubeconfig") + clientset, err := data.NewClientset(kubeCfgPath) + if err != nil { + return err + } + + const ( + minWidth = 1 + tabWidth = 12 + padding = 3 + padChar = ' ' + flags = 0 + ) + tw := tabwriter.NewWriter(os.Stdout, minWidth, tabWidth, padding, padChar, flags) + fmt.Fprintln(tw, "NAME\tSIZE\tGROUP_SIZE\tTOTAL\t") + + var labelSelector string + if cliCtx.NArg() == 0 { + labelSelector = fmt.Sprintf("app=%s,secName", appLabel) + } else { + args := cliCtx.Args() + namesStr := strings.Join(args, ",") + labelSelector = fmt.Sprintf("app=%s, secName in (%s)", appLabel, namesStr) + } + + secMap := make(map[string][]int) + err = listSecretsByName(clientset, labelSelector, namespace, secMap) + if err != nil { + return err + } + + for key, value := range secMap { + fmt.Fprintf(tw, "%s\t%d\t%d\t%d\n", + key, + value[0], + value[1], + value[2], + ) + } + return tw.Flush() + }, +} + +func prepareNamespace(clientset *kubernetes.Clientset, namespace string) error { + if namespace == "" { + return fmt.Errorf("namespace cannot be empty") + } + + if namespace == "default" { + return nil + } + + _, err := clientset.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + }, metav1.CreateOptions{}) + if err != nil { + if errors.IsAlreadyExists(err) { + return nil + } + return fmt.Errorf("failed to create namespace %s: %v", namespace, err) + } + return nil +} + +func checkSecretParams(size int, groupSize int, total int) error { + if size <= 0 { + return fmt.Errorf("size must be greater than 0") + } + if groupSize <= 0 { + return fmt.Errorf("group-size must be greater than 0") + } + if total <= 0 { + return fmt.Errorf("total amount must be greater than 0") + } + if groupSize > total { + return fmt.Errorf("group-size must be less than or equal to total") + } + return nil +} + +var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + +func randBytes(n int) ([]byte, error) { + if n <= 0 { + return nil, fmt.Errorf("length must be positive") + } + + b := make([]byte, n) + for i := range b { + random, err := rand.Int(rand.Reader, big.NewInt(int64(len(letterRunes)))) + if err != nil { + return nil, fmt.Errorf("error generating random number: %w", err) + } + b[i] = byte(letterRunes[int(random.Int64())]) + } + return b, nil +} + +func createSecrets(clientset *kubernetes.Clientset, namespace string, secName string, size int, groupSize int, total int) error { + for i := 0; i < total; i += groupSize { + ownerID := i + g := new(errgroup.Group) + for j := i; j < i+groupSize && j < total; j++ { + idx := j + g.Go(func() error { + name := fmt.Sprintf("%s-sec-%s-%d", appLabel, secName, idx) + + data, err := randBytes(size) + if err != nil { + return fmt.Errorf("failed to generate random data for secret %s: %v", name, err) + } + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: map[string]string{ + "ownerID": strconv.Itoa(ownerID), + "app": appLabel, + "secName": secName, + }, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "data": data, + }, + } + + _, err = clientset.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to create secret %s: %v", name, err) + } + return nil + }) + } + if err := g.Wait(); err != nil { + return err + } + } + return nil +} + +func deleteSecrets(clientset *kubernetes.Clientset, labelSelector string, namespace string) error { + secrets, err := listSecrets(clientset, labelSelector, namespace) + if err != nil { + return err + } + + if len(secrets.Items) == 0 { + return fmt.Errorf("no secrets set found in namespace: %s", namespace) + } + + n, batch := len(secrets.Items), 300 + for i := 0; i < n; i += batch { + g := new(errgroup.Group) + for j := i; j < i+batch && j < n; j++ { + idx := j + g.Go(func() error { + err := clientset.CoreV1().Secrets(namespace).Delete(context.TODO(), secrets.Items[idx].Name, metav1.DeleteOptions{}) + if err != nil && !errors.IsNotFound(err) { + return fmt.Errorf("failed to delete secret %s: %v", secrets.Items[idx].Name, err) + } + return nil + }) + } + if err := g.Wait(); err != nil { + return err + } + } + return nil +} + +func listSecrets(clientset *kubernetes.Clientset, labelSelector string, namespace string) (*corev1.SecretList, error) { + secrets, err := clientset.CoreV1().Secrets(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector}) + if err != nil { + return nil, fmt.Errorf("failed to list secrets: %v", err) + } + return secrets, nil +} + +func listSecretsByName(clientset *kubernetes.Clientset, labelSelector string, namespace string, secMap map[string][]int) error { + secrets, err := listSecrets(clientset, labelSelector, namespace) + if err != nil { + return err + } + + for _, sec := range secrets.Items { + name, ok := sec.Labels["secName"] + if !ok { + return fmt.Errorf("failed to find the secName of secret %s", sec.Name) + } + + _, ok = secMap[name] + if !ok { + // Initialize: size, group-size, total + secMap[name] = []int{0, 0, 0} + + if data, exists := sec.Data["data"]; exists { + secMap[name][0] = len(data) + } + } + + secMap[name][2]++ + + if secMap[name][1] != 0 { + continue + } + + ownerID, ok := sec.Labels["ownerID"] + if !ok { + return fmt.Errorf("failed to find the ownerID of secret %s", name) + } + + if ownerIDInt, err := strconv.Atoi(ownerID); err == nil { + if ownerIDInt > secMap[name][1] { + secMap[name][1] = ownerIDInt + } + } else { + return fmt.Errorf("failed to convert ownerID %s to int: %v", ownerID, err) + } + } + return nil +} diff --git a/contrib/cmd/runkperf/commands/root.go b/contrib/cmd/runkperf/commands/root.go index 50796d1..ab86680 100644 --- a/contrib/cmd/runkperf/commands/root.go +++ b/contrib/cmd/runkperf/commands/root.go @@ -13,6 +13,8 @@ import ( "github.com/Azure/kperf/contrib/cmd/runkperf/commands/data" "github.com/Azure/kperf/contrib/cmd/runkperf/commands/data/configmaps" "github.com/Azure/kperf/contrib/cmd/runkperf/commands/data/daemonsets" + "github.com/Azure/kperf/contrib/cmd/runkperf/commands/data/events" + "github.com/Azure/kperf/contrib/cmd/runkperf/commands/data/secrets" "github.com/Azure/kperf/contrib/cmd/runkperf/commands/warmup" "github.com/urfave/cli" @@ -23,6 +25,8 @@ func init() { data.RegisterSubcommands( configmaps.Command, daemonsets.Command, + events.Command, + secrets.Command, ) } From 04b1834b19aaa7a861c989f179a8850710be8c2e Mon Sep 17 00:00:00 2001 From: Liyu Ma Date: Tue, 17 Mar 2026 10:33:05 +1000 Subject: [PATCH 02/12] add git ignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index d8b5cd0..df1d344 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ tmp/ # VSCode settings .vscode/ + +# Claude Code +.claude/ From 3ce5927db5c00a625bf1eb6109c0d52b76a3e469 Mon Sep 17 00:00:00 2001 From: Liyu Ma Date: Tue, 17 Mar 2026 12:44:26 +1000 Subject: [PATCH 03/12] Reuse PrepareNamespace func --- contrib/cmd/runkperf/commands/data/client.go | 31 +++++++++++ .../commands/data/configmaps/configmap.go | 26 +-------- .../commands/data/daemonsets/daemonset.go | 55 ++----------------- .../runkperf/commands/data/events/event.go | 25 +-------- .../runkperf/commands/data/secrets/secret.go | 25 +-------- 5 files changed, 39 insertions(+), 123 deletions(-) diff --git a/contrib/cmd/runkperf/commands/data/client.go b/contrib/cmd/runkperf/commands/data/client.go index 47e6df2..156db49 100644 --- a/contrib/cmd/runkperf/commands/data/client.go +++ b/contrib/cmd/runkperf/commands/data/client.go @@ -4,6 +4,12 @@ package data import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/flowcontrol" @@ -29,3 +35,28 @@ func NewClientsetWithRateLimiter(kubeCfgPath string, qps float32, burst int) (*k config.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(qps, burst) return kubernetes.NewForConfig(config) } + +// PrepareNamespace creates the namespace if it does not already exist. +// It skips creation for the "default" namespace. +func PrepareNamespace(clientset *kubernetes.Clientset, namespace string) error { + if namespace == "" { + return fmt.Errorf("namespace cannot be empty") + } + + if namespace == "default" { + return nil + } + + _, err := clientset.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + }, metav1.CreateOptions{}) + if err != nil { + if errors.IsAlreadyExists(err) { + return nil + } + return fmt.Errorf("failed to create namespace %s: %v", namespace, err) + } + return nil +} diff --git a/contrib/cmd/runkperf/commands/data/configmaps/configmap.go b/contrib/cmd/runkperf/commands/data/configmaps/configmap.go index 6ce936a..e252888 100644 --- a/contrib/cmd/runkperf/commands/data/configmaps/configmap.go +++ b/contrib/cmd/runkperf/commands/data/configmaps/configmap.go @@ -111,7 +111,7 @@ var configmapAddCommand = cli.Command{ return err } - err = prepareNamespace(clientset, namespace) + err = data.PrepareNamespace(clientset, namespace) if err != nil { return err } @@ -213,30 +213,6 @@ var configmapListCommand = cli.Command{ }, } -func prepareNamespace(clientset *kubernetes.Clientset, namespace string) error { - if namespace == "" { - return fmt.Errorf("namespace cannot be empty") - } - - if namespace == "default" { - return nil - } - - _, err := clientset.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - }, - }, metav1.CreateOptions{}) - if err != nil { - // If the namespace already exists, ignore the error - if errors.IsAlreadyExists(err) { - return nil - } - return fmt.Errorf("failed to create namespace %s: %v", namespace, err) - } - return nil -} - func checkConfigmapParams(size int, groupSize int, total int) error { if size <= 0 { return fmt.Errorf("size must be greater than 0") diff --git a/contrib/cmd/runkperf/commands/data/daemonsets/daemonset.go b/contrib/cmd/runkperf/commands/data/daemonsets/daemonset.go index 9c7b79f..79d3b06 100644 --- a/contrib/cmd/runkperf/commands/data/daemonsets/daemonset.go +++ b/contrib/cmd/runkperf/commands/data/daemonsets/daemonset.go @@ -11,8 +11,7 @@ import ( "text/tabwriter" "github.com/Azure/kperf/cmd/kperf/commands/utils" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/util/flowcontrol" + "github.com/Azure/kperf/contrib/cmd/runkperf/commands/data" "github.com/urfave/cli" @@ -76,12 +75,12 @@ var daemonsetAddCommand = cli.Command{ return fmt.Errorf("count must be greater than 0") } - err := prepareNamespace(kubeCfgPath, namespace) + clientset, err := data.NewClientsetWithRateLimiter(kubeCfgPath, 30, 10) if err != nil { return err } - clientset, err := newClientsetWithRateLimiter(kubeCfgPath, 30, 10) + err = data.PrepareNamespace(clientset, namespace) if err != nil { return err } @@ -113,7 +112,7 @@ var daemonsetDelCommand = cli.Command{ namespace := cliCtx.GlobalString("namespace") kubeCfgPath := cliCtx.GlobalString("kubeconfig") - clientset, err := newClientsetWithRateLimiter(kubeCfgPath, 30, 10) + clientset, err := data.NewClientsetWithRateLimiter(kubeCfgPath, 30, 10) if err != nil { return err } @@ -139,7 +138,7 @@ var daemonsetListCommand = cli.Command{ Action: func(cliCtx *cli.Context) error { namespace := cliCtx.GlobalString("namespace") kubeCfgPath := cliCtx.GlobalString("kubeconfig") - clientset, err := newClientsetWithRateLimiter(kubeCfgPath, 30, 10) + clientset, err := data.NewClientsetWithRateLimiter(kubeCfgPath, 30, 10) if err != nil { return err } @@ -192,50 +191,6 @@ var daemonsetListCommand = cli.Command{ }, } -func prepareNamespace(kubeCfgPath string, namespace string) error { - if namespace == "" { - return fmt.Errorf("namespace cannot be empty") - } - - if namespace == "default" { - return nil - } - - clientset, err := newClientsetWithRateLimiter(kubeCfgPath, 30, 10) - if err != nil { - return err - } - - _, err = clientset.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - }, - }, metav1.CreateOptions{}) - if err != nil { - // If the namespace already exists, ignore the error - if errors.IsAlreadyExists(err) { - return nil - } - return fmt.Errorf("failed to create namespace %s: %v", namespace, err) - } - return nil -} - -func newClientsetWithRateLimiter(kubeCfgPath string, qps float32, burst int) (*kubernetes.Clientset, error) { - config, err := clientcmd.BuildConfigFromFlags("", kubeCfgPath) - if err != nil { - return nil, err - } - - config.QPS = qps - config.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(qps, burst) - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, err - } - return clientset, nil -} - func createDaemonsets(clientset *kubernetes.Clientset, namespace string, dsName string, count int) error { for i := 0; i < count; i++ { ds := &appsv1.DaemonSet{ diff --git a/contrib/cmd/runkperf/commands/data/events/event.go b/contrib/cmd/runkperf/commands/data/events/event.go index ccdc068..0d955c8 100644 --- a/contrib/cmd/runkperf/commands/data/events/event.go +++ b/contrib/cmd/runkperf/commands/data/events/event.go @@ -144,7 +144,7 @@ var eventAddCommand = cli.Command{ return err } - err = prepareNamespace(clientset, namespace) + err = data.PrepareNamespace(clientset, namespace) if err != nil { return err } @@ -245,29 +245,6 @@ type eventSetInfo struct { total int } -func prepareNamespace(clientset *kubernetes.Clientset, namespace string) error { - if namespace == "" { - return fmt.Errorf("namespace cannot be empty") - } - - if namespace == "default" { - return nil - } - - _, err := clientset.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - }, - }, metav1.CreateOptions{}) - if err != nil { - if errors.IsAlreadyExists(err) { - return nil - } - return fmt.Errorf("failed to create namespace %s: %v", namespace, err) - } - return nil -} - var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") func randString(n int) (string, error) { diff --git a/contrib/cmd/runkperf/commands/data/secrets/secret.go b/contrib/cmd/runkperf/commands/data/secrets/secret.go index dff497b..8c9b4bf 100644 --- a/contrib/cmd/runkperf/commands/data/secrets/secret.go +++ b/contrib/cmd/runkperf/commands/data/secrets/secret.go @@ -110,7 +110,7 @@ var secretAddCommand = cli.Command{ return err } - err = prepareNamespace(clientset, namespace) + err = data.PrepareNamespace(clientset, namespace) if err != nil { return err } @@ -206,29 +206,6 @@ var secretListCommand = cli.Command{ }, } -func prepareNamespace(clientset *kubernetes.Clientset, namespace string) error { - if namespace == "" { - return fmt.Errorf("namespace cannot be empty") - } - - if namespace == "default" { - return nil - } - - _, err := clientset.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - }, - }, metav1.CreateOptions{}) - if err != nil { - if errors.IsAlreadyExists(err) { - return nil - } - return fmt.Errorf("failed to create namespace %s: %v", namespace, err) - } - return nil -} - func checkSecretParams(size int, groupSize int, total int) error { if size <= 0 { return fmt.Errorf("size must be greater than 0") From dc931bdf3c6a294154b407625f591dbdc14f400c Mon Sep 17 00:00:00 2001 From: Liyu Ma Date: Tue, 17 Mar 2026 13:05:53 +1000 Subject: [PATCH 04/12] reuse RandBytes --- contrib/cmd/runkperf/commands/data/client.go | 30 ++++++++++++++++ .../commands/data/configmaps/configmap.go | 36 +++++-------------- .../runkperf/commands/data/events/event.go | 22 +----------- .../runkperf/commands/data/secrets/secret.go | 24 ++----------- 4 files changed, 41 insertions(+), 71 deletions(-) diff --git a/contrib/cmd/runkperf/commands/data/client.go b/contrib/cmd/runkperf/commands/data/client.go index 156db49..6b2ddcf 100644 --- a/contrib/cmd/runkperf/commands/data/client.go +++ b/contrib/cmd/runkperf/commands/data/client.go @@ -5,7 +5,9 @@ package data import ( "context" + "crypto/rand" "fmt" + "math/big" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -60,3 +62,31 @@ func PrepareNamespace(clientset *kubernetes.Clientset, namespace string) error { } return nil } + +var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + +// RandBytes generates a random byte slice of length n using alphanumeric characters. +func RandBytes(n int) ([]byte, error) { + if n <= 0 { + return nil, fmt.Errorf("length must be positive") + } + + b := make([]byte, n) + for i := range b { + random, err := rand.Int(rand.Reader, big.NewInt(int64(len(letterRunes)))) + if err != nil { + return nil, fmt.Errorf("error generating random number: %w", err) + } + b[i] = byte(letterRunes[int(random.Int64())]) + } + return b, nil +} + +// RandString generates a random string of length n using alphanumeric characters. +func RandString(n int) (string, error) { + b, err := RandBytes(n) + if err != nil { + return "", err + } + return string(b), nil +} diff --git a/contrib/cmd/runkperf/commands/data/configmaps/configmap.go b/contrib/cmd/runkperf/commands/data/configmaps/configmap.go index e252888..9675085 100644 --- a/contrib/cmd/runkperf/commands/data/configmaps/configmap.go +++ b/contrib/cmd/runkperf/commands/data/configmaps/configmap.go @@ -5,9 +5,7 @@ package configmaps import ( "context" - "crypto/rand" "fmt" - "math/big" "os" "strconv" "strings" @@ -26,7 +24,7 @@ import ( "k8s.io/client-go/kubernetes" ) -var appLebel = "runkperf" +var appLabel = "runkperf" var Command = cli.Command{ Name: "configmap", @@ -141,7 +139,7 @@ var configmapDelCommand = cli.Command{ namespace := cliCtx.GlobalString("namespace") kubeCfgPath := cliCtx.GlobalString("kubeconfig") - labelSelector := fmt.Sprintf("app=%s,cmName=%s", appLebel, cmName) + labelSelector := fmt.Sprintf("app=%s,cmName=%s", appLabel, cmName) clientset, err := data.NewClientset(kubeCfgPath) if err != nil { @@ -187,12 +185,12 @@ var configmapListCommand = cli.Command{ // If args are provided, list all configmaps with the label app=runkperf and cmName in (args) var labelSelector string if cliCtx.NArg() == 0 { - labelSelector = fmt.Sprintf("app=%s", appLebel) + labelSelector = fmt.Sprintf("app=%s", appLabel) } else { args := cliCtx.Args() namesStr := strings.Join(args, ",") - labelSelector = fmt.Sprintf("app=%s, cmName in (%s)", appLebel, namesStr) + labelSelector = fmt.Sprintf("app=%s, cmName in (%s)", appLabel, namesStr) } cmMap := make(map[string][]int) err = listConfigmapsByName(clientset, labelSelector, namespace, cmMap) @@ -229,24 +227,6 @@ func checkConfigmapParams(size int, groupSize int, total int) error { return nil } -var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") - -func randString(n int) (string, error) { - if n <= 0 { - return "", fmt.Errorf("length must be positive") - } - - b := make([]rune, n) - for i := range b { - random, err := rand.Int(rand.Reader, big.NewInt(int64(len(letterRunes)))) - if err != nil { - return "", fmt.Errorf("error generating random number: %w", err) - } - b[i] = letterRunes[int(random.Int64())] - } - return string(b), nil -} - func createConfigmaps(clientset *kubernetes.Clientset, namespace string, cmName string, size int, groupSize int, total int) error { // Generate configmaps in parallel with fixed group size // and random data @@ -257,22 +237,22 @@ func createConfigmaps(clientset *kubernetes.Clientset, namespace string, cmName g.Go(func() error { cli := clientset.CoreV1().ConfigMaps(namespace) - name := fmt.Sprintf("%s-cm-%s-%d", appLebel, cmName, j) + name := fmt.Sprintf("%s-cm-%s-%d", appLabel, cmName, j) cm := &corev1.ConfigMap{} cm.Name = name // Set the labels for the configmap to easily identify in delete or list commands cm.Labels = map[string]string{ "ownerID": strconv.Itoa(ownerID), - "app": appLebel, + "app": appLabel, "cmName": cmName, } - data, err := randString(size) + randData, err := data.RandString(size) if err != nil { return fmt.Errorf("failed to generate random string for configmap %s: %v", name, err) } cm.Data = map[string]string{ - "data": data, + "data": randData, } _, err = cli.Create(context.TODO(), cm, metav1.CreateOptions{}) diff --git a/contrib/cmd/runkperf/commands/data/events/event.go b/contrib/cmd/runkperf/commands/data/events/event.go index 0d955c8..9881fb5 100644 --- a/contrib/cmd/runkperf/commands/data/events/event.go +++ b/contrib/cmd/runkperf/commands/data/events/event.go @@ -5,9 +5,7 @@ package events import ( "context" - "crypto/rand" "fmt" - "math/big" "os" "strings" "text/tabwriter" @@ -245,24 +243,6 @@ type eventSetInfo struct { total int } -var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") - -func randString(n int) (string, error) { - if n <= 0 { - return "", fmt.Errorf("length must be positive") - } - - b := make([]rune, n) - for i := range b { - random, err := rand.Int(rand.Reader, big.NewInt(int64(len(letterRunes)))) - if err != nil { - return "", fmt.Errorf("error generating random number: %w", err) - } - b[i] = letterRunes[int(random.Int64())] - } - return string(b), nil -} - func createEvents(clientset *kubernetes.Clientset, namespace, evName string, total, groupSize int, reason, eventType string, messageSize int, involvedObjectKind, involvedObjectName string) error { now := time.Now() @@ -273,7 +253,7 @@ func createEvents(clientset *kubernetes.Clientset, namespace, evName string, tot g.Go(func() error { name := fmt.Sprintf("%s-ev-%s-%d", appLabel, evName, idx) - message, err := randString(messageSize) + message, err := data.RandString(messageSize) if err != nil { return fmt.Errorf("failed to generate random message for event %s: %v", name, err) } diff --git a/contrib/cmd/runkperf/commands/data/secrets/secret.go b/contrib/cmd/runkperf/commands/data/secrets/secret.go index 8c9b4bf..65da362 100644 --- a/contrib/cmd/runkperf/commands/data/secrets/secret.go +++ b/contrib/cmd/runkperf/commands/data/secrets/secret.go @@ -5,9 +5,7 @@ package secrets import ( "context" - "crypto/rand" "fmt" - "math/big" "os" "strconv" "strings" @@ -222,24 +220,6 @@ func checkSecretParams(size int, groupSize int, total int) error { return nil } -var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") - -func randBytes(n int) ([]byte, error) { - if n <= 0 { - return nil, fmt.Errorf("length must be positive") - } - - b := make([]byte, n) - for i := range b { - random, err := rand.Int(rand.Reader, big.NewInt(int64(len(letterRunes)))) - if err != nil { - return nil, fmt.Errorf("error generating random number: %w", err) - } - b[i] = byte(letterRunes[int(random.Int64())]) - } - return b, nil -} - func createSecrets(clientset *kubernetes.Clientset, namespace string, secName string, size int, groupSize int, total int) error { for i := 0; i < total; i += groupSize { ownerID := i @@ -249,7 +229,7 @@ func createSecrets(clientset *kubernetes.Clientset, namespace string, secName st g.Go(func() error { name := fmt.Sprintf("%s-sec-%s-%d", appLabel, secName, idx) - data, err := randBytes(size) + randData, err := data.RandBytes(size) if err != nil { return fmt.Errorf("failed to generate random data for secret %s: %v", name, err) } @@ -265,7 +245,7 @@ func createSecrets(clientset *kubernetes.Clientset, namespace string, secName st }, Type: corev1.SecretTypeOpaque, Data: map[string][]byte{ - "data": data, + "data": randData, }, } From 834a06b34d7068e07c79ff687c53cb7642df94e2 Mon Sep 17 00:00:00 2001 From: Liyu Ma Date: Tue, 17 Mar 2026 18:23:32 +1000 Subject: [PATCH 05/12] Apply rate limiter for deletion --- contrib/cmd/runkperf/commands/data/client.go | 11 +- .../runkperf/commands/data/events/event.go | 152 ++++++++++------ .../runkperf/commands/data/secrets/secret.go | 164 +++++++++++------- 3 files changed, 203 insertions(+), 124 deletions(-) diff --git a/contrib/cmd/runkperf/commands/data/client.go b/contrib/cmd/runkperf/commands/data/client.go index 6b2ddcf..56bb846 100644 --- a/contrib/cmd/runkperf/commands/data/client.go +++ b/contrib/cmd/runkperf/commands/data/client.go @@ -5,9 +5,8 @@ package data import ( "context" - "crypto/rand" "fmt" - "math/big" + "math/rand" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -58,7 +57,7 @@ func PrepareNamespace(clientset *kubernetes.Clientset, namespace string) error { if errors.IsAlreadyExists(err) { return nil } - return fmt.Errorf("failed to create namespace %s: %v", namespace, err) + return fmt.Errorf("failed to create namespace %s: %w", namespace, err) } return nil } @@ -73,11 +72,7 @@ func RandBytes(n int) ([]byte, error) { b := make([]byte, n) for i := range b { - random, err := rand.Int(rand.Reader, big.NewInt(int64(len(letterRunes)))) - if err != nil { - return nil, fmt.Errorf("error generating random number: %w", err) - } - b[i] = byte(letterRunes[int(random.Int64())]) + b[i] = byte(letterRunes[rand.Intn(len(letterRunes))]) } return b, nil } diff --git a/contrib/cmd/runkperf/commands/data/events/event.go b/contrib/cmd/runkperf/commands/data/events/event.go index 9881fb5..f307aec 100644 --- a/contrib/cmd/runkperf/commands/data/events/event.go +++ b/contrib/cmd/runkperf/commands/data/events/event.go @@ -26,6 +26,10 @@ import ( var appLabel = "runkperf" +// defaultBatchSize is the number of events to list or delete per batch. +// It is used as the page size for paginated API calls and the concurrency limit for parallel deletions. +const defaultBatchSize int64 = 300 + var Command = cli.Command{ Name: "event", ShortName: "ev", @@ -104,8 +108,8 @@ var eventAddCommand = cli.Command{ if cliCtx.NArg() != 1 { return fmt.Errorf("required only one argument as events set name: %v", cliCtx.Args()) } - evName := strings.TrimSpace(cliCtx.Args().Get(0)) - if len(evName) == 0 { + eventSetName := strings.TrimSpace(cliCtx.Args().Get(0)) + if len(eventSetName) == 0 { return fmt.Errorf("required non-empty event set name") } @@ -147,12 +151,12 @@ var eventAddCommand = cli.Command{ return err } - err = createEvents(clientset, namespace, evName, total, groupSize, reason, eventType, messageSize, involvedObjectKind, involvedObjectName) + err = createEvents(clientset, namespace, eventSetName, total, groupSize, reason, eventType, messageSize, involvedObjectKind, involvedObjectName) if err != nil { return err } fmt.Printf("Created %d events (set=%s, reason=%s, type=%s) in namespace %s\n", - total, evName, reason, eventType, namespace) + total, eventSetName, reason, eventType, namespace) return nil }, } @@ -162,37 +166,58 @@ var eventDelCommand = cli.Command{ ShortName: "del", ArgsUsage: "NAME", Usage: "Delete an events set", + Flags: []cli.Flag{ + cli.Float64Flag{ + Name: "qps", + Usage: "QPS for the Kubernetes client rate limiter to control event deletion", + Value: 100, + }, + cli.IntFlag{ + Name: "burst", + Usage: "Burst for the Kubernetes client rate limiter to control event deletion", + Value: 200, + }, + cli.IntFlag{ + Name: "group-size", + Usage: "Number of events to delete in parallel per batch", + Value: 30, + }, + }, Action: func(cliCtx *cli.Context) error { if cliCtx.NArg() != 1 { return fmt.Errorf("required only one events set name") } - evName := strings.TrimSpace(cliCtx.Args().Get(0)) - if len(evName) == 0 { + eventSetName := strings.TrimSpace(cliCtx.Args().Get(0)) + if len(eventSetName) == 0 { return fmt.Errorf("required non-empty events set name") } namespace := cliCtx.GlobalString("namespace") kubeCfgPath := cliCtx.GlobalString("kubeconfig") - labelSelector := fmt.Sprintf("app=%s,evName=%s", appLabel, evName) + qps := float32(cliCtx.Float64("qps")) + burst := cliCtx.Int("burst") + groupSize := cliCtx.Int("group-size") + labelSelector := fmt.Sprintf("app=%s,eventSetName=%s", appLabel, eventSetName) - clientset, err := data.NewClientset(kubeCfgPath) + clientset, err := data.NewClientsetWithRateLimiter(kubeCfgPath, qps, burst) if err != nil { return err } - err = deleteEvents(clientset, labelSelector, namespace) + err = deleteEvents(clientset, labelSelector, namespace, groupSize) if err != nil { return err } - fmt.Printf("Deleted events set %s in %s namespace\n", evName, namespace) + fmt.Printf("Deleted events set %s in %s namespace\n", eventSetName, namespace) return nil }, } var eventListCommand = cli.Command{ - Name: "list", - Usage: "List generated event sets. Lists all if no arguments are given; otherwise, provide event set names separated by spaces.", + Name: "list", + Usage: "List generated event sets. Lists all if no arguments are given; otherwise, provide event set names separated by spaces.", + ArgsUsage: "[NAME ...]", Action: func(cliCtx *cli.Context) error { namespace := cliCtx.GlobalString("namespace") kubeCfgPath := cliCtx.GlobalString("kubeconfig") @@ -213,14 +238,31 @@ var eventListCommand = cli.Command{ var labelSelector string if cliCtx.NArg() == 0 { - labelSelector = fmt.Sprintf("app=%s,evName", appLabel) + labelSelector = fmt.Sprintf("app=%s", appLabel) } else { args := cliCtx.Args() namesStr := strings.Join(args, ",") - labelSelector = fmt.Sprintf("app=%s, evName in (%s)", appLabel, namesStr) + labelSelector = fmt.Sprintf("app=%s, eventSetName in (%s)", appLabel, namesStr) } - evMap, err := listEventsByName(clientset, labelSelector, namespace) + evMap := make(map[string]*eventSetInfo) + err = listEvents(clientset, labelSelector, namespace, defaultBatchSize, func(ev corev1.Event) error { + name, ok := ev.Labels["eventSetName"] + if !ok { + return nil + } + + info, ok := evMap[name] + if !ok { + info = &eventSetInfo{ + eventType: ev.Type, + reason: ev.Reason, + } + evMap[name] = info + } + info.total++ + return nil + }) if err != nil { return err } @@ -243,7 +285,7 @@ type eventSetInfo struct { total int } -func createEvents(clientset *kubernetes.Clientset, namespace, evName string, total, groupSize int, reason, eventType string, messageSize int, involvedObjectKind, involvedObjectName string) error { +func createEvents(clientset *kubernetes.Clientset, namespace, eventSetName string, total, groupSize int, reason, eventType string, messageSize int, involvedObjectKind, involvedObjectName string) error { now := time.Now() for i := 0; i < total; i += groupSize { @@ -251,7 +293,7 @@ func createEvents(clientset *kubernetes.Clientset, namespace, evName string, tot for j := i; j < i+groupSize && j < total; j++ { idx := j g.Go(func() error { - name := fmt.Sprintf("%s-ev-%s-%d", appLabel, evName, idx) + name := fmt.Sprintf("%s-ev-%s-%d", appLabel, eventSetName, idx) message, err := data.RandString(messageSize) if err != nil { @@ -260,7 +302,7 @@ func createEvents(clientset *kubernetes.Clientset, namespace, evName string, tot objName := involvedObjectName if objName == "" { - objName = fmt.Sprintf("%s-obj-%d", evName, idx) + objName = fmt.Sprintf("%s-obj-%d", eventSetName, idx) } event := &corev1.Event{ @@ -268,8 +310,8 @@ func createEvents(clientset *kubernetes.Clientset, namespace, evName string, tot Name: name, Namespace: namespace, Labels: map[string]string{ - "app": appLabel, - "evName": evName, + "app": appLabel, + "eventSetName": eventSetName, }, }, InvolvedObject: corev1.ObjectReference{ @@ -302,25 +344,30 @@ func createEvents(clientset *kubernetes.Clientset, namespace, evName string, tot return nil } -func deleteEvents(clientset *kubernetes.Clientset, labelSelector, namespace string) error { - eventList, err := listEvents(clientset, labelSelector, namespace) +func deleteEvents(clientset *kubernetes.Clientset, labelSelector, namespace string, groupSize int) error { + var names []string + err := listEvents(clientset, labelSelector, namespace, defaultBatchSize, func(ev corev1.Event) error { + names = append(names, ev.Name) + return nil + }) if err != nil { return err } - if len(eventList.Items) == 0 { - return fmt.Errorf("no events set found in namespace: %s", namespace) + if len(names) == 0 { + fmt.Printf("No events found in namespace %s, nothing to delete\n", namespace) + return nil } - n, batch := len(eventList.Items), 300 - for i := 0; i < n; i += batch { + n := len(names) + for i := 0; i < n; i += groupSize { g := new(errgroup.Group) - for j := i; j < i+batch && j < n; j++ { + for j := i; j < i+groupSize && j < n; j++ { idx := j g.Go(func() error { - err := clientset.CoreV1().Events(namespace).Delete(context.TODO(), eventList.Items[idx].Name, metav1.DeleteOptions{}) + err := clientset.CoreV1().Events(namespace).Delete(context.TODO(), names[idx], metav1.DeleteOptions{}) if err != nil && !errors.IsNotFound(err) { - return fmt.Errorf("failed to delete event %s: %v", eventList.Items[idx].Name, err) + return fmt.Errorf("failed to delete event %s: %v", names[idx], err) } return nil }) @@ -332,36 +379,31 @@ func deleteEvents(clientset *kubernetes.Clientset, labelSelector, namespace stri return nil } -func listEvents(clientset *kubernetes.Clientset, labelSelector, namespace string) (*corev1.EventList, error) { - eventList, err := clientset.CoreV1().Events(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector}) - if err != nil { - return nil, fmt.Errorf("failed to list events: %v", err) - } - return eventList, nil -} - -func listEventsByName(clientset *kubernetes.Clientset, labelSelector, namespace string) (map[string]*eventSetInfo, error) { - eventList, err := listEvents(clientset, labelSelector, namespace) - if err != nil { - return nil, err +func listEvents(clientset *kubernetes.Clientset, labelSelector, namespace string, limit int64, fn func(corev1.Event) error) error { + if limit <= 0 { + limit = defaultBatchSize } - - evMap := make(map[string]*eventSetInfo) - for _, ev := range eventList.Items { - name, ok := ev.Labels["evName"] - if !ok { - continue + var continueToken string + for { + eventList, err := clientset.CoreV1().Events(namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: labelSelector, + Limit: limit, + Continue: continueToken, + }) + if err != nil { + return fmt.Errorf("failed to list events: %v", err) } - info, ok := evMap[name] - if !ok { - info = &eventSetInfo{ - eventType: ev.Type, - reason: ev.Reason, + for _, ev := range eventList.Items { + if err := fn(ev); err != nil { + return err } - evMap[name] = info } - info.total++ + + continueToken = eventList.Continue + if continueToken == "" { + break + } } - return evMap, nil + return nil } diff --git a/contrib/cmd/runkperf/commands/data/secrets/secret.go b/contrib/cmd/runkperf/commands/data/secrets/secret.go index 65da362..08eedc1 100644 --- a/contrib/cmd/runkperf/commands/data/secrets/secret.go +++ b/contrib/cmd/runkperf/commands/data/secrets/secret.go @@ -26,6 +26,10 @@ import ( var appLabel = "runkperf" +// defaultBatchSize is the number of secrets to list or delete per batch. +// It is used as the page size for paginated API calls and the concurrency limit for parallel deletions. +const defaultBatchSize int64 = 300 + var Command = cli.Command{ Name: "secret", ShortName: "sec", @@ -127,6 +131,23 @@ var secretDelCommand = cli.Command{ ShortName: "del", ArgsUsage: "NAME", Usage: "Delete a secrets set", + Flags: []cli.Flag{ + cli.Float64Flag{ + Name: "qps", + Usage: "QPS for the Kubernetes client rate limiter to control secret deletion", + Value: 100, + }, + cli.IntFlag{ + Name: "burst", + Usage: "Burst for the Kubernetes client rate limiter to control secret deletion", + Value: 200, + }, + cli.IntFlag{ + Name: "group-size", + Usage: "Number of secrets to delete in parallel per batch", + Value: 30, + }, + }, Action: func(cliCtx *cli.Context) error { if cliCtx.NArg() != 1 { return fmt.Errorf("required only one secrets set name") @@ -138,14 +159,17 @@ var secretDelCommand = cli.Command{ namespace := cliCtx.GlobalString("namespace") kubeCfgPath := cliCtx.GlobalString("kubeconfig") + qps := float32(cliCtx.Float64("qps")) + burst := cliCtx.Int("burst") + groupSize := cliCtx.Int("group-size") labelSelector := fmt.Sprintf("app=%s,secName=%s", appLabel, secName) - clientset, err := data.NewClientset(kubeCfgPath) + clientset, err := data.NewClientsetWithRateLimiter(kubeCfgPath, qps, burst) if err != nil { return err } - err = deleteSecrets(clientset, labelSelector, namespace) + err = deleteSecrets(clientset, labelSelector, namespace, groupSize) if err != nil { return err } @@ -158,7 +182,7 @@ var secretDelCommand = cli.Command{ var secretListCommand = cli.Command{ Name: "list", Usage: "List generated secrets. Lists all if no arguments are given; otherwise, provide secret set names separated by spaces.", - ArgsUsage: "NAME", + ArgsUsage: "[NAME ...]", Action: func(cliCtx *cli.Context) error { namespace := cliCtx.GlobalString("namespace") kubeCfgPath := cliCtx.GlobalString("kubeconfig") @@ -179,25 +203,61 @@ var secretListCommand = cli.Command{ var labelSelector string if cliCtx.NArg() == 0 { - labelSelector = fmt.Sprintf("app=%s,secName", appLabel) + labelSelector = fmt.Sprintf("app=%s", appLabel) } else { args := cliCtx.Args() namesStr := strings.Join(args, ",") labelSelector = fmt.Sprintf("app=%s, secName in (%s)", appLabel, namesStr) } - secMap := make(map[string][]int) - err = listSecretsByName(clientset, labelSelector, namespace, secMap) + type secretSetInfo struct { + size int + total int + ownerID map[string]int // ownerID -> count of secrets in that group + } + secMap := make(map[string]*secretSetInfo) + err = listSecrets(clientset, labelSelector, namespace, defaultBatchSize, func(sec corev1.Secret) error { + name, ok := sec.Labels["secName"] + if !ok { + return fmt.Errorf("failed to find the secName of secret %s", sec.Name) + } + + info, ok := secMap[name] + if !ok { + info = &secretSetInfo{ + ownerID: make(map[string]int), + } + if d, exists := sec.Data["data"]; exists { + info.size = len(d) + } + secMap[name] = info + } + + info.total++ + + ownerID, ok := sec.Labels["ownerID"] + if !ok { + return fmt.Errorf("failed to find the ownerID of secret %s", name) + } + info.ownerID[ownerID]++ + return nil + }) if err != nil { return err } - for key, value := range secMap { + for name, info := range secMap { + // Determine group size from the first ownerID group count + groupSize := 0 + for _, count := range info.ownerID { + groupSize = count + break + } fmt.Fprintf(tw, "%s\t%d\t%d\t%d\n", - key, - value[0], - value[1], - value[2], + name, + info.size, + groupSize, + info.total, ) } return tw.Flush() @@ -263,25 +323,30 @@ func createSecrets(clientset *kubernetes.Clientset, namespace string, secName st return nil } -func deleteSecrets(clientset *kubernetes.Clientset, labelSelector string, namespace string) error { - secrets, err := listSecrets(clientset, labelSelector, namespace) +func deleteSecrets(clientset *kubernetes.Clientset, labelSelector string, namespace string, groupSize int) error { + var names []string + err := listSecrets(clientset, labelSelector, namespace, defaultBatchSize, func(sec corev1.Secret) error { + names = append(names, sec.Name) + return nil + }) if err != nil { return err } - if len(secrets.Items) == 0 { - return fmt.Errorf("no secrets set found in namespace: %s", namespace) + if len(names) == 0 { + fmt.Printf("No secrets found in namespace %s, nothing to delete\n", namespace) + return nil } - n, batch := len(secrets.Items), 300 - for i := 0; i < n; i += batch { + n := len(names) + for i := 0; i < n; i += groupSize { g := new(errgroup.Group) - for j := i; j < i+batch && j < n; j++ { + for j := i; j < i+groupSize && j < n; j++ { idx := j g.Go(func() error { - err := clientset.CoreV1().Secrets(namespace).Delete(context.TODO(), secrets.Items[idx].Name, metav1.DeleteOptions{}) + err := clientset.CoreV1().Secrets(namespace).Delete(context.TODO(), names[idx], metav1.DeleteOptions{}) if err != nil && !errors.IsNotFound(err) { - return fmt.Errorf("failed to delete secret %s: %v", secrets.Items[idx].Name, err) + return fmt.Errorf("failed to delete secret %s: %v", names[idx], err) } return nil }) @@ -293,53 +358,30 @@ func deleteSecrets(clientset *kubernetes.Clientset, labelSelector string, namesp return nil } -func listSecrets(clientset *kubernetes.Clientset, labelSelector string, namespace string) (*corev1.SecretList, error) { - secrets, err := clientset.CoreV1().Secrets(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector}) - if err != nil { - return nil, fmt.Errorf("failed to list secrets: %v", err) - } - return secrets, nil -} - -func listSecretsByName(clientset *kubernetes.Clientset, labelSelector string, namespace string, secMap map[string][]int) error { - secrets, err := listSecrets(clientset, labelSelector, namespace) - if err != nil { - return err +func listSecrets(clientset *kubernetes.Clientset, labelSelector string, namespace string, limit int64, fn func(corev1.Secret) error) error { + if limit <= 0 { + limit = defaultBatchSize } - - for _, sec := range secrets.Items { - name, ok := sec.Labels["secName"] - if !ok { - return fmt.Errorf("failed to find the secName of secret %s", sec.Name) + var continueToken string + for { + secrets, err := clientset.CoreV1().Secrets(namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: labelSelector, + Limit: limit, + Continue: continueToken, + }) + if err != nil { + return fmt.Errorf("failed to list secrets: %v", err) } - _, ok = secMap[name] - if !ok { - // Initialize: size, group-size, total - secMap[name] = []int{0, 0, 0} - - if data, exists := sec.Data["data"]; exists { - secMap[name][0] = len(data) + for _, sec := range secrets.Items { + if err := fn(sec); err != nil { + return err } } - secMap[name][2]++ - - if secMap[name][1] != 0 { - continue - } - - ownerID, ok := sec.Labels["ownerID"] - if !ok { - return fmt.Errorf("failed to find the ownerID of secret %s", name) - } - - if ownerIDInt, err := strconv.Atoi(ownerID); err == nil { - if ownerIDInt > secMap[name][1] { - secMap[name][1] = ownerIDInt - } - } else { - return fmt.Errorf("failed to convert ownerID %s to int: %v", ownerID, err) + continueToken = secrets.Continue + if continueToken == "" { + break } } return nil From 3d7c0e29aad39eac2d61f77f8c3db7f985b50c54 Mon Sep 17 00:00:00 2001 From: Liyu Ma Date: Tue, 17 Mar 2026 23:43:37 +1000 Subject: [PATCH 06/12] fix linting --- contrib/cmd/runkperf/commands/data/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/cmd/runkperf/commands/data/client.go b/contrib/cmd/runkperf/commands/data/client.go index 56bb846..eefa824 100644 --- a/contrib/cmd/runkperf/commands/data/client.go +++ b/contrib/cmd/runkperf/commands/data/client.go @@ -72,7 +72,7 @@ func RandBytes(n int) ([]byte, error) { b := make([]byte, n) for i := range b { - b[i] = byte(letterRunes[rand.Intn(len(letterRunes))]) + b[i] = byte(letterRunes[rand.Intn(len(letterRunes))]) //nolint:gosec // G404: intentionally using math/rand for benchmark data, cryptographic randomness not needed } return b, nil } From f071ca9eace2db99c93867334a149ab86bca9b2e Mon Sep 17 00:00:00 2001 From: Liyu Ma Date: Wed, 18 Mar 2026 11:45:03 +1000 Subject: [PATCH 07/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- contrib/cmd/runkperf/commands/data/events/event.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/cmd/runkperf/commands/data/events/event.go b/contrib/cmd/runkperf/commands/data/events/event.go index f307aec..40b859e 100644 --- a/contrib/cmd/runkperf/commands/data/events/event.go +++ b/contrib/cmd/runkperf/commands/data/events/event.go @@ -27,7 +27,7 @@ import ( var appLabel = "runkperf" // defaultBatchSize is the number of events to list or delete per batch. -// It is used as the page size for paginated API calls and the concurrency limit for parallel deletions. +// It is used as the page size for paginated API calls. const defaultBatchSize int64 = 300 var Command = cli.Command{ From effce6860921e4620a42f7f5b0fe68037e814d5e Mon Sep 17 00:00:00 2001 From: Liyu Ma Date: Wed, 18 Mar 2026 11:45:13 +1000 Subject: [PATCH 08/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- contrib/cmd/runkperf/commands/data/secrets/secret.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/cmd/runkperf/commands/data/secrets/secret.go b/contrib/cmd/runkperf/commands/data/secrets/secret.go index 08eedc1..85cf291 100644 --- a/contrib/cmd/runkperf/commands/data/secrets/secret.go +++ b/contrib/cmd/runkperf/commands/data/secrets/secret.go @@ -27,7 +27,7 @@ import ( var appLabel = "runkperf" // defaultBatchSize is the number of secrets to list or delete per batch. -// It is used as the page size for paginated API calls and the concurrency limit for parallel deletions. +// It is used as the page size for paginated API calls. const defaultBatchSize int64 = 300 var Command = cli.Command{ From 3bd547bc2f36a209b36dd326554c2036ca2c9e57 Mon Sep 17 00:00:00 2001 From: Liyu Ma Date: Wed, 18 Mar 2026 12:25:48 +1000 Subject: [PATCH 09/12] Address copilot comments --- contrib/cmd/runkperf/commands/data/events/event.go | 3 +++ contrib/cmd/runkperf/commands/data/secrets/secret.go | 12 ++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/contrib/cmd/runkperf/commands/data/events/event.go b/contrib/cmd/runkperf/commands/data/events/event.go index 40b859e..e1a4b8c 100644 --- a/contrib/cmd/runkperf/commands/data/events/event.go +++ b/contrib/cmd/runkperf/commands/data/events/event.go @@ -197,6 +197,9 @@ var eventDelCommand = cli.Command{ qps := float32(cliCtx.Float64("qps")) burst := cliCtx.Int("burst") groupSize := cliCtx.Int("group-size") + if groupSize <= 0 { + return fmt.Errorf("group-size must be greater than 0") + } labelSelector := fmt.Sprintf("app=%s,eventSetName=%s", appLabel, eventSetName) clientset, err := data.NewClientsetWithRateLimiter(kubeCfgPath, qps, burst) diff --git a/contrib/cmd/runkperf/commands/data/secrets/secret.go b/contrib/cmd/runkperf/commands/data/secrets/secret.go index 85cf291..bc2641c 100644 --- a/contrib/cmd/runkperf/commands/data/secrets/secret.go +++ b/contrib/cmd/runkperf/commands/data/secrets/secret.go @@ -121,7 +121,7 @@ var secretAddCommand = cli.Command{ if err != nil { return err } - fmt.Printf("Created secret %s with size %d B, group-size %d, total %d\n", secName, size, groupSize, total) + fmt.Printf("Created secret set %s with size %d B, group-size %d, total %d\n", secName, size, groupSize, total) return nil }, } @@ -162,6 +162,9 @@ var secretDelCommand = cli.Command{ qps := float32(cliCtx.Float64("qps")) burst := cliCtx.Int("burst") groupSize := cliCtx.Int("group-size") + if groupSize <= 0 { + return fmt.Errorf("group-size must be greater than 0") + } labelSelector := fmt.Sprintf("app=%s,secName=%s", appLabel, secName) clientset, err := data.NewClientsetWithRateLimiter(kubeCfgPath, qps, burst) @@ -247,11 +250,12 @@ var secretListCommand = cli.Command{ } for name, info := range secMap { - // Determine group size from the first ownerID group count + // Group size should be the max count across all ownerID groups groupSize := 0 for _, count := range info.ownerID { - groupSize = count - break + if count > groupSize { + groupSize = count + } } fmt.Fprintf(tw, "%s\t%d\t%d\t%d\n", name, From 62fdb8d77f94bdb30450d4ffe1ac725961252d8c Mon Sep 17 00:00:00 2001 From: Liyu Ma Date: Fri, 20 Mar 2026 15:05:06 +1000 Subject: [PATCH 10/12] Consolidate AppLabel --- contrib/cmd/runkperf/commands/data/client.go | 3 +++ .../runkperf/commands/data/configmaps/configmap.go | 12 +++++------- contrib/cmd/runkperf/commands/data/events/event.go | 12 +++++------- contrib/cmd/runkperf/commands/data/secrets/secret.go | 12 +++++------- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/contrib/cmd/runkperf/commands/data/client.go b/contrib/cmd/runkperf/commands/data/client.go index eefa824..3c894fe 100644 --- a/contrib/cmd/runkperf/commands/data/client.go +++ b/contrib/cmd/runkperf/commands/data/client.go @@ -16,6 +16,9 @@ import ( "k8s.io/client-go/util/flowcontrol" ) +// default label value used to identify resources created by runkperf. +const AppLabel = "runkperf" + // NewClientset creates a Kubernetes clientset with default rate limiting. func NewClientset(kubeCfgPath string) (*kubernetes.Clientset, error) { config, err := clientcmd.BuildConfigFromFlags("", kubeCfgPath) diff --git a/contrib/cmd/runkperf/commands/data/configmaps/configmap.go b/contrib/cmd/runkperf/commands/data/configmaps/configmap.go index 9675085..1c0c504 100644 --- a/contrib/cmd/runkperf/commands/data/configmaps/configmap.go +++ b/contrib/cmd/runkperf/commands/data/configmaps/configmap.go @@ -24,8 +24,6 @@ import ( "k8s.io/client-go/kubernetes" ) -var appLabel = "runkperf" - var Command = cli.Command{ Name: "configmap", ShortName: "cm", @@ -139,7 +137,7 @@ var configmapDelCommand = cli.Command{ namespace := cliCtx.GlobalString("namespace") kubeCfgPath := cliCtx.GlobalString("kubeconfig") - labelSelector := fmt.Sprintf("app=%s,cmName=%s", appLabel, cmName) + labelSelector := fmt.Sprintf("app=%s,cmName=%s", data.AppLabel, cmName) clientset, err := data.NewClientset(kubeCfgPath) if err != nil { @@ -185,12 +183,12 @@ var configmapListCommand = cli.Command{ // If args are provided, list all configmaps with the label app=runkperf and cmName in (args) var labelSelector string if cliCtx.NArg() == 0 { - labelSelector = fmt.Sprintf("app=%s", appLabel) + labelSelector = fmt.Sprintf("app=%s", data.AppLabel) } else { args := cliCtx.Args() namesStr := strings.Join(args, ",") - labelSelector = fmt.Sprintf("app=%s, cmName in (%s)", appLabel, namesStr) + labelSelector = fmt.Sprintf("app=%s, cmName in (%s)", data.AppLabel, namesStr) } cmMap := make(map[string][]int) err = listConfigmapsByName(clientset, labelSelector, namespace, cmMap) @@ -237,14 +235,14 @@ func createConfigmaps(clientset *kubernetes.Clientset, namespace string, cmName g.Go(func() error { cli := clientset.CoreV1().ConfigMaps(namespace) - name := fmt.Sprintf("%s-cm-%s-%d", appLabel, cmName, j) + name := fmt.Sprintf("%s-cm-%s-%d", data.AppLabel, cmName, j) cm := &corev1.ConfigMap{} cm.Name = name // Set the labels for the configmap to easily identify in delete or list commands cm.Labels = map[string]string{ "ownerID": strconv.Itoa(ownerID), - "app": appLabel, + "app": data.AppLabel, "cmName": cmName, } randData, err := data.RandString(size) diff --git a/contrib/cmd/runkperf/commands/data/events/event.go b/contrib/cmd/runkperf/commands/data/events/event.go index e1a4b8c..379a1aa 100644 --- a/contrib/cmd/runkperf/commands/data/events/event.go +++ b/contrib/cmd/runkperf/commands/data/events/event.go @@ -24,8 +24,6 @@ import ( "k8s.io/client-go/kubernetes" ) -var appLabel = "runkperf" - // defaultBatchSize is the number of events to list or delete per batch. // It is used as the page size for paginated API calls. const defaultBatchSize int64 = 300 @@ -200,7 +198,7 @@ var eventDelCommand = cli.Command{ if groupSize <= 0 { return fmt.Errorf("group-size must be greater than 0") } - labelSelector := fmt.Sprintf("app=%s,eventSetName=%s", appLabel, eventSetName) + labelSelector := fmt.Sprintf("app=%s,eventSetName=%s", data.AppLabel, eventSetName) clientset, err := data.NewClientsetWithRateLimiter(kubeCfgPath, qps, burst) if err != nil { @@ -241,11 +239,11 @@ var eventListCommand = cli.Command{ var labelSelector string if cliCtx.NArg() == 0 { - labelSelector = fmt.Sprintf("app=%s", appLabel) + labelSelector = fmt.Sprintf("app=%s", data.AppLabel) } else { args := cliCtx.Args() namesStr := strings.Join(args, ",") - labelSelector = fmt.Sprintf("app=%s, eventSetName in (%s)", appLabel, namesStr) + labelSelector = fmt.Sprintf("app=%s, eventSetName in (%s)", data.AppLabel, namesStr) } evMap := make(map[string]*eventSetInfo) @@ -296,7 +294,7 @@ func createEvents(clientset *kubernetes.Clientset, namespace, eventSetName strin for j := i; j < i+groupSize && j < total; j++ { idx := j g.Go(func() error { - name := fmt.Sprintf("%s-ev-%s-%d", appLabel, eventSetName, idx) + name := fmt.Sprintf("%s-ev-%s-%d", data.AppLabel, eventSetName, idx) message, err := data.RandString(messageSize) if err != nil { @@ -313,7 +311,7 @@ func createEvents(clientset *kubernetes.Clientset, namespace, eventSetName strin Name: name, Namespace: namespace, Labels: map[string]string{ - "app": appLabel, + "app": data.AppLabel, "eventSetName": eventSetName, }, }, diff --git a/contrib/cmd/runkperf/commands/data/secrets/secret.go b/contrib/cmd/runkperf/commands/data/secrets/secret.go index bc2641c..bb95bab 100644 --- a/contrib/cmd/runkperf/commands/data/secrets/secret.go +++ b/contrib/cmd/runkperf/commands/data/secrets/secret.go @@ -24,8 +24,6 @@ import ( "k8s.io/client-go/kubernetes" ) -var appLabel = "runkperf" - // defaultBatchSize is the number of secrets to list or delete per batch. // It is used as the page size for paginated API calls. const defaultBatchSize int64 = 300 @@ -165,7 +163,7 @@ var secretDelCommand = cli.Command{ if groupSize <= 0 { return fmt.Errorf("group-size must be greater than 0") } - labelSelector := fmt.Sprintf("app=%s,secName=%s", appLabel, secName) + labelSelector := fmt.Sprintf("app=%s,secName=%s", data.AppLabel, secName) clientset, err := data.NewClientsetWithRateLimiter(kubeCfgPath, qps, burst) if err != nil { @@ -206,11 +204,11 @@ var secretListCommand = cli.Command{ var labelSelector string if cliCtx.NArg() == 0 { - labelSelector = fmt.Sprintf("app=%s", appLabel) + labelSelector = fmt.Sprintf("app=%s", data.AppLabel) } else { args := cliCtx.Args() namesStr := strings.Join(args, ",") - labelSelector = fmt.Sprintf("app=%s, secName in (%s)", appLabel, namesStr) + labelSelector = fmt.Sprintf("app=%s, secName in (%s)", data.AppLabel, namesStr) } type secretSetInfo struct { @@ -291,7 +289,7 @@ func createSecrets(clientset *kubernetes.Clientset, namespace string, secName st for j := i; j < i+groupSize && j < total; j++ { idx := j g.Go(func() error { - name := fmt.Sprintf("%s-sec-%s-%d", appLabel, secName, idx) + name := fmt.Sprintf("%s-sec-%s-%d", data.AppLabel, secName, idx) randData, err := data.RandBytes(size) if err != nil { @@ -303,7 +301,7 @@ func createSecrets(clientset *kubernetes.Clientset, namespace string, secName st Name: name, Labels: map[string]string{ "ownerID": strconv.Itoa(ownerID), - "app": appLabel, + "app": data.AppLabel, "secName": secName, }, }, From 28bb01de307e1645a8591c9796c1bea5e05a5bc4 Mon Sep 17 00:00:00 2001 From: Liyu Ma Date: Fri, 20 Mar 2026 15:12:00 +1000 Subject: [PATCH 11/12] update comments --- contrib/cmd/runkperf/commands/data/configmaps/configmap.go | 2 +- contrib/cmd/runkperf/commands/data/events/event.go | 2 +- contrib/cmd/runkperf/commands/data/secrets/secret.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/cmd/runkperf/commands/data/configmaps/configmap.go b/contrib/cmd/runkperf/commands/data/configmaps/configmap.go index 1c0c504..4f18fdb 100644 --- a/contrib/cmd/runkperf/commands/data/configmaps/configmap.go +++ b/contrib/cmd/runkperf/commands/data/configmaps/configmap.go @@ -36,7 +36,7 @@ var Command = cli.Command{ }, cli.StringFlag{ Name: "namespace", - Usage: "Namespace to use with commands. If the namespace does not exist, it will be created.", + Usage: "Namespace to use with commands. If the namespace does not exist, it will be created when executing add subcommand", Value: "default", }, }, diff --git a/contrib/cmd/runkperf/commands/data/events/event.go b/contrib/cmd/runkperf/commands/data/events/event.go index 379a1aa..dee4246 100644 --- a/contrib/cmd/runkperf/commands/data/events/event.go +++ b/contrib/cmd/runkperf/commands/data/events/event.go @@ -40,7 +40,7 @@ var Command = cli.Command{ }, cli.StringFlag{ Name: "namespace", - Usage: "Namespace to use with commands. If the namespace does not exist, it will be created.", + Usage: "Namespace to use with commands. If the namespace does not exist, it will be created when executing add subcommand.", Value: "default", }, }, diff --git a/contrib/cmd/runkperf/commands/data/secrets/secret.go b/contrib/cmd/runkperf/commands/data/secrets/secret.go index bb95bab..6741ae7 100644 --- a/contrib/cmd/runkperf/commands/data/secrets/secret.go +++ b/contrib/cmd/runkperf/commands/data/secrets/secret.go @@ -40,7 +40,7 @@ var Command = cli.Command{ }, cli.StringFlag{ Name: "namespace", - Usage: "Namespace to use with commands. If the namespace does not exist, it will be created.", + Usage: "Namespace to use with commands. If the namespace does not exist, it will be created when executing add subcommand", Value: "default", }, }, From a997f6ebc5b2b0a18d090559a3577d34812cfab4 Mon Sep 17 00:00:00 2001 From: Liyu Ma Date: Fri, 20 Mar 2026 16:39:07 +1000 Subject: [PATCH 12/12] Update contrib/cmd/runkperf/commands/data/secrets/secret.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- contrib/cmd/runkperf/commands/data/secrets/secret.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/cmd/runkperf/commands/data/secrets/secret.go b/contrib/cmd/runkperf/commands/data/secrets/secret.go index 6741ae7..cd89b84 100644 --- a/contrib/cmd/runkperf/commands/data/secrets/secret.go +++ b/contrib/cmd/runkperf/commands/data/secrets/secret.go @@ -238,7 +238,7 @@ var secretListCommand = cli.Command{ ownerID, ok := sec.Labels["ownerID"] if !ok { - return fmt.Errorf("failed to find the ownerID of secret %s", name) + return fmt.Errorf("failed to find the ownerID of secret %s", sec.Name) } info.ownerID[ownerID]++ return nil