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