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 }