77 "sort"
88 "strconv"
99 "strings"
10+ "sync"
1011 "time"
1112
1213 "bufio"
@@ -112,15 +113,14 @@ func NewClientWithDebug(kubeconfigPath, kubeContext string, debug bool) (*Client
112113 }
113114 }
114115
115- // Configure rate limiting to prevent throttling
116- // QPS: queries per second (default is 5, increase to 10)
117- // Burst: maximum burst of requests (default is 10, increase to 20)
118- // This helps prevent "client-side throttling" errors when querying many resources
116+ // Increase client-side rate limits (defaults: 5 QPS, 10 burst).
117+ // The parallel namespace listing issues up to maxConcurrent*4 calls concurrently;
118+ // low limits cause artificial multi-second delays per request.
119119 if config .QPS == 0 {
120- config .QPS = 10
120+ config .QPS = 50
121121 }
122122 if config .Burst == 0 {
123- config .Burst = 20
123+ config .Burst = 100
124124 }
125125
126126 clientset , err := kubernetes .NewForConfig (config )
@@ -350,13 +350,21 @@ func (c *Client) calculateWorkloadsStorage(ctx context.Context, workloads []Work
350350 return workloads , nil
351351 }
352352
353- // For StatefulSets, fetch them to get volumeClaimTemplate names for accurate matching
354- statefulSetMap := make (map [string ]map [string ]bool ) // namespace/name -> set of PVC template names
355- for _ , workload := range workloads {
356- if workload .Type == "statefulset" {
357- sts , err := c .clientset .AppsV1 ().StatefulSets (workload .Namespace ).Get (ctx , workload .Name , metav1.GetOptions {})
358- if err == nil {
359- key := fmt .Sprintf ("%s/%s" , workload .Namespace , workload .Name )
353+ // Build volumeClaimTemplate map with one cluster-wide List() instead of N individual Get() calls.
354+ statefulSetMap := make (map [string ]map [string ]bool ) // "namespace/name" -> set of PVC template names
355+ hasStatefulSets := false
356+ for _ , w := range workloads {
357+ if w .Type == "statefulset" {
358+ hasStatefulSets = true
359+ break
360+ }
361+ }
362+ if hasStatefulSets {
363+ allSTS , err := c .clientset .AppsV1 ().StatefulSets ("" ).List (ctx , metav1.ListOptions {})
364+ if err == nil {
365+ for i := range allSTS .Items {
366+ sts := & allSTS .Items [i ]
367+ key := fmt .Sprintf ("%s/%s" , sts .Namespace , sts .Name )
360368 templateNames := make (map [string ]bool )
361369 for _ , vct := range sts .Spec .VolumeClaimTemplates {
362370 templateNames [vct .Name ] = true
@@ -488,25 +496,59 @@ func (c *Client) ListAllWorkloads(ctx context.Context) ([]WorkloadInfo, error) {
488496 return allWorkloads , nil
489497 }
490498
491- // List workloads from each namespace
492- var errors []string
493- successfulNamespaces := 0
499+ // Filter out system namespaces first
500+ userNamespaces := make ([]string , 0 , len (namespaces ))
494501 for _ , ns := range namespaces {
495- // Skip system namespaces
496502 if ns == "kube-system" || ns == "kube-public" || ns == "kube-node-lease" {
497503 continue
498504 }
505+ userNamespaces = append (userNamespaces , ns )
506+ }
499507
500- workloads , err := c .ListWorkloads (ctx , ns )
501- if err != nil {
502- // Log error but continue with other namespaces
503- // Collect errors to return if all namespaces fail
504- errors = append (errors , fmt .Sprintf ("namespace %s: %v" , ns , err ))
508+ // List workloads concurrently across namespaces with bounded parallelism.
509+ type nsResult struct {
510+ ns string
511+ workloads []WorkloadInfo
512+ err error
513+ }
514+
515+ const maxConcurrent = 10
516+ sem := make (chan struct {}, maxConcurrent )
517+ results := make (chan nsResult , len (userNamespaces ))
518+
519+ var wg sync.WaitGroup
520+ for _ , ns := range userNamespaces {
521+ wg .Add (1 )
522+ go func (namespace string ) {
523+ defer wg .Done ()
524+ select {
525+ case sem <- struct {}{}:
526+ defer func () { <- sem }()
527+ case <- ctx .Done ():
528+ results <- nsResult {ns : namespace , err : ctx .Err ()}
529+ return
530+ }
531+ workloads , err := c .ListWorkloads (ctx , namespace )
532+ results <- nsResult {ns : namespace , workloads : workloads , err : err }
533+ }(ns )
534+ }
535+
536+ // Close results channel once all goroutines complete
537+ go func () {
538+ wg .Wait ()
539+ close (results )
540+ }()
541+
542+ // Collect results
543+ var errors []string
544+ successfulNamespaces := 0
545+ for result := range results {
546+ if result .err != nil {
547+ errors = append (errors , fmt .Sprintf ("namespace %s: %v" , result .ns , result .err ))
505548 continue
506549 }
507-
508550 successfulNamespaces ++
509- allWorkloads = append (allWorkloads , workloads ... )
551+ allWorkloads = append (allWorkloads , result . workloads ... )
510552 }
511553
512554 // If we have no workloads and errors from all namespaces, return an error
@@ -517,7 +559,7 @@ func (c *Client) ListAllWorkloads(ctx context.Context) ([]WorkloadInfo, error) {
517559 if len (errors ) < maxErrors {
518560 maxErrors = len (errors )
519561 }
520- return nil , fmt .Errorf ("failed to list workloads from any namespace (checked %d namespaces). First errors: %s" , len (namespaces ), strings .Join (errors [:maxErrors ], "; " ))
562+ return nil , fmt .Errorf ("failed to list workloads from any namespace (checked %d namespaces). First errors: %s" , len (userNamespaces ), strings .Join (errors [:maxErrors ], "; " ))
521563 }
522564
523565 // Calculate usage for all workloads in batch (much faster - single pod fetch)
0 commit comments