github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/bucket/bandwidth/monitor.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package bandwidth 19 20 //go:generate msgp -file=$GOFILE -unexported 21 22 import ( 23 "context" 24 "sync" 25 "time" 26 27 "golang.org/x/time/rate" 28 ) 29 30 //msgp:ignore bucketThrottle Monitor 31 32 type bucketThrottle struct { 33 *rate.Limiter 34 NodeBandwidthPerSec int64 35 } 36 37 // Monitor holds the state of the global bucket monitor 38 type Monitor struct { 39 tlock sync.RWMutex // mutex for bucket throttling 40 mlock sync.RWMutex // mutex for bucket measurement 41 42 bucketsThrottle map[BucketOptions]*bucketThrottle 43 bucketsMeasurement map[BucketOptions]*bucketMeasurement // Buckets with objects in flight 44 45 bucketMovingAvgTicker *time.Ticker // Ticker for calculating moving averages 46 ctx context.Context // Context for generate 47 NodeCount uint64 48 } 49 50 // NewMonitor returns a monitor with defaults. 51 func NewMonitor(ctx context.Context, numNodes uint64) *Monitor { 52 m := &Monitor{ 53 bucketsMeasurement: make(map[BucketOptions]*bucketMeasurement), 54 bucketsThrottle: make(map[BucketOptions]*bucketThrottle), 55 bucketMovingAvgTicker: time.NewTicker(2 * time.Second), 56 ctx: ctx, 57 NodeCount: numNodes, 58 } 59 go m.trackEWMA() 60 return m 61 } 62 63 func (m *Monitor) updateMeasurement(opts BucketOptions, bytes uint64) { 64 m.mlock.Lock() 65 defer m.mlock.Unlock() 66 67 tm, ok := m.bucketsMeasurement[opts] 68 if !ok { 69 tm = &bucketMeasurement{} 70 } 71 tm.incrementBytes(bytes) 72 m.bucketsMeasurement[opts] = tm 73 } 74 75 // SelectionFunction for buckets 76 type SelectionFunction func(bucket string) bool 77 78 // SelectBuckets will select all the buckets passed in. 79 func SelectBuckets(buckets ...string) SelectionFunction { 80 if len(buckets) == 0 { 81 return func(bucket string) bool { 82 return true 83 } 84 } 85 return func(bucket string) bool { 86 for _, bkt := range buckets { 87 if bkt == bucket { 88 return true 89 } 90 } 91 return false 92 } 93 } 94 95 // Details for the measured bandwidth 96 type Details struct { 97 LimitInBytesPerSecond int64 `json:"limitInBits"` 98 CurrentBandwidthInBytesPerSecond float64 `json:"currentBandwidth"` 99 } 100 101 // BucketBandwidthReport captures the details for all buckets. 102 type BucketBandwidthReport struct { 103 BucketStats map[BucketOptions]Details `json:"bucketStats,omitempty"` 104 } 105 106 // GetReport gets the report for all bucket bandwidth details. 107 func (m *Monitor) GetReport(selectBucket SelectionFunction) *BucketBandwidthReport { 108 m.mlock.RLock() 109 defer m.mlock.RUnlock() 110 return m.getReport(selectBucket) 111 } 112 113 func (m *Monitor) getReport(selectBucket SelectionFunction) *BucketBandwidthReport { 114 report := &BucketBandwidthReport{ 115 BucketStats: make(map[BucketOptions]Details), 116 } 117 for bucketOpts, bucketMeasurement := range m.bucketsMeasurement { 118 if !selectBucket(bucketOpts.Name) { 119 continue 120 } 121 m.tlock.RLock() 122 if tgtThrottle, ok := m.bucketsThrottle[bucketOpts]; ok { 123 currBw := bucketMeasurement.getExpMovingAvgBytesPerSecond() 124 report.BucketStats[bucketOpts] = Details{ 125 LimitInBytesPerSecond: tgtThrottle.NodeBandwidthPerSec * int64(m.NodeCount), 126 CurrentBandwidthInBytesPerSecond: currBw, 127 } 128 } 129 m.tlock.RUnlock() 130 131 } 132 return report 133 } 134 135 func (m *Monitor) trackEWMA() { 136 for { 137 select { 138 case <-m.bucketMovingAvgTicker.C: 139 m.updateMovingAvg() 140 case <-m.ctx.Done(): 141 return 142 } 143 } 144 } 145 146 func (m *Monitor) updateMovingAvg() { 147 m.mlock.Lock() 148 defer m.mlock.Unlock() 149 for _, bucketMeasurement := range m.bucketsMeasurement { 150 bucketMeasurement.updateExponentialMovingAverage(time.Now()) 151 } 152 } 153 154 func (m *Monitor) init(opts BucketOptions) { 155 m.mlock.Lock() 156 defer m.mlock.Unlock() 157 158 _, ok := m.bucketsMeasurement[opts] 159 if !ok { 160 m.bucketsMeasurement[opts] = newBucketMeasurement(time.Now()) 161 } 162 } 163 164 // DeleteBucket deletes monitoring the 'bucket' 165 func (m *Monitor) DeleteBucket(bucket string) { 166 m.tlock.Lock() 167 for opts := range m.bucketsThrottle { 168 if opts.Name == bucket { 169 delete(m.bucketsThrottle, opts) 170 } 171 } 172 m.tlock.Unlock() 173 174 m.mlock.Lock() 175 for opts := range m.bucketsMeasurement { 176 if opts.Name == bucket { 177 delete(m.bucketsMeasurement, opts) 178 } 179 } 180 m.mlock.Unlock() 181 } 182 183 // DeleteBucketThrottle deletes monitoring for a bucket's target 184 func (m *Monitor) DeleteBucketThrottle(bucket, arn string) { 185 m.tlock.Lock() 186 delete(m.bucketsThrottle, BucketOptions{Name: bucket, ReplicationARN: arn}) 187 m.tlock.Unlock() 188 m.mlock.Lock() 189 delete(m.bucketsMeasurement, BucketOptions{Name: bucket, ReplicationARN: arn}) 190 m.mlock.Unlock() 191 } 192 193 // throttle returns currently configured throttle for this bucket 194 func (m *Monitor) throttle(opts BucketOptions) *bucketThrottle { 195 m.tlock.RLock() 196 defer m.tlock.RUnlock() 197 return m.bucketsThrottle[opts] 198 } 199 200 // SetBandwidthLimit sets the bandwidth limit for a bucket 201 func (m *Monitor) SetBandwidthLimit(bucket, arn string, limit int64) { 202 m.tlock.Lock() 203 defer m.tlock.Unlock() 204 limitBytes := limit / int64(m.NodeCount) 205 throttle, ok := m.bucketsThrottle[BucketOptions{Name: bucket, ReplicationARN: arn}] 206 if !ok { 207 throttle = &bucketThrottle{} 208 } 209 throttle.NodeBandwidthPerSec = limitBytes 210 throttle.Limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes)) 211 m.bucketsThrottle[BucketOptions{Name: bucket, ReplicationARN: arn}] = throttle 212 } 213 214 // IsThrottled returns true if a bucket has bandwidth throttling enabled. 215 func (m *Monitor) IsThrottled(bucket, arn string) bool { 216 m.tlock.RLock() 217 defer m.tlock.RUnlock() 218 _, ok := m.bucketsThrottle[BucketOptions{Name: bucket, ReplicationARN: arn}] 219 return ok 220 }