ratelimiter.go 2.02 KB
Newer Older
Karthik Bala's avatar
Karthik Bala committed
1 2 3
package mocknet

import (
Jeromy's avatar
Jeromy committed
4
	"sync"
Karthik Bala's avatar
Karthik Bala committed
5 6 7 8 9 10
	"time"
)

//  A ratelimiter is used by a link to determine how long to wait before sending
//  data given a bandwidth cap.
type ratelimiter struct {
Jeromy's avatar
Jeromy committed
11
	lock         sync.Mutex
Karthik Bala's avatar
Karthik Bala committed
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
	bandwidth    float64       // bytes per nanosecond
	allowance    float64       // in bytes
	maxAllowance float64       // in bytes
	lastUpdate   time.Time     // when allowance was updated last
	count        int           // number of times rate limiting was applied
	duration     time.Duration // total delay introduced due to rate limiting
}

//  Creates a new ratelimiter with bandwidth (in bytes/sec)
func NewRatelimiter(bandwidth float64) *ratelimiter {
	//  convert bandwidth to bytes per nanosecond
	b := bandwidth / float64(time.Second)
	return &ratelimiter{
		bandwidth:    b,
		allowance:    0,
		maxAllowance: bandwidth,
		lastUpdate:   time.Now(),
	}
}

//  Changes bandwidth of a ratelimiter and resets its allowance
func (r *ratelimiter) UpdateBandwidth(bandwidth float64) {
Jeromy's avatar
Jeromy committed
34 35
	r.lock.Lock()
	defer r.lock.Unlock()
Karthik Bala's avatar
Karthik Bala committed
36 37 38 39 40 41 42 43 44 45 46
	//  Convert bandwidth from bytes/second to bytes/nanosecond
	b := bandwidth / float64(time.Second)
	r.bandwidth = b
	//  Reset allowance
	r.allowance = 0
	r.maxAllowance = bandwidth
	r.lastUpdate = time.Now()
}

//  Returns how long to wait before sending data with length 'dataSize' bytes
func (r *ratelimiter) Limit(dataSize int) time.Duration {
Jeromy's avatar
Jeromy committed
47 48
	r.lock.Lock()
	defer r.lock.Unlock()
Karthik Bala's avatar
Karthik Bala committed
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
	//  update time
	var duration time.Duration = time.Duration(0)
	if r.bandwidth == 0 {
		return duration
	}
	current := time.Now()
	elapsedTime := current.Sub(r.lastUpdate)
	r.lastUpdate = current

	allowance := r.allowance + float64(elapsedTime)*r.bandwidth
	//  allowance can't exceed bandwidth
	if allowance > r.maxAllowance {
		allowance = r.maxAllowance
	}

	allowance -= float64(dataSize)
	if allowance < 0 {
		//  sleep until allowance is back to 0
		duration = time.Duration(-allowance / r.bandwidth)
		//  rate limiting was applied, record stats
		r.count++
		r.duration += duration
	}

	r.allowance = allowance
	return duration
}