github.com/amazechain/amc@v0.1.3/internal/p2p/leaky-bucket/collector.go (about)

     1  package leakybucket
     2  
     3  import (
     4  	"container/heap"
     5  	"sync"
     6  	"time"
     7  )
     8  
     9  //TODO: Finer grained locking.
    10  
    11  type bucketMap map[string]*LeakyBucket
    12  
    13  // A Collector can keep track of multiple LeakyBucket's. The caller does not
    14  // directly interact with the buckets, but instead addresses them by a string
    15  // key (e.g. IP address, hostname, hash, etc.) that is passed to most Collector
    16  // methods.
    17  //
    18  // All Collector methods are goroutine safe.
    19  type Collector struct {
    20  	buckets  bucketMap
    21  	heap     priorityQueue
    22  	rate     float64
    23  	capacity int64
    24  	period   time.Duration
    25  	lock     sync.Mutex
    26  	quit     chan bool
    27  }
    28  
    29  // NewCollector creates a new Collector. When new buckets are created within
    30  // the Collector, they will be assigned the capacity and rate of the Collector.
    31  // A Collector does not provide a way to change the rate or capacity of
    32  // bucket's within it. If different rates or capacities are required, either
    33  // use multiple Collector's or manage your own LeakyBucket's.
    34  //
    35  // If deleteEmptyBuckets is true, a concurrent goroutine will be run that
    36  // watches for bucket's that become empty and automatically removes them,
    37  // freeing up memory resources.
    38  func NewCollector(rate float64, capacity int64, period time.Duration, deleteEmptyBuckets bool) *Collector {
    39  	c := &Collector{
    40  		buckets:  make(bucketMap),
    41  		heap:     make(priorityQueue, 0, 4096),
    42  		rate:     rate,
    43  		capacity: capacity,
    44  		period:   period,
    45  		quit:     make(chan bool),
    46  	}
    47  
    48  	if deleteEmptyBuckets {
    49  		c.PeriodicPrune()
    50  	}
    51  
    52  	return c
    53  }
    54  
    55  // Free releases the collector's resources. If the collector was created with
    56  // deleteEmptyBuckets = true, then the goroutine looking for empty buckets,
    57  // will be stopped.
    58  func (c *Collector) Free() {
    59  	c.Reset()
    60  	close(c.quit)
    61  }
    62  
    63  // Reset removes all internal buckets and resets the collector back to as if it
    64  // was just created.
    65  func (c *Collector) Reset() {
    66  	c.lock.Lock()
    67  	defer c.lock.Unlock()
    68  
    69  	// Let the garbage collector do all the work.
    70  	c.buckets = make(bucketMap)
    71  	c.heap = make(priorityQueue, 0, 4096)
    72  }
    73  
    74  // Capacity returns the collector's capacity.
    75  func (c *Collector) Capacity() int64 {
    76  	return c.capacity
    77  }
    78  
    79  // Rate returns the collector's rate.
    80  func (c *Collector) Rate() float64 {
    81  	return c.rate
    82  }
    83  
    84  // Remaining returns the remaining capacity of the internal bucket associated
    85  // with key.  If key is not associated with a bucket internally, it is treated
    86  // as being empty.
    87  func (c *Collector) Remaining(key string) int64 {
    88  	return c.capacity - c.Count(key)
    89  }
    90  
    91  // Count returns the count of the internal bucket associated with key. If key
    92  // is not associated with a bucket internally, it is treated as being empty.
    93  func (c *Collector) Count(key string) int64 {
    94  	c.lock.Lock()
    95  	defer c.lock.Unlock()
    96  
    97  	b, ok := c.buckets[key]
    98  	if !ok || b == nil {
    99  		return 0
   100  	}
   101  
   102  	return b.Count()
   103  }
   104  
   105  // TillEmpty returns how much time must pass until the internal bucket
   106  // associated with key is empty. If key is not associated with a bucket
   107  // internally, it is treated as being empty.
   108  func (c *Collector) TillEmpty(key string) time.Duration {
   109  	c.lock.Lock()
   110  	defer c.lock.Unlock()
   111  
   112  	b, ok := c.buckets[key]
   113  	if !ok || b == nil {
   114  		return 0
   115  	}
   116  
   117  	return b.TillEmpty()
   118  }
   119  
   120  // Remove deletes the internal bucket associated with key. If key is not
   121  // associated with a bucket internally, nothing is done.
   122  func (c *Collector) Remove(key string) {
   123  	c.lock.Lock()
   124  	defer c.lock.Unlock()
   125  
   126  	b, ok := c.buckets[key]
   127  	if !ok || b == nil {
   128  		return
   129  	}
   130  
   131  	delete(c.buckets, b.key)
   132  	heap.Remove(&c.heap, b.index)
   133  }
   134  
   135  // Add 'amount' to the internal bucket associated with key, up to it's
   136  // capacity. Returns how much was added to the bucket. If the return is less
   137  // than 'amount', then the bucket's capacity was reached.
   138  //
   139  // If key is not associated with a bucket internally, a new bucket is created
   140  // and amount is added to it.
   141  func (c *Collector) Add(key string, amount int64) int64 {
   142  	c.lock.Lock()
   143  	defer c.lock.Unlock()
   144  
   145  	b, ok := c.buckets[key]
   146  
   147  	if !ok || b == nil {
   148  		// Create a new bucket.
   149  		b = &LeakyBucket{
   150  			key:      key,
   151  			capacity: c.capacity,
   152  			rate:     c.rate,
   153  			period:   c.period,
   154  			p:        now(),
   155  		}
   156  		c.heap.Push(b)
   157  		c.buckets[key] = b
   158  	}
   159  
   160  	n := b.Add(amount)
   161  
   162  	if n > 0 {
   163  		heap.Fix(&c.heap, b.index)
   164  	}
   165  
   166  	return n
   167  }
   168  
   169  // Prune removes all empty buckets in the collector.
   170  func (c *Collector) Prune() {
   171  	c.lock.Lock()
   172  	for c.heap.Peak() != nil {
   173  		b := c.heap.Peak()
   174  
   175  		if now().Before(b.p) {
   176  			// The bucket isn't empty.
   177  			break
   178  		}
   179  
   180  		// The bucket should be empty.
   181  		delete(c.buckets, b.key)
   182  		heap.Remove(&c.heap, b.index)
   183  	}
   184  	c.lock.Unlock()
   185  }
   186  
   187  // PeriodicPrune runs a concurrent goroutine that calls Prune() at the given
   188  // time interval.
   189  func (c *Collector) PeriodicPrune() {
   190  	go func() {
   191  		ticker := time.NewTicker(c.period)
   192  		for {
   193  			select {
   194  			case <-ticker.C:
   195  				c.Prune()
   196  			case <-c.quit:
   197  				ticker.Stop()
   198  				return
   199  			}
   200  		}
   201  	}()
   202  }