github.com/bytedance/gopkg@v0.0.0-20240514070511-01b2cbcf35e1/cloud/circuitbreaker/per_p_metricer.go (about)

     1  // Copyright 2021 ByteDance 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  
    15  package circuitbreaker
    16  
    17  import (
    18  	"fmt"
    19  	"sync/atomic"
    20  	"time"
    21  
    22  	"github.com/bytedance/gopkg/lang/syncx"
    23  )
    24  
    25  // perPBucket holds counts of failures and successes
    26  type perPBucket struct {
    27  	failure        int64
    28  	successCounter perPCounter
    29  	timeout        int64
    30  }
    31  
    32  func newPerPBucket() perPBucket {
    33  	return perPBucket{
    34  		successCounter: newPerPCounter(),
    35  	}
    36  }
    37  
    38  // Reset resets the counts to 0 and refreshes the time stamp
    39  func (b *perPBucket) Reset() {
    40  	atomic.StoreInt64(&b.failure, 0)
    41  	b.successCounter.Zero()
    42  	atomic.StoreInt64(&b.timeout, 0)
    43  }
    44  
    45  func (b *perPBucket) Fail() {
    46  	atomic.AddInt64(&b.failure, 1)
    47  }
    48  
    49  func (b *perPBucket) Succeed() {
    50  	b.successCounter.Add(1)
    51  }
    52  
    53  func (b *perPBucket) Timeout() {
    54  	atomic.AddInt64(&b.timeout, 1)
    55  }
    56  
    57  func (b *perPBucket) Failures() int64 {
    58  	return atomic.LoadInt64(&b.failure)
    59  }
    60  
    61  func (b *perPBucket) Successes() int64 {
    62  	return b.successCounter.Get()
    63  }
    64  
    65  func (b *perPBucket) Timeouts() int64 {
    66  	return atomic.LoadInt64(&b.timeout)
    67  }
    68  
    69  // perPWindow maintains a ring of buckets and increments the failure and success
    70  // counts of the current perPBucket.
    71  type perPWindow struct {
    72  	rw      syncx.RWMutex
    73  	oldest  int32        // oldest perPBucket index
    74  	latest  int32        // latest perPBucket index
    75  	buckets []perPBucket // buckets this perPWindow holds
    76  
    77  	bucketTime time.Duration // time each perPBucket holds
    78  	bucketNums int32         // the numbe of buckets
    79  	inWindow   int32         // the number of buckets in the perPWindow
    80  
    81  	allSuccessCounter perPCounter
    82  	allFailure        int64
    83  	allTimeout        int64
    84  
    85  	errStart int64
    86  	conseErr int64
    87  }
    88  
    89  // newPerPWindow .
    90  func newPerPWindow() metricer {
    91  	m, _ := newPerPWindowWithOptions(defaultBucketTime, defaultBucketNums)
    92  	return m
    93  }
    94  
    95  // newPerPWindowWithOptions creates a new perPWindow.
    96  func newPerPWindowWithOptions(bucketTime time.Duration, bucketNums int32) (metricer, error) {
    97  	if bucketNums < 100 {
    98  		return nil, fmt.Errorf("BucketNums can't be less than 100")
    99  	}
   100  
   101  	w := new(perPWindow)
   102  	w.rw = syncx.NewRWMutex()
   103  	w.allSuccessCounter = newPerPCounter()
   104  	w.bucketNums = bucketNums
   105  	w.bucketTime = bucketTime
   106  	w.buckets = make([]perPBucket, w.bucketNums)
   107  	for i := range w.buckets {
   108  		w.buckets[i] = newPerPBucket()
   109  	}
   110  
   111  	w.Reset()
   112  	return w, nil
   113  }
   114  
   115  // Succeed records a success in the current perPBucket.
   116  func (w *perPWindow) Succeed() {
   117  	rwx := w.rw.RLocker()
   118  	rwx.Lock()
   119  	b := w.getBucket()
   120  	atomic.StoreInt64(&w.errStart, 0)
   121  	atomic.StoreInt64(&w.conseErr, 0)
   122  	w.allSuccessCounter.Add(1)
   123  	rwx.Unlock()
   124  	b.Succeed()
   125  }
   126  
   127  // Fail records a failure in the current perPBucket.
   128  func (w *perPWindow) Fail() {
   129  	w.rw.Lock()
   130  	b := w.getBucket()
   131  	atomic.AddInt64(&w.conseErr, 1)
   132  	atomic.AddInt64(&w.allFailure, 1)
   133  	if atomic.LoadInt64(&w.errStart) == 0 {
   134  		atomic.StoreInt64(&w.errStart, time.Now().UnixNano())
   135  	}
   136  	w.rw.Unlock()
   137  	b.Fail()
   138  }
   139  
   140  // Timeout records a timeout in the current perPBucket
   141  func (w *perPWindow) Timeout() {
   142  	w.rw.Lock()
   143  	b := w.getBucket()
   144  	atomic.AddInt64(&w.conseErr, 1)
   145  	atomic.AddInt64(&w.allTimeout, 1)
   146  	if atomic.LoadInt64(&w.errStart) == 0 {
   147  		atomic.StoreInt64(&w.errStart, time.Now().UnixNano())
   148  	}
   149  	w.rw.Unlock()
   150  	b.Timeout()
   151  }
   152  
   153  func (w *perPWindow) Counts() (successes, failures, timeouts int64) {
   154  	return w.allSuccessCounter.Get(), atomic.LoadInt64(&w.allFailure), atomic.LoadInt64(&w.allTimeout)
   155  }
   156  
   157  // Successes returns the total number of successes recorded in all buckets.
   158  func (w *perPWindow) Successes() int64 {
   159  	return w.allSuccessCounter.Get()
   160  }
   161  
   162  // Failures returns the total number of failures recorded in all buckets.
   163  func (w *perPWindow) Failures() int64 {
   164  	return atomic.LoadInt64(&w.allFailure)
   165  }
   166  
   167  // Timeouts returns the total number of Timeout recorded in all buckets.
   168  func (w *perPWindow) Timeouts() int64 {
   169  	return atomic.LoadInt64(&w.allTimeout)
   170  }
   171  
   172  func (w *perPWindow) ConseErrors() int64 {
   173  	return atomic.LoadInt64(&w.conseErr)
   174  }
   175  
   176  func (w *perPWindow) ConseTime() time.Duration {
   177  	return time.Duration(time.Now().UnixNano() - atomic.LoadInt64(&w.errStart))
   178  }
   179  
   180  // ErrorRate returns the error rate calculated over all buckets, expressed as
   181  // a floating point number (e.g. 0.9 for 90%)
   182  func (w *perPWindow) ErrorRate() float64 {
   183  	successes, failures, timeouts := w.Counts()
   184  
   185  	if (successes + failures + timeouts) == 0 {
   186  		return 0.0
   187  	}
   188  
   189  	return float64(failures+timeouts) / float64(successes+failures+timeouts)
   190  }
   191  
   192  func (w *perPWindow) Samples() int64 {
   193  	successes, failures, timeouts := w.Counts()
   194  
   195  	return successes + failures + timeouts
   196  }
   197  
   198  // Reset resets this perPWindow
   199  func (w *perPWindow) Reset() {
   200  	w.rw.Lock()
   201  	atomic.StoreInt32(&w.oldest, 0)
   202  	atomic.StoreInt32(&w.latest, 0)
   203  	atomic.StoreInt32(&w.inWindow, 1)
   204  	atomic.StoreInt64(&w.conseErr, 0)
   205  	w.allSuccessCounter.Zero()
   206  	atomic.StoreInt64(&w.allFailure, 0)
   207  	atomic.StoreInt64(&w.allTimeout, 0)
   208  	w.getBucket().Reset()
   209  	w.rw.Unlock() // don't use defer
   210  }
   211  
   212  func (w *perPWindow) tick() {
   213  	w.rw.Lock()
   214  	// 这一段必须在前面,因为latest可能会覆盖oldest
   215  	if w.inWindow == w.bucketNums {
   216  		// the lastest covered the oldest(latest == oldest)
   217  		oldBucket := &w.buckets[w.oldest]
   218  		w.allSuccessCounter.Add(-oldBucket.Successes())
   219  		atomic.AddInt64(&w.allFailure, -oldBucket.Failures())
   220  		atomic.AddInt64(&w.allTimeout, -oldBucket.Timeouts())
   221  		w.oldest++
   222  		if w.oldest >= w.bucketNums {
   223  			w.oldest = 0
   224  		}
   225  	} else {
   226  		w.inWindow++
   227  	}
   228  
   229  	w.latest++
   230  	if w.latest >= w.bucketNums {
   231  		w.latest = 0
   232  	}
   233  	w.getBucket().Reset()
   234  	w.rw.Unlock()
   235  }
   236  
   237  func (w *perPWindow) getBucket() *perPBucket {
   238  	return &w.buckets[atomic.LoadInt32(&w.latest)]
   239  }