github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/bucket/bandwidth/measurement.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  import (
    21  	"sync"
    22  	"sync/atomic"
    23  	"time"
    24  )
    25  
    26  const (
    27  	// betaBucket is the weight used to calculate exponential moving average
    28  	betaBucket = 0.1 // Number of averages considered = 1/(1-betaObject)
    29  )
    30  
    31  // bucketMeasurement captures the bandwidth details for one bucket
    32  type bucketMeasurement struct {
    33  	lock                 sync.Mutex
    34  	bytesSinceLastWindow uint64    // Total bytes since last window was processed
    35  	startTime            time.Time // Start time for window
    36  	expMovingAvg         float64   // Previously calculate sliding window
    37  }
    38  
    39  // newBucketMeasurement creates a new instance of the measurement with the initial start time.
    40  func newBucketMeasurement(initTime time.Time) *bucketMeasurement {
    41  	return &bucketMeasurement{
    42  		startTime: initTime,
    43  	}
    44  }
    45  
    46  // incrementBytes add bytes reported for a bucket.
    47  func (m *bucketMeasurement) incrementBytes(bytes uint64) {
    48  	atomic.AddUint64(&m.bytesSinceLastWindow, bytes)
    49  }
    50  
    51  // updateExponentialMovingAverage processes the measurements captured so far.
    52  func (m *bucketMeasurement) updateExponentialMovingAverage(endTime time.Time) {
    53  	// Calculate aggregate avg bandwidth and exp window avg
    54  	m.lock.Lock()
    55  	defer func() {
    56  		m.startTime = endTime
    57  		m.lock.Unlock()
    58  	}()
    59  
    60  	if m.startTime.IsZero() {
    61  		return
    62  	}
    63  
    64  	if endTime.Before(m.startTime) {
    65  		return
    66  	}
    67  
    68  	duration := endTime.Sub(m.startTime)
    69  
    70  	bytesSinceLastWindow := atomic.SwapUint64(&m.bytesSinceLastWindow, 0)
    71  
    72  	if m.expMovingAvg == 0 {
    73  		// Should address initial calculation and should be fine for resuming from 0
    74  		m.expMovingAvg = float64(bytesSinceLastWindow) / duration.Seconds()
    75  		return
    76  	}
    77  
    78  	increment := float64(bytesSinceLastWindow) / duration.Seconds()
    79  	m.expMovingAvg = exponentialMovingAverage(betaBucket, m.expMovingAvg, increment)
    80  }
    81  
    82  // exponentialMovingAverage calculates the exponential moving average
    83  func exponentialMovingAverage(beta, previousAvg, incrementAvg float64) float64 {
    84  	return (1-beta)*incrementAvg + beta*previousAvg
    85  }
    86  
    87  // getExpMovingAvgBytesPerSecond returns the exponential moving average for the bucket in bytes
    88  func (m *bucketMeasurement) getExpMovingAvgBytesPerSecond() float64 {
    89  	m.lock.Lock()
    90  	defer m.lock.Unlock()
    91  	return m.expMovingAvg
    92  }