github.com/cilium/cilium@v1.16.2/operator/pkg/ciliumendpointslice/rate_limit.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package ciliumendpointslice
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"sort"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/sirupsen/logrus"
    14  	"golang.org/x/time/rate"
    15  	"k8s.io/client-go/util/workqueue"
    16  
    17  	"github.com/cilium/cilium/pkg/logging/logfields"
    18  )
    19  
    20  type rateLimit struct {
    21  	Nodes int
    22  	Limit float64
    23  	Burst int
    24  }
    25  
    26  type rateLimitConfig struct {
    27  	current          rateLimit
    28  	dynamicRateLimit dynamicRateLimit
    29  
    30  	rateLimiter *workqueue.BucketRateLimiter
    31  
    32  	logger logrus.FieldLogger
    33  }
    34  
    35  type dynamicRateLimit []rateLimit
    36  
    37  func getRateLimitConfig(p params) (rateLimitConfig, error) {
    38  	rlc := rateLimitConfig{
    39  		logger: p.Logger,
    40  	}
    41  	parsed, err := parseDynamicRateLimit(p.Cfg.CESDynamicRateLimitConfig)
    42  	if err != nil {
    43  		return rlc, fmt.Errorf("Couldn't parse CES rate limit config: %w", err)
    44  	}
    45  	rlc.dynamicRateLimit = parsed
    46  	rlc.updateRateLimitWithNodes(0, true)
    47  	rlc.rateLimiter = &workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(rlc.current.Limit), rlc.current.Burst)}
    48  	return rlc, nil
    49  }
    50  
    51  func parseDynamicRateLimit(cfg string) (dynamicRateLimit, error) {
    52  	if len(cfg) == 0 {
    53  		return nil, fmt.Errorf("invalid: config is empty")
    54  	}
    55  	dynamicRateLimit := dynamicRateLimit{}
    56  	decoder := json.NewDecoder(strings.NewReader(cfg))
    57  	decoder.DisallowUnknownFields()
    58  
    59  	if err := decoder.Decode(&dynamicRateLimit); err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	sort.Slice(dynamicRateLimit, func(i, j int) bool {
    64  		return dynamicRateLimit[i].Nodes < dynamicRateLimit[j].Nodes
    65  	})
    66  	return dynamicRateLimit, nil
    67  }
    68  
    69  func (rlc *rateLimitConfig) getDelay() time.Duration {
    70  	return rlc.rateLimiter.Reserve().Delay()
    71  }
    72  
    73  func (rlc *rateLimitConfig) updateRateLimiterWithNodes(nodes int) bool {
    74  	changed := rlc.updateRateLimitWithNodes(nodes, false)
    75  	if changed {
    76  		rlc.logger.WithFields(logrus.Fields{
    77  			"nodes":                       nodes,
    78  			logfields.WorkQueueQPSLimit:   rlc.current.Limit,
    79  			logfields.WorkQueueBurstLimit: rlc.current.Burst,
    80  		}).Info("Updating rate limit")
    81  
    82  		rlc.rateLimiter.SetBurst(rlc.current.Burst)
    83  		rlc.rateLimiter.SetLimit(rate.Limit(rlc.current.Limit))
    84  	}
    85  	return changed
    86  }
    87  
    88  func (rlc *rateLimitConfig) updateRateLimitWithNodes(nodes int, force bool) bool {
    89  	index := 0
    90  	for ; index < len(rlc.dynamicRateLimit)-1; index++ {
    91  		if rlc.dynamicRateLimit[index+1].Nodes > nodes {
    92  			break
    93  		}
    94  	}
    95  	changed := rlc.current.Nodes != rlc.dynamicRateLimit[index].Nodes
    96  
    97  	if changed || force {
    98  		rlc.current = rateLimit{
    99  			Nodes: rlc.dynamicRateLimit[index].Nodes,
   100  			Limit: rlc.dynamicRateLimit[index].Limit,
   101  			Burst: rlc.dynamicRateLimit[index].Burst,
   102  		}
   103  		if rlc.current.Limit > CESWriteQPSLimitMax {
   104  			rlc.current.Limit = CESWriteQPSLimitMax
   105  		}
   106  		if rlc.current.Burst > CESWriteQPSBurstMax {
   107  			rlc.current.Burst = CESWriteQPSBurstMax
   108  		}
   109  		return true
   110  	}
   111  	return false
   112  }