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  }