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 }