github.com/alibaba/sentinel-golang@v1.0.4/core/stat/base/leap_array.go (about) 1 // Copyright 1999-2020 Alibaba Group Holding Ltd. 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 base 16 17 import ( 18 "fmt" 19 "runtime" 20 "sync/atomic" 21 "unsafe" 22 23 "github.com/alibaba/sentinel-golang/core/base" 24 "github.com/alibaba/sentinel-golang/logging" 25 "github.com/alibaba/sentinel-golang/util" 26 "github.com/pkg/errors" 27 ) 28 29 // BucketWrap represent a slot to record metrics 30 // In order to reduce the usage of memory, BucketWrap don't hold length of BucketWrap 31 // The length of BucketWrap could be seen in LeapArray. 32 // The scope of time is [startTime, startTime+bucketLength) 33 // The size of BucketWrap is 24(8+16) bytes 34 type BucketWrap struct { 35 // The start timestamp of this statistic bucket wrapper. 36 BucketStart uint64 37 // The actual data structure to record the metrics (e.g. MetricBucket). 38 Value atomic.Value 39 } 40 41 func (ww *BucketWrap) resetTo(startTime uint64) { 42 ww.BucketStart = startTime 43 } 44 45 func (ww *BucketWrap) isTimeInBucket(now uint64, bucketLengthInMs uint32) bool { 46 return ww.BucketStart <= now && now < ww.BucketStart+uint64(bucketLengthInMs) 47 } 48 49 func calculateStartTime(now uint64, bucketLengthInMs uint32) uint64 { 50 return now - (now % uint64(bucketLengthInMs)) 51 } 52 53 // atomic BucketWrap array to resolve race condition 54 // AtomicBucketWrapArray can not append or delete element after initializing 55 type AtomicBucketWrapArray struct { 56 // The base address for real data array 57 base unsafe.Pointer 58 // The length of slice(array), it can not be modified. 59 length int 60 data []*BucketWrap 61 } 62 63 func NewAtomicBucketWrapArrayWithTime(len int, bucketLengthInMs uint32, now uint64, generator BucketGenerator) *AtomicBucketWrapArray { 64 ret := &AtomicBucketWrapArray{ 65 length: len, 66 data: make([]*BucketWrap, len), 67 } 68 69 idx := int((now / uint64(bucketLengthInMs)) % uint64(len)) 70 startTime := calculateStartTime(now, bucketLengthInMs) 71 72 for i := idx; i <= len-1; i++ { 73 ww := &BucketWrap{ 74 BucketStart: startTime, 75 Value: atomic.Value{}, 76 } 77 ww.Value.Store(generator.NewEmptyBucket()) 78 ret.data[i] = ww 79 startTime += uint64(bucketLengthInMs) 80 } 81 for i := 0; i < idx; i++ { 82 ww := &BucketWrap{ 83 BucketStart: startTime, 84 Value: atomic.Value{}, 85 } 86 ww.Value.Store(generator.NewEmptyBucket()) 87 ret.data[i] = ww 88 startTime += uint64(bucketLengthInMs) 89 } 90 91 // calculate base address for real data array 92 sliHeader := (*util.SliceHeader)(unsafe.Pointer(&ret.data)) 93 ret.base = unsafe.Pointer((**BucketWrap)(unsafe.Pointer(sliHeader.Data))) 94 return ret 95 } 96 97 // New AtomicBucketWrapArray with initializing field data 98 // Default, automatically initialize each BucketWrap 99 // len: length of array 100 // bucketLengthInMs: bucket length of BucketWrap 101 // generator: generator to generate bucket 102 func NewAtomicBucketWrapArray(len int, bucketLengthInMs uint32, generator BucketGenerator) *AtomicBucketWrapArray { 103 return NewAtomicBucketWrapArrayWithTime(len, bucketLengthInMs, util.CurrentTimeMillis(), generator) 104 } 105 106 func (aa *AtomicBucketWrapArray) elementOffset(idx int) (unsafe.Pointer, bool) { 107 if idx >= aa.length || idx < 0 { 108 logging.Error(errors.New("array index out of bounds"), 109 "array index out of bounds in AtomicBucketWrapArray.elementOffset()", 110 "idx", idx, "arrayLength", aa.length) 111 return nil, false 112 } 113 basePtr := aa.base 114 return unsafe.Pointer(uintptr(basePtr) + uintptr(idx)*unsafe.Sizeof(basePtr)), true 115 } 116 117 func (aa *AtomicBucketWrapArray) get(idx int) *BucketWrap { 118 // aa.elementOffset(idx) return the secondary pointer of BucketWrap, which is the pointer to the aa.data[idx] 119 // then convert to (*unsafe.Pointer) 120 if offset, ok := aa.elementOffset(idx); ok { 121 return (*BucketWrap)(atomic.LoadPointer((*unsafe.Pointer)(offset))) 122 } 123 return nil 124 } 125 126 func (aa *AtomicBucketWrapArray) compareAndSet(idx int, except, update *BucketWrap) bool { 127 // aa.elementOffset(idx) return the secondary pointer of BucketWrap, which is the pointer to the aa.data[idx] 128 // then convert to (*unsafe.Pointer) 129 // update secondary pointer 130 if offset, ok := aa.elementOffset(idx); ok { 131 return atomic.CompareAndSwapPointer((*unsafe.Pointer)(offset), unsafe.Pointer(except), unsafe.Pointer(update)) 132 } 133 return false 134 } 135 136 // The BucketWrap leap array, 137 // sampleCount represent the number of BucketWrap 138 // intervalInMs represent the interval of LeapArray. 139 // For example, bucketLengthInMs is 200ms, intervalInMs is 1000ms, so sampleCount is 5. 140 // Give a diagram to illustrate 141 // Suppose current time is 888, bucketLengthInMs is 200ms, intervalInMs is 1000ms, LeapArray will build the below windows 142 // B0 B1 B2 B3 B4 143 // |_______|_______|_______|_______|_______| 144 // 1000 1200 1400 1600 800 (1000) 145 // ^ 146 // time=888 147 type LeapArray struct { 148 bucketLengthInMs uint32 149 sampleCount uint32 150 intervalInMs uint32 151 array *AtomicBucketWrapArray 152 // update lock 153 updateLock mutex 154 } 155 156 func NewLeapArray(sampleCount uint32, intervalInMs uint32, generator BucketGenerator) (*LeapArray, error) { 157 if sampleCount == 0 || intervalInMs%sampleCount != 0 { 158 return nil, errors.Errorf("Invalid parameters, intervalInMs is %d, sampleCount is %d", intervalInMs, sampleCount) 159 } 160 if generator == nil { 161 return nil, errors.Errorf("Invalid parameters, BucketGenerator is nil") 162 } 163 bucketLengthInMs := intervalInMs / sampleCount 164 return &LeapArray{ 165 bucketLengthInMs: bucketLengthInMs, 166 sampleCount: sampleCount, 167 intervalInMs: intervalInMs, 168 array: NewAtomicBucketWrapArray(int(sampleCount), bucketLengthInMs, generator), 169 }, nil 170 } 171 172 func (la *LeapArray) CurrentBucket(bg BucketGenerator) (*BucketWrap, error) { 173 return la.currentBucketOfTime(util.CurrentTimeMillis(), bg) 174 } 175 176 func (la *LeapArray) currentBucketOfTime(now uint64, bg BucketGenerator) (*BucketWrap, error) { 177 if now <= 0 { 178 return nil, errors.New("Current time is less than 0.") 179 } 180 181 idx := la.calculateTimeIdx(now) 182 bucketStart := calculateStartTime(now, la.bucketLengthInMs) 183 184 for { //spin to get the current BucketWrap 185 old := la.array.get(idx) 186 if old == nil { 187 // because la.array.data had initiated when new la.array 188 // theoretically, here is not reachable 189 newWrap := &BucketWrap{ 190 BucketStart: bucketStart, 191 Value: atomic.Value{}, 192 } 193 newWrap.Value.Store(bg.NewEmptyBucket()) 194 if la.array.compareAndSet(idx, nil, newWrap) { 195 return newWrap, nil 196 } else { 197 runtime.Gosched() 198 } 199 } else if bucketStart == atomic.LoadUint64(&old.BucketStart) { 200 return old, nil 201 } else if bucketStart > atomic.LoadUint64(&old.BucketStart) { 202 // current time has been next cycle of LeapArray and LeapArray dont't count in last cycle. 203 // reset BucketWrap 204 if la.updateLock.TryLock() { 205 old = bg.ResetBucketTo(old, bucketStart) 206 la.updateLock.Unlock() 207 return old, nil 208 } else { 209 runtime.Gosched() 210 } 211 } else if bucketStart < atomic.LoadUint64(&old.BucketStart) { 212 if la.sampleCount == 1 { 213 // if sampleCount==1 in leap array, in concurrency scenario, this case is possible 214 return old, nil 215 } 216 // TODO: reserve for some special case (e.g. when occupying "future" buckets). 217 return nil, errors.New(fmt.Sprintf("Provided time timeMillis=%d is already behind old.BucketStart=%d.", bucketStart, old.BucketStart)) 218 } 219 } 220 } 221 222 func (la *LeapArray) calculateTimeIdx(now uint64) int { 223 timeId := now / uint64(la.bucketLengthInMs) 224 return int(timeId) % la.array.length 225 } 226 227 // Get all BucketWrap between [current time - leap array interval, current time] 228 func (la *LeapArray) Values() []*BucketWrap { 229 return la.valuesWithTime(util.CurrentTimeMillis()) 230 } 231 232 func (la *LeapArray) valuesWithTime(now uint64) []*BucketWrap { 233 if now <= 0 { 234 return make([]*BucketWrap, 0) 235 } 236 ret := make([]*BucketWrap, 0, la.array.length) 237 for i := 0; i < la.array.length; i++ { 238 ww := la.array.get(i) 239 if ww == nil || la.isBucketDeprecated(now, ww) { 240 continue 241 } 242 ret = append(ret, ww) 243 } 244 return ret 245 } 246 247 func (la *LeapArray) ValuesConditional(now uint64, predicate base.TimePredicate) []*BucketWrap { 248 if now <= 0 { 249 return make([]*BucketWrap, 0) 250 } 251 ret := make([]*BucketWrap, 0, la.array.length) 252 for i := 0; i < la.array.length; i++ { 253 ww := la.array.get(i) 254 if ww == nil || la.isBucketDeprecated(now, ww) || !predicate(atomic.LoadUint64(&ww.BucketStart)) { 255 continue 256 } 257 ret = append(ret, ww) 258 } 259 return ret 260 } 261 262 // Judge whether the BucketWrap is expired 263 func (la *LeapArray) isBucketDeprecated(now uint64, ww *BucketWrap) bool { 264 ws := atomic.LoadUint64(&ww.BucketStart) 265 return (now - ws) > uint64(la.intervalInMs) 266 } 267 268 // Generic interface to generate bucket 269 type BucketGenerator interface { 270 // called when timestamp entry a new slot interval 271 NewEmptyBucket() interface{} 272 273 // reset the BucketWrap, clear all data of BucketWrap 274 ResetBucketTo(bw *BucketWrap, startTime uint64) *BucketWrap 275 }