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 }