github.com/iotexproject/iotex-core@v1.14.1-rc1/pkg/counter/counter.go (about)

     1  // Copyright (c) 2018 IoTeX
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package counter
     7  
     8  import (
     9  	"sync"
    10  	"time"
    11  )
    12  
    13  // SlidingWindowCounter is used to count the number of events happened in the last X duration (in terms of a sliding
    14  // window). Interval defines how big the time window is and SlotGranularity defines how fine grained the counter is.
    15  type SlidingWindowCounter struct {
    16  	Interval        time.Duration
    17  	SlotGranularity time.Duration
    18  	window          []uint64
    19  	count           uint64
    20  	headIdx         int
    21  	lastUpdateTime  time.Time
    22  	locker          sync.Mutex
    23  }
    24  
    25  // NewSlidingWindowCounter creates an instance of SlidingWindowCounter
    26  func NewSlidingWindowCounter(i time.Duration, sg time.Duration) *SlidingWindowCounter {
    27  	c := &SlidingWindowCounter{Interval: i, SlotGranularity: sg}
    28  	c.window = make([]uint64, i/sg)
    29  	c.count = 0
    30  	c.headIdx = 0
    31  	c.lastUpdateTime = time.Now()
    32  	return c
    33  }
    34  
    35  // NewSlidingWindowCounterWithSecondSlot creates an instance of SlidingWindowCounter with the second level slot
    36  func NewSlidingWindowCounterWithSecondSlot(i time.Duration) *SlidingWindowCounter {
    37  	return NewSlidingWindowCounter(i, time.Second)
    38  }
    39  
    40  // Increment increase the counter by 1. It's a blocking operation.
    41  func (c *SlidingWindowCounter) Increment() {
    42  	c.locker.Lock()
    43  	defer c.locker.Unlock()
    44  
    45  	c.refresh()
    46  	c.window[c.headIdx]++
    47  	c.count++
    48  }
    49  
    50  // Count reads the current gauge. It's a blocking operation.
    51  func (c *SlidingWindowCounter) Count() uint64 {
    52  	c.locker.Lock()
    53  	defer c.locker.Unlock()
    54  
    55  	c.refresh()
    56  	return c.count
    57  }
    58  
    59  func (c *SlidingWindowCounter) refresh() {
    60  	now := time.Now()
    61  	duration := int(now.Sub(c.lastUpdateTime) / c.SlotGranularity)
    62  	if duration >= len(c.window) {
    63  		for i := 0; i < len(c.window); i++ {
    64  			if i == 0 {
    65  				c.window[i] = 1
    66  			} else {
    67  				c.window[i] = 0
    68  			}
    69  		}
    70  		c.headIdx = 0
    71  		c.count = 0
    72  
    73  	} else {
    74  		for i := 0; i < duration; i++ {
    75  			c.headIdx++
    76  			if c.headIdx >= len(c.window) {
    77  				c.headIdx = 0
    78  			}
    79  			c.count -= c.window[c.headIdx]
    80  			c.window[c.headIdx] = 0
    81  		}
    82  	}
    83  	// Only change the lastUpdateTime when duration is greater than 0. That said, lastUpdateTime is updated only when
    84  	// the delta is greater than the slog granularity. This is to prevent keep updating the lastUpdateTime if incoming
    85  	// messages is so frequent that now - lastUpdateTime is always smaller than slog granularity, eventually always
    86  	// increasing the counter in the same slot.
    87  	if duration > 0 {
    88  		c.lastUpdateTime = now
    89  	}
    90  }