github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/fs/accounting/accounting.go (about)

     1  // Package accounting providers an accounting and limiting reader
     2  package accounting
     3  
     4  import (
     5  	"fmt"
     6  	"io"
     7  	"sync"
     8  	"time"
     9  	"unicode/utf8"
    10  
    11  	"github.com/ncw/rclone/fs"
    12  	"github.com/ncw/rclone/fs/asyncreader"
    13  	"github.com/ncw/rclone/fs/fserrors"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  // ErrorMaxTransferLimitReached is returned from Read when the max
    18  // transfer limit is reached.
    19  var ErrorMaxTransferLimitReached = fserrors.FatalError(errors.New("Max transfer limit reached as set by --max-transfer"))
    20  
    21  // Account limits and accounts for one transfer
    22  type Account struct {
    23  	// The mutex is to make sure Read() and Close() aren't called
    24  	// concurrently.  Unfortunately the persistent connection loop
    25  	// in http transport calls Read() after Do() returns on
    26  	// CancelRequest so this race can happen when it apparently
    27  	// shouldn't.
    28  	mu      sync.Mutex
    29  	in      io.Reader
    30  	origIn  io.ReadCloser
    31  	close   io.Closer
    32  	size    int64
    33  	name    string
    34  	statmu  sync.Mutex    // Separate mutex for stat values.
    35  	bytes   int64         // Total number of bytes read
    36  	max     int64         // if >=0 the max number of bytes to transfer
    37  	start   time.Time     // Start time of first read
    38  	lpTime  time.Time     // Time of last average measurement
    39  	lpBytes int           // Number of bytes read since last measurement
    40  	avg     float64       // Moving average of last few measurements in bytes/s
    41  	closed  bool          // set if the file is closed
    42  	exit    chan struct{} // channel that will be closed when transfer is finished
    43  	withBuf bool          // is using a buffered in
    44  }
    45  
    46  const averagePeriod = 16 // period to do exponentially weighted averages over
    47  
    48  // NewAccountSizeName makes a Account reader for an io.ReadCloser of
    49  // the given size and name
    50  func NewAccountSizeName(in io.ReadCloser, size int64, name string) *Account {
    51  	acc := &Account{
    52  		in:     in,
    53  		close:  in,
    54  		origIn: in,
    55  		size:   size,
    56  		name:   name,
    57  		exit:   make(chan struct{}),
    58  		avg:    0,
    59  		lpTime: time.Now(),
    60  		max:    int64(fs.Config.MaxTransfer),
    61  	}
    62  	go acc.averageLoop()
    63  	Stats.inProgress.set(acc.name, acc)
    64  	return acc
    65  }
    66  
    67  // NewAccount makes a Account reader for an object
    68  func NewAccount(in io.ReadCloser, obj fs.Object) *Account {
    69  	return NewAccountSizeName(in, obj.Size(), obj.Remote())
    70  }
    71  
    72  // WithBuffer - If the file is above a certain size it adds an Async reader
    73  func (acc *Account) WithBuffer() *Account {
    74  	acc.withBuf = true
    75  	var buffers int
    76  	if acc.size >= int64(fs.Config.BufferSize) || acc.size == -1 {
    77  		buffers = int(int64(fs.Config.BufferSize) / asyncreader.BufferSize)
    78  	} else {
    79  		buffers = int(acc.size / asyncreader.BufferSize)
    80  	}
    81  	// On big files add a buffer
    82  	if buffers > 0 {
    83  		rc, err := asyncreader.New(acc.origIn, buffers)
    84  		if err != nil {
    85  			fs.Errorf(acc.name, "Failed to make buffer: %v", err)
    86  		} else {
    87  			acc.in = rc
    88  			acc.close = rc
    89  		}
    90  	}
    91  	return acc
    92  }
    93  
    94  // GetReader returns the underlying io.ReadCloser under any Buffer
    95  func (acc *Account) GetReader() io.ReadCloser {
    96  	acc.mu.Lock()
    97  	defer acc.mu.Unlock()
    98  	return acc.origIn
    99  }
   100  
   101  // GetAsyncReader returns the current AsyncReader or nil if Account is unbuffered
   102  func (acc *Account) GetAsyncReader() *asyncreader.AsyncReader {
   103  	acc.mu.Lock()
   104  	defer acc.mu.Unlock()
   105  	if asyncIn, ok := acc.in.(*asyncreader.AsyncReader); ok {
   106  		return asyncIn
   107  	}
   108  	return nil
   109  }
   110  
   111  // StopBuffering stops the async buffer doing any more buffering
   112  func (acc *Account) StopBuffering() {
   113  	if asyncIn, ok := acc.in.(*asyncreader.AsyncReader); ok {
   114  		asyncIn.Abandon()
   115  	}
   116  }
   117  
   118  // UpdateReader updates the underlying io.ReadCloser stopping the
   119  // async buffer (if any) and re-adding it
   120  func (acc *Account) UpdateReader(in io.ReadCloser) {
   121  	acc.mu.Lock()
   122  	acc.StopBuffering()
   123  	acc.in = in
   124  	acc.close = in
   125  	acc.origIn = in
   126  	acc.WithBuffer()
   127  	acc.mu.Unlock()
   128  }
   129  
   130  // averageLoop calculates averages for the stats in the background
   131  func (acc *Account) averageLoop() {
   132  	tick := time.NewTicker(time.Second)
   133  	var period float64
   134  	defer tick.Stop()
   135  	for {
   136  		select {
   137  		case now := <-tick.C:
   138  			acc.statmu.Lock()
   139  			// Add average of last second.
   140  			elapsed := now.Sub(acc.lpTime).Seconds()
   141  			avg := float64(acc.lpBytes) / elapsed
   142  			// Soft start the moving average
   143  			if period < averagePeriod {
   144  				period++
   145  			}
   146  			acc.avg = (avg + (period-1)*acc.avg) / period
   147  			acc.lpBytes = 0
   148  			acc.lpTime = now
   149  			// Unlock stats
   150  			acc.statmu.Unlock()
   151  		case <-acc.exit:
   152  			return
   153  		}
   154  	}
   155  }
   156  
   157  // Check the read is valid
   158  func (acc *Account) checkRead() (err error) {
   159  	acc.statmu.Lock()
   160  	if acc.max >= 0 && Stats.GetBytes() >= acc.max {
   161  		acc.statmu.Unlock()
   162  		return ErrorMaxTransferLimitReached
   163  	}
   164  	// Set start time.
   165  	if acc.start.IsZero() {
   166  		acc.start = time.Now()
   167  	}
   168  	acc.statmu.Unlock()
   169  	return nil
   170  }
   171  
   172  // Account the read and limit bandwidth
   173  func (acc *Account) accountRead(n int) {
   174  	// Update Stats
   175  	acc.statmu.Lock()
   176  	acc.lpBytes += n
   177  	acc.bytes += int64(n)
   178  	acc.statmu.Unlock()
   179  
   180  	Stats.Bytes(int64(n))
   181  
   182  	limitBandwidth(n)
   183  }
   184  
   185  // read bytes from the io.Reader passed in and account them
   186  func (acc *Account) read(in io.Reader, p []byte) (n int, err error) {
   187  	err = acc.checkRead()
   188  	if err == nil {
   189  		n, err = in.Read(p)
   190  		acc.accountRead(n)
   191  	}
   192  	return n, err
   193  }
   194  
   195  // Read bytes from the object - see io.Reader
   196  func (acc *Account) Read(p []byte) (n int, err error) {
   197  	acc.mu.Lock()
   198  	defer acc.mu.Unlock()
   199  	return acc.read(acc.in, p)
   200  }
   201  
   202  // AccountRead account having read n bytes
   203  func (acc *Account) AccountRead(n int) (err error) {
   204  	acc.mu.Lock()
   205  	defer acc.mu.Unlock()
   206  	err = acc.checkRead()
   207  	if err == nil {
   208  		acc.accountRead(n)
   209  	}
   210  	return err
   211  }
   212  
   213  // Close the object
   214  func (acc *Account) Close() error {
   215  	acc.mu.Lock()
   216  	defer acc.mu.Unlock()
   217  	if acc.closed {
   218  		return nil
   219  	}
   220  	acc.closed = true
   221  	close(acc.exit)
   222  	Stats.inProgress.clear(acc.name)
   223  	if acc.close == nil {
   224  		return nil
   225  	}
   226  	return acc.close.Close()
   227  }
   228  
   229  // progress returns bytes read as well as the size.
   230  // Size can be <= 0 if the size is unknown.
   231  func (acc *Account) progress() (bytes, size int64) {
   232  	if acc == nil {
   233  		return 0, 0
   234  	}
   235  	acc.statmu.Lock()
   236  	bytes, size = acc.bytes, acc.size
   237  	acc.statmu.Unlock()
   238  	return bytes, size
   239  }
   240  
   241  // speed returns the speed of the current file transfer
   242  // in bytes per second, as well a an exponentially weighted moving average
   243  // If no read has completed yet, 0 is returned for both values.
   244  func (acc *Account) speed() (bps, current float64) {
   245  	if acc == nil {
   246  		return 0, 0
   247  	}
   248  	acc.statmu.Lock()
   249  	defer acc.statmu.Unlock()
   250  	if acc.bytes == 0 {
   251  		return 0, 0
   252  	}
   253  	// Calculate speed from first read.
   254  	total := float64(time.Now().Sub(acc.start)) / float64(time.Second)
   255  	bps = float64(acc.bytes) / total
   256  	current = acc.avg
   257  	return
   258  }
   259  
   260  // eta returns the ETA of the current operation,
   261  // rounded to full seconds.
   262  // If the ETA cannot be determined 'ok' returns false.
   263  func (acc *Account) eta() (etaDuration time.Duration, ok bool) {
   264  	if acc == nil {
   265  		return 0, false
   266  	}
   267  	acc.statmu.Lock()
   268  	defer acc.statmu.Unlock()
   269  	return eta(acc.bytes, acc.size, acc.avg)
   270  }
   271  
   272  // shortenName shortens in to size runes long
   273  // If size <= 0 then in is left untouched
   274  func shortenName(in string, size int) string {
   275  	if size <= 0 {
   276  		return in
   277  	}
   278  	if utf8.RuneCountInString(in) <= size {
   279  		return in
   280  	}
   281  	name := []rune(in)
   282  	size-- // don't count elipsis rune
   283  	suffixLength := size / 2
   284  	prefixLength := size - suffixLength
   285  	suffixStart := len(name) - suffixLength
   286  	name = append(append(name[:prefixLength], '…'), name[suffixStart:]...)
   287  	return string(name)
   288  }
   289  
   290  // String produces stats for this file
   291  func (acc *Account) String() string {
   292  	a, b := acc.progress()
   293  	_, cur := acc.speed()
   294  	eta, etaok := acc.eta()
   295  	etas := "-"
   296  	if etaok {
   297  		if eta > 0 {
   298  			etas = fmt.Sprintf("%v", eta)
   299  		} else {
   300  			etas = "0s"
   301  		}
   302  	}
   303  
   304  	if fs.Config.DataRateUnit == "bits" {
   305  		cur = cur * 8
   306  	}
   307  
   308  	percentageDone := 0
   309  	if b > 0 {
   310  		percentageDone = int(100 * float64(a) / float64(b))
   311  	}
   312  
   313  	return fmt.Sprintf("%*s:%3d%% /%s, %s/s, %s",
   314  		fs.Config.StatsFileNameLength,
   315  		shortenName(acc.name, fs.Config.StatsFileNameLength),
   316  		percentageDone,
   317  		fs.SizeSuffix(b),
   318  		fs.SizeSuffix(cur),
   319  		etas,
   320  	)
   321  }
   322  
   323  // RemoteStats produces stats for this file
   324  func (acc *Account) RemoteStats() (out map[string]interface{}) {
   325  	out = make(map[string]interface{})
   326  	a, b := acc.progress()
   327  	out["bytes"] = a
   328  	out["size"] = b
   329  	spd, cur := acc.speed()
   330  	out["speed"] = spd
   331  	out["speedAvg"] = cur
   332  
   333  	eta, etaok := acc.eta()
   334  	out["eta"] = nil
   335  	if etaok {
   336  		if eta > 0 {
   337  			out["eta"] = eta.Seconds()
   338  		} else {
   339  			out["eta"] = 0
   340  		}
   341  	}
   342  	out["name"] = acc.name
   343  
   344  	percentageDone := 0
   345  	if b > 0 {
   346  		percentageDone = int(100 * float64(a) / float64(b))
   347  	}
   348  	out["percentage"] = percentageDone
   349  
   350  	return out
   351  }
   352  
   353  // OldStream returns the top io.Reader
   354  func (acc *Account) OldStream() io.Reader {
   355  	acc.mu.Lock()
   356  	defer acc.mu.Unlock()
   357  	return acc.in
   358  }
   359  
   360  // SetStream updates the top io.Reader
   361  func (acc *Account) SetStream(in io.Reader) {
   362  	acc.mu.Lock()
   363  	acc.in = in
   364  	acc.mu.Unlock()
   365  }
   366  
   367  // WrapStream wraps an io Reader so it will be accounted in the same
   368  // way as account
   369  func (acc *Account) WrapStream(in io.Reader) io.Reader {
   370  	return &accountStream{
   371  		acc: acc,
   372  		in:  in,
   373  	}
   374  }
   375  
   376  // accountStream accounts a single io.Reader into a parent *Account
   377  type accountStream struct {
   378  	acc *Account
   379  	in  io.Reader
   380  }
   381  
   382  // OldStream return the underlying stream
   383  func (a *accountStream) OldStream() io.Reader {
   384  	return a.in
   385  }
   386  
   387  // SetStream set the underlying stream
   388  func (a *accountStream) SetStream(in io.Reader) {
   389  	a.in = in
   390  }
   391  
   392  // WrapStream wrap in in an accounter
   393  func (a *accountStream) WrapStream(in io.Reader) io.Reader {
   394  	return a.acc.WrapStream(in)
   395  }
   396  
   397  // Read bytes from the object - see io.Reader
   398  func (a *accountStream) Read(p []byte) (n int, err error) {
   399  	return a.acc.read(a.in, p)
   400  }
   401  
   402  // Accounter accounts a stream allowing the accounting to be removed and re-added
   403  type Accounter interface {
   404  	io.Reader
   405  	OldStream() io.Reader
   406  	SetStream(io.Reader)
   407  	WrapStream(io.Reader) io.Reader
   408  }
   409  
   410  // WrapFn wraps an io.Reader (for accounting purposes usually)
   411  type WrapFn func(io.Reader) io.Reader
   412  
   413  // UnWrap unwraps a reader returning unwrapped and wrap, a function to
   414  // wrap it back up again.  If `in` is an Accounter then this function
   415  // will take the accounting unwrapped and wrap will put it back on
   416  // again the new Reader passed in.
   417  //
   418  // This allows functions which wrap io.Readers to move the accounting
   419  // to the end of the wrapped chain of readers.  This is very important
   420  // if buffering is being introduced and if the Reader might be wrapped
   421  // again.
   422  func UnWrap(in io.Reader) (unwrapped io.Reader, wrap WrapFn) {
   423  	acc, ok := in.(Accounter)
   424  	if !ok {
   425  		return in, func(r io.Reader) io.Reader { return r }
   426  	}
   427  	return acc.OldStream(), acc.WrapStream
   428  }