github.com/uber/kraken@v0.1.4/utils/bandwidth/limiter.go (about)

     1  // Copyright (c) 2016-2019 Uber Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  package bandwidth
    15  
    16  import (
    17  	"errors"
    18  	"fmt"
    19  	"time"
    20  
    21  	"github.com/uber/kraken/utils/log"
    22  	"github.com/uber/kraken/utils/memsize"
    23  
    24  	"go.uber.org/zap"
    25  	"golang.org/x/time/rate"
    26  )
    27  
    28  // Config defines Limiter configuration.
    29  type Config struct {
    30  	EgressBitsPerSec  uint64 `yaml:"egress_bits_per_sec"`
    31  	IngressBitsPerSec uint64 `yaml:"ingress_bits_per_sec"`
    32  
    33  	// TokenSize defines the granularity of a token in the bucket. It is used to
    34  	// avoid integer overflow errors that would occur if we mapped each bit to a
    35  	// token.
    36  	TokenSize uint64 `yaml:"token_size"`
    37  
    38  	Enable bool `yaml:"enable"`
    39  }
    40  
    41  func (c Config) applyDefaults() Config {
    42  	if c.TokenSize == 0 {
    43  		c.TokenSize = 8 * memsize.Mbit
    44  	}
    45  	return c
    46  }
    47  
    48  // Limiter limits egress and ingress bandwidth via token-bucket rate limiter.
    49  type Limiter struct {
    50  	config  Config
    51  	egress  *rate.Limiter
    52  	ingress *rate.Limiter
    53  	logger  *zap.SugaredLogger
    54  }
    55  
    56  // Option allows setting optional parameters in Limiter.
    57  type Option func(*Limiter)
    58  
    59  // WithLogger configures a Limiter with a custom logger.
    60  func WithLogger(logger *zap.SugaredLogger) Option {
    61  	return func(l *Limiter) { l.logger = logger }
    62  }
    63  
    64  // NewLimiter creates a new Limiter.
    65  func NewLimiter(config Config, opts ...Option) (*Limiter, error) {
    66  	config = config.applyDefaults()
    67  
    68  	l := &Limiter{
    69  		config: config,
    70  		logger: log.Default(),
    71  	}
    72  	for _, opt := range opts {
    73  		opt(l)
    74  	}
    75  
    76  	if !config.Enable {
    77  		l.logger.Warn("Bandwidth limits disabled")
    78  		return l, nil
    79  	}
    80  
    81  	if config.EgressBitsPerSec == 0 {
    82  		return nil, errors.New("invalid config: egress_bits_per_sec must be non-zero")
    83  	}
    84  	if config.IngressBitsPerSec == 0 {
    85  		return nil, errors.New("invalid config: ingress_bits_per_sec must be non-zero")
    86  	}
    87  
    88  	l.logger.Infof("Setting egress bandwidth to %s/sec", memsize.BitFormat(config.EgressBitsPerSec))
    89  	l.logger.Infof("Setting ingress bandwidth to %s/sec", memsize.BitFormat(config.IngressBitsPerSec))
    90  
    91  	etps := config.EgressBitsPerSec / config.TokenSize
    92  	itps := config.IngressBitsPerSec / config.TokenSize
    93  
    94  	l.egress = rate.NewLimiter(rate.Limit(etps), int(etps))
    95  	l.ingress = rate.NewLimiter(rate.Limit(itps), int(itps))
    96  
    97  	return l, nil
    98  }
    99  
   100  func (l *Limiter) reserve(rl *rate.Limiter, nbytes int64) error {
   101  	if !l.config.Enable {
   102  		return nil
   103  	}
   104  	tokens := int(uint64(nbytes*8) / l.config.TokenSize)
   105  	if tokens == 0 {
   106  		tokens = 1
   107  	}
   108  	r := rl.ReserveN(time.Now(), tokens)
   109  	if !r.OK() {
   110  		return fmt.Errorf(
   111  			"cannot reserve %s of bandwidth, max is %s",
   112  			memsize.Format(uint64(nbytes)),
   113  			memsize.BitFormat(l.config.TokenSize*uint64(rl.Burst())))
   114  	}
   115  	time.Sleep(r.Delay())
   116  	return nil
   117  }
   118  
   119  // ReserveEgress blocks until egress bandwidth for nbytes is available.
   120  // Returns error if nbytes is larger than the maximum egress bandwidth.
   121  func (l *Limiter) ReserveEgress(nbytes int64) error {
   122  	return l.reserve(l.egress, nbytes)
   123  }
   124  
   125  // ReserveIngress blocks until ingress bandwidth for nbytes is available.
   126  // Returns error if nbytes is larger than the maximum ingress bandwidth.
   127  func (l *Limiter) ReserveIngress(nbytes int64) error {
   128  	return l.reserve(l.ingress, nbytes)
   129  }
   130  
   131  // Adjust divides the originally configured egress and ingress bps by denominator.
   132  // Note, because the original configuration is always used, multiple Adjust calls
   133  // have no affect on each other.
   134  func (l *Limiter) Adjust(denominator int) error {
   135  	if denominator <= 0 {
   136  		return errors.New("denominator must be greater than 0")
   137  	}
   138  
   139  	ebps := max(l.config.EgressBitsPerSec/l.config.TokenSize/uint64(denominator), 1)
   140  	ibps := max(l.config.IngressBitsPerSec/l.config.TokenSize/uint64(denominator), 1)
   141  
   142  	l.egress.SetLimit(rate.Limit(ebps))
   143  	l.ingress.SetLimit(rate.Limit(ibps))
   144  
   145  	return nil
   146  }
   147  
   148  // EgressLimit returns the current egress limit.
   149  func (l *Limiter) EgressLimit() int64 {
   150  	return int64(l.egress.Limit())
   151  }
   152  
   153  // IngressLimit returns the current ingress limit.
   154  func (l *Limiter) IngressLimit() int64 {
   155  	return int64(l.ingress.Limit())
   156  }
   157  
   158  func max(a, b uint64) uint64 {
   159  	if a > b {
   160  		return a
   161  	}
   162  	return b
   163  }