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  }