2018-02-01 14:13:24 +01:00
|
|
|
package accounting
|
|
|
|
|
|
|
|
import (
|
2018-04-06 20:13:27 +02:00
|
|
|
"context"
|
2018-02-01 14:13:24 +01:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2018-03-16 22:45:09 +01:00
|
|
|
"github.com/pkg/errors"
|
2019-07-28 19:47:38 +02:00
|
|
|
"github.com/rclone/rclone/fs"
|
|
|
|
"github.com/rclone/rclone/fs/rc"
|
2018-02-01 14:13:24 +01:00
|
|
|
"golang.org/x/time/rate"
|
|
|
|
)
|
|
|
|
|
2020-07-04 18:20:54 +02:00
|
|
|
// TokenBucket holds the global token bucket limiter
|
|
|
|
var TokenBucket tokenBucket
|
|
|
|
|
|
|
|
// TokenBucketSlot is the type to select which token bucket to use
|
|
|
|
type TokenBucketSlot int
|
|
|
|
|
|
|
|
// Slots for the token bucket
|
|
|
|
const (
|
|
|
|
TokenBucketSlotAccounting TokenBucketSlot = iota
|
|
|
|
TokenBucketSlots
|
2018-02-01 14:13:24 +01:00
|
|
|
)
|
|
|
|
|
2020-07-04 18:20:54 +02:00
|
|
|
type buckets [TokenBucketSlots]*rate.Limiter
|
|
|
|
|
|
|
|
// tokenBucket holds info about the rate limiters in use
|
|
|
|
type tokenBucket struct {
|
|
|
|
mu sync.RWMutex // protects the token bucket variables
|
|
|
|
curr buckets
|
|
|
|
prev buckets
|
|
|
|
toggledOff bool
|
|
|
|
currLimitMu sync.Mutex // protects changes to the timeslot
|
|
|
|
currLimit fs.BwTimeSlot
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return true if limit is disabled
|
|
|
|
//
|
|
|
|
// Call with lock held
|
|
|
|
func (bs *buckets) _isOff() bool {
|
|
|
|
return bs[0] == nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Disable the limits
|
|
|
|
//
|
|
|
|
// Call with lock held
|
|
|
|
func (bs *buckets) _setOff() {
|
|
|
|
for i := range bs {
|
|
|
|
bs[i] = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-30 18:24:08 +02:00
|
|
|
const maxBurstSize = 4 * 1024 * 1024 // must be bigger than the biggest request
|
2018-02-01 14:13:24 +01:00
|
|
|
|
|
|
|
// make a new empty token bucket with the bandwidth given
|
2020-07-04 18:20:54 +02:00
|
|
|
func newTokenBucket(bandwidth fs.SizeSuffix) (newTokenBucket buckets) {
|
|
|
|
for i := range newTokenBucket {
|
|
|
|
newTokenBucket[i] = rate.NewLimiter(rate.Limit(bandwidth), maxBurstSize)
|
|
|
|
// empty the bucket
|
|
|
|
err := newTokenBucket[i].WaitN(context.Background(), maxBurstSize)
|
|
|
|
if err != nil {
|
|
|
|
fs.Errorf(nil, "Failed to empty token bucket: %v", err)
|
|
|
|
}
|
2018-02-01 14:13:24 +01:00
|
|
|
}
|
|
|
|
return newTokenBucket
|
|
|
|
}
|
|
|
|
|
|
|
|
// StartTokenBucket starts the token bucket if necessary
|
2020-07-04 18:20:54 +02:00
|
|
|
func (tb *tokenBucket) StartTokenBucket(ctx context.Context) {
|
|
|
|
tb.mu.Lock()
|
|
|
|
defer tb.mu.Unlock()
|
2020-11-05 12:33:32 +01:00
|
|
|
ci := fs.GetConfig(ctx)
|
2020-07-04 18:20:54 +02:00
|
|
|
tb.currLimit = ci.BwLimit.LimitAt(time.Now())
|
|
|
|
if tb.currLimit.Bandwidth > 0 {
|
|
|
|
tb.curr = newTokenBucket(tb.currLimit.Bandwidth)
|
|
|
|
fs.Infof(nil, "Starting bandwidth limiter at %vBytes/s", &tb.currLimit.Bandwidth)
|
2018-02-01 14:13:24 +01:00
|
|
|
|
|
|
|
// Start the SIGUSR2 signal handler to toggle bandwidth.
|
|
|
|
// This function does nothing in windows systems.
|
2020-07-04 18:20:54 +02:00
|
|
|
tb.startSignalHandler()
|
2018-02-01 14:13:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// StartTokenTicker creates a ticker to update the bandwidth limiter every minute.
|
2020-07-04 18:20:54 +02:00
|
|
|
func (tb *tokenBucket) StartTokenTicker(ctx context.Context) {
|
2020-11-05 12:33:32 +01:00
|
|
|
ci := fs.GetConfig(ctx)
|
2018-02-01 14:13:24 +01:00
|
|
|
// If the timetable has a single entry or was not specified, we don't need
|
|
|
|
// a ticker to update the bandwidth.
|
2020-11-05 12:33:32 +01:00
|
|
|
if len(ci.BwLimit) <= 1 {
|
2018-02-01 14:13:24 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ticker := time.NewTicker(time.Minute)
|
|
|
|
go func() {
|
|
|
|
for range ticker.C {
|
2020-11-05 12:33:32 +01:00
|
|
|
limitNow := ci.BwLimit.LimitAt(time.Now())
|
2020-07-04 18:20:54 +02:00
|
|
|
tb.currLimitMu.Lock()
|
2018-02-01 14:13:24 +01:00
|
|
|
|
2020-07-04 18:20:54 +02:00
|
|
|
if tb.currLimit.Bandwidth != limitNow.Bandwidth {
|
|
|
|
tb.mu.Lock()
|
2018-02-01 14:13:24 +01:00
|
|
|
|
|
|
|
// If bwlimit is toggled off, the change should only
|
|
|
|
// become active on the next toggle, which causes
|
2020-07-04 18:20:54 +02:00
|
|
|
// an exchange of tb.curr <-> tb.prev
|
|
|
|
var targetBucket *buckets
|
|
|
|
if tb.toggledOff {
|
|
|
|
targetBucket = &tb.prev
|
2018-02-01 14:13:24 +01:00
|
|
|
} else {
|
2020-07-04 18:20:54 +02:00
|
|
|
targetBucket = &tb.curr
|
2018-02-01 14:13:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set new bandwidth. If unlimited, set tokenbucket to nil.
|
|
|
|
if limitNow.Bandwidth > 0 {
|
|
|
|
*targetBucket = newTokenBucket(limitNow.Bandwidth)
|
2020-07-04 18:20:54 +02:00
|
|
|
if tb.toggledOff {
|
2018-02-01 14:13:24 +01:00
|
|
|
fs.Logf(nil, "Scheduled bandwidth change. "+
|
|
|
|
"Limit will be set to %vBytes/s when toggled on again.", &limitNow.Bandwidth)
|
|
|
|
} else {
|
|
|
|
fs.Logf(nil, "Scheduled bandwidth change. Limit set to %vBytes/s", &limitNow.Bandwidth)
|
|
|
|
}
|
|
|
|
} else {
|
2020-07-04 18:20:54 +02:00
|
|
|
targetBucket._setOff()
|
2018-02-01 14:13:24 +01:00
|
|
|
fs.Logf(nil, "Scheduled bandwidth change. Bandwidth limits disabled")
|
|
|
|
}
|
|
|
|
|
2020-07-04 18:20:54 +02:00
|
|
|
tb.currLimit = limitNow
|
|
|
|
tb.mu.Unlock()
|
2018-02-01 14:13:24 +01:00
|
|
|
}
|
2020-07-04 18:20:54 +02:00
|
|
|
tb.currLimitMu.Unlock()
|
2018-02-01 14:13:24 +01:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2020-07-04 18:20:54 +02:00
|
|
|
// LimitBandwidth sleeps for the correct amount of time for the passage
|
2018-02-01 14:13:24 +01:00
|
|
|
// of n bytes according to the current bandwidth limit
|
2020-07-04 18:20:54 +02:00
|
|
|
func (tb *tokenBucket) LimitBandwidth(i TokenBucketSlot, n int) {
|
|
|
|
tb.mu.RLock()
|
2018-02-01 14:13:24 +01:00
|
|
|
|
|
|
|
// Limit the transfer speed if required
|
2020-07-04 18:20:54 +02:00
|
|
|
if !tb.curr._isOff() {
|
|
|
|
err := tb.curr[i].WaitN(context.Background(), n)
|
2018-02-01 14:13:24 +01:00
|
|
|
if err != nil {
|
|
|
|
fs.Errorf(nil, "Token bucket error: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-04 18:20:54 +02:00
|
|
|
tb.mu.RUnlock()
|
2018-02-01 14:13:24 +01:00
|
|
|
}
|
2018-03-16 22:45:09 +01:00
|
|
|
|
2018-04-24 10:43:07 +02:00
|
|
|
// SetBwLimit sets the current bandwidth limit
|
2020-07-04 18:20:54 +02:00
|
|
|
func (tb *tokenBucket) SetBwLimit(bandwidth fs.SizeSuffix) {
|
|
|
|
tb.mu.Lock()
|
|
|
|
defer tb.mu.Unlock()
|
2018-04-24 10:43:07 +02:00
|
|
|
if bandwidth > 0 {
|
2020-07-04 18:20:54 +02:00
|
|
|
tb.curr = newTokenBucket(bandwidth)
|
2018-04-24 10:43:07 +02:00
|
|
|
fs.Logf(nil, "Bandwidth limit set to %v", bandwidth)
|
|
|
|
} else {
|
2020-07-04 18:20:54 +02:00
|
|
|
tb.curr._setOff()
|
2018-04-24 10:43:07 +02:00
|
|
|
fs.Logf(nil, "Bandwidth limit reset to unlimited")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-04 18:20:54 +02:00
|
|
|
// read and set the bandwidth limits
|
|
|
|
func (tb *tokenBucket) rcBwlimit(ctx context.Context, in rc.Params) (out rc.Params, err error) {
|
|
|
|
if in["rate"] != nil {
|
|
|
|
bwlimit, err := in.GetString("rate")
|
|
|
|
if err != nil {
|
|
|
|
return out, err
|
|
|
|
}
|
|
|
|
var bws fs.BwTimetable
|
|
|
|
err = bws.Set(bwlimit)
|
|
|
|
if err != nil {
|
|
|
|
return out, errors.Wrap(err, "bad bwlimit")
|
|
|
|
}
|
|
|
|
if len(bws) != 1 {
|
|
|
|
return out, errors.New("need exactly 1 bandwidth setting")
|
|
|
|
}
|
|
|
|
bw := bws[0]
|
|
|
|
tb.SetBwLimit(bw.Bandwidth)
|
|
|
|
}
|
|
|
|
tb.mu.RLock()
|
|
|
|
bytesPerSecond := int64(-1)
|
|
|
|
if !tb.curr._isOff() {
|
|
|
|
bytesPerSecond = int64(tb.curr[0].Limit())
|
|
|
|
}
|
|
|
|
tb.mu.RUnlock()
|
|
|
|
out = rc.Params{
|
|
|
|
"rate": fs.SizeSuffix(bytesPerSecond).String(),
|
|
|
|
"bytesPerSecond": bytesPerSecond,
|
|
|
|
}
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
2018-03-16 22:45:09 +01:00
|
|
|
// Remote control for the token bucket
|
|
|
|
func init() {
|
|
|
|
rc.Add(rc.Call{
|
|
|
|
Path: "core/bwlimit",
|
2019-06-17 10:34:30 +02:00
|
|
|
Fn: func(ctx context.Context, in rc.Params) (out rc.Params, err error) {
|
2020-07-04 18:20:54 +02:00
|
|
|
return TokenBucket.rcBwlimit(ctx, in)
|
2018-03-16 22:45:09 +01:00
|
|
|
},
|
|
|
|
Title: "Set the bandwidth limit.",
|
|
|
|
Help: `
|
|
|
|
This sets the bandwidth limit to that passed in.
|
|
|
|
|
|
|
|
Eg
|
|
|
|
|
2018-04-23 21:44:44 +02:00
|
|
|
rclone rc core/bwlimit rate=off
|
2019-06-24 14:18:52 +02:00
|
|
|
{
|
|
|
|
"bytesPerSecond": -1,
|
|
|
|
"rate": "off"
|
|
|
|
}
|
|
|
|
rclone rc core/bwlimit rate=1M
|
|
|
|
{
|
|
|
|
"bytesPerSecond": 1048576,
|
|
|
|
"rate": "1M"
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-05-19 13:02:44 +02:00
|
|
|
If the rate parameter is not supplied then the bandwidth is queried
|
2019-06-24 14:18:52 +02:00
|
|
|
|
|
|
|
rclone rc core/bwlimit
|
|
|
|
{
|
|
|
|
"bytesPerSecond": 1048576,
|
|
|
|
"rate": "1M"
|
|
|
|
}
|
2018-03-16 22:45:09 +01:00
|
|
|
|
|
|
|
The format of the parameter is exactly the same as passed to --bwlimit
|
|
|
|
except only one bandwidth may be specified.
|
2019-06-24 14:18:52 +02:00
|
|
|
|
|
|
|
In either case "rate" is returned as a human readable string, and
|
|
|
|
"bytesPerSecond" is returned as a number.
|
2018-03-16 22:45:09 +01:00
|
|
|
`,
|
|
|
|
})
|
|
|
|
}
|