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

     1  package accounting
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/ncw/rclone/fs"
    12  	"github.com/ncw/rclone/fs/fserrors"
    13  	"github.com/ncw/rclone/fs/rc"
    14  )
    15  
    16  var (
    17  	// Stats is global statistics counter
    18  	Stats = NewStats()
    19  )
    20  
    21  func init() {
    22  	// Set the function pointer up in fs
    23  	fs.CountError = Stats.Error
    24  
    25  	rc.Add(rc.Call{
    26  		Path:  "core/stats",
    27  		Fn:    Stats.RemoteStats,
    28  		Title: "Returns stats about current transfers.",
    29  		Help: `
    30  This returns all available stats
    31  
    32  	rclone rc core/stats
    33  
    34  Returns the following values:
    35  
    36  ` + "```" + `
    37  {
    38  	"speed": average speed in bytes/sec since start of the process,
    39  	"bytes": total transferred bytes since the start of the process,
    40  	"errors": number of errors,
    41  	"fatalError": whether there has been at least one FatalError,
    42  	"retryError": whether there has been at least one non-NoRetryError,
    43  	"checks": number of checked files,
    44  	"transfers": number of transferred files,
    45  	"deletes" : number of deleted files,
    46  	"elapsedTime": time in seconds since the start of the process,
    47  	"lastError": last occurred error,
    48  	"transferring": an array of currently active file transfers:
    49  		[
    50  			{
    51  				"bytes": total transferred bytes for this file,
    52  				"eta": estimated time in seconds until file transfer completion
    53  				"name": name of the file,
    54  				"percentage": progress of the file transfer in percent,
    55  				"speed": speed in bytes/sec,
    56  				"speedAvg": speed in bytes/sec as an exponentially weighted moving average,
    57  				"size": size of the file in bytes
    58  			}
    59  		],
    60  	"checking": an array of names of currently active file checks
    61  		[]
    62  }
    63  ` + "```" + `
    64  Values for "transferring", "checking" and "lastError" are only assigned if data is available.
    65  The value for "eta" is null if an eta cannot be determined.
    66  `,
    67  	})
    68  }
    69  
    70  // StatsInfo accounts all transfers
    71  type StatsInfo struct {
    72  	mu                sync.RWMutex
    73  	bytes             int64
    74  	errors            int64
    75  	lastError         error
    76  	fatalError        bool
    77  	retryError        bool
    78  	retryAfter        time.Time
    79  	checks            int64
    80  	checking          *stringSet
    81  	checkQueue        int
    82  	checkQueueSize    int64
    83  	transfers         int64
    84  	transferring      *stringSet
    85  	transferQueue     int
    86  	transferQueueSize int64
    87  	renameQueue       int
    88  	renameQueueSize   int64
    89  	deletes           int64
    90  	start             time.Time
    91  	inProgress        *inProgress
    92  }
    93  
    94  // NewStats creates an initialised StatsInfo
    95  func NewStats() *StatsInfo {
    96  	return &StatsInfo{
    97  		checking:     newStringSet(fs.Config.Checkers, "checking"),
    98  		transferring: newStringSet(fs.Config.Transfers, "transferring"),
    99  		start:        time.Now(),
   100  		inProgress:   newInProgress(),
   101  	}
   102  }
   103  
   104  // RemoteStats returns stats for rc
   105  func (s *StatsInfo) RemoteStats(ctx context.Context, in rc.Params) (out rc.Params, err error) {
   106  	out = make(rc.Params)
   107  	s.mu.RLock()
   108  	dt := time.Now().Sub(s.start)
   109  	dtSeconds := dt.Seconds()
   110  	speed := 0.0
   111  	if dt > 0 {
   112  		speed = float64(s.bytes) / dtSeconds
   113  	}
   114  	out["speed"] = speed
   115  	out["bytes"] = s.bytes
   116  	out["errors"] = s.errors
   117  	out["fatalError"] = s.fatalError
   118  	out["retryError"] = s.retryError
   119  	out["checks"] = s.checks
   120  	out["transfers"] = s.transfers
   121  	out["deletes"] = s.deletes
   122  	out["elapsedTime"] = dtSeconds
   123  	s.mu.RUnlock()
   124  	if !s.checking.empty() {
   125  		var c []string
   126  		s.checking.mu.RLock()
   127  		defer s.checking.mu.RUnlock()
   128  		for name := range s.checking.items {
   129  			c = append(c, name)
   130  		}
   131  		out["checking"] = c
   132  	}
   133  	if !s.transferring.empty() {
   134  		var t []interface{}
   135  		s.transferring.mu.RLock()
   136  		defer s.transferring.mu.RUnlock()
   137  		for name := range s.transferring.items {
   138  			if acc := s.inProgress.get(name); acc != nil {
   139  				t = append(t, acc.RemoteStats())
   140  			} else {
   141  				t = append(t, name)
   142  			}
   143  		}
   144  		out["transferring"] = t
   145  	}
   146  	if s.errors > 0 {
   147  		out["lastError"] = s.lastError
   148  	}
   149  	return out, nil
   150  }
   151  
   152  // eta returns the ETA of the current operation,
   153  // rounded to full seconds.
   154  // If the ETA cannot be determined 'ok' returns false.
   155  func eta(size, total int64, rate float64) (eta time.Duration, ok bool) {
   156  	if total <= 0 || size < 0 || rate <= 0 {
   157  		return 0, false
   158  	}
   159  	remaining := total - size
   160  	if remaining < 0 {
   161  		return 0, false
   162  	}
   163  	seconds := float64(remaining) / rate
   164  	return time.Second * time.Duration(seconds), true
   165  }
   166  
   167  // etaString returns the ETA of the current operation,
   168  // rounded to full seconds.
   169  // If the ETA cannot be determined it returns "-"
   170  func etaString(done, total int64, rate float64) string {
   171  	d, ok := eta(done, total, rate)
   172  	if !ok {
   173  		return "-"
   174  	}
   175  	return fs.Duration(d).ReadableString()
   176  }
   177  
   178  // percent returns a/b as a percentage rounded to the nearest integer
   179  // as a string
   180  //
   181  // if the percentage is invalid it returns "-"
   182  func percent(a int64, b int64) string {
   183  	if a < 0 || b <= 0 {
   184  		return "-"
   185  	}
   186  	return fmt.Sprintf("%d%%", int(float64(a)*100/float64(b)+0.5))
   187  }
   188  
   189  // String convert the StatsInfo to a string for printing
   190  func (s *StatsInfo) String() string {
   191  	// checking and transferring have their own locking so read
   192  	// here before lock to prevent deadlock on GetBytes
   193  	transferring, checking := s.transferring.count(), s.checking.count()
   194  	transferringBytesDone, transferringBytesTotal := s.transferring.progress()
   195  
   196  	s.mu.RLock()
   197  
   198  	dt := time.Now().Sub(s.start)
   199  	dtSeconds := dt.Seconds()
   200  	speed := 0.0
   201  	if dt > 0 {
   202  		speed = float64(s.bytes) / dtSeconds
   203  	}
   204  	dtRounded := dt - (dt % (time.Second / 10))
   205  
   206  	displaySpeed := speed
   207  	if fs.Config.DataRateUnit == "bits" {
   208  		displaySpeed *= 8
   209  	}
   210  
   211  	var (
   212  		totalChecks   = int64(s.checkQueue) + s.checks + int64(checking)
   213  		totalTransfer = int64(s.transferQueue) + s.transfers + int64(transferring)
   214  		// note that s.bytes already includes transferringBytesDone so
   215  		// we take it off here to avoid double counting
   216  		totalSize    = s.transferQueueSize + s.bytes + transferringBytesTotal - transferringBytesDone
   217  		currentSize  = s.bytes
   218  		buf          = &bytes.Buffer{}
   219  		xfrchkString = ""
   220  		dateString   = ""
   221  	)
   222  
   223  	if !fs.Config.StatsOneLine {
   224  		_, _ = fmt.Fprintf(buf, "\nTransferred:   	")
   225  	} else {
   226  		xfrchk := []string{}
   227  		if totalTransfer > 0 && s.transferQueue > 0 {
   228  			xfrchk = append(xfrchk, fmt.Sprintf("xfr#%d/%d", s.transfers, totalTransfer))
   229  		}
   230  		if totalChecks > 0 && s.checkQueue > 0 {
   231  			xfrchk = append(xfrchk, fmt.Sprintf("chk#%d/%d", s.checks, totalChecks))
   232  		}
   233  		if len(xfrchk) > 0 {
   234  			xfrchkString = fmt.Sprintf(" (%s)", strings.Join(xfrchk, ", "))
   235  		}
   236  		if fs.Config.StatsOneLineDate {
   237  			t := time.Now()
   238  			dateString = t.Format(fs.Config.StatsOneLineDateFormat) // Including the separator so people can customize it
   239  		}
   240  	}
   241  
   242  	_, _ = fmt.Fprintf(buf, "%s%10s / %s, %s, %s, ETA %s%s",
   243  		dateString,
   244  		fs.SizeSuffix(s.bytes),
   245  		fs.SizeSuffix(totalSize).Unit("Bytes"),
   246  		percent(s.bytes, totalSize),
   247  		fs.SizeSuffix(displaySpeed).Unit(strings.Title(fs.Config.DataRateUnit)+"/s"),
   248  		etaString(currentSize, totalSize, speed),
   249  		xfrchkString,
   250  	)
   251  
   252  	if !fs.Config.StatsOneLine {
   253  		errorDetails := ""
   254  		switch {
   255  		case s.fatalError:
   256  			errorDetails = " (fatal error encountered)"
   257  		case s.retryError:
   258  			errorDetails = " (retrying may help)"
   259  		case s.errors != 0:
   260  			errorDetails = " (no need to retry)"
   261  		}
   262  
   263  		_, _ = fmt.Fprintf(buf, `
   264  Errors:        %10d%s
   265  Checks:        %10d / %d, %s
   266  Transferred:   %10d / %d, %s
   267  Elapsed time:  %10v
   268  `,
   269  			s.errors, errorDetails,
   270  			s.checks, totalChecks, percent(s.checks, totalChecks),
   271  			s.transfers, totalTransfer, percent(s.transfers, totalTransfer),
   272  			dtRounded)
   273  	}
   274  
   275  	// checking and transferring have their own locking so unlock
   276  	// here to prevent deadlock on GetBytes
   277  	s.mu.RUnlock()
   278  
   279  	// Add per transfer stats if required
   280  	if !fs.Config.StatsOneLine {
   281  		if !s.checking.empty() {
   282  			_, _ = fmt.Fprintf(buf, "Checking:\n%s\n", s.checking)
   283  		}
   284  		if !s.transferring.empty() {
   285  			_, _ = fmt.Fprintf(buf, "Transferring:\n%s\n", s.transferring)
   286  		}
   287  	}
   288  
   289  	return buf.String()
   290  }
   291  
   292  // Log outputs the StatsInfo to the log
   293  func (s *StatsInfo) Log() {
   294  	fs.LogLevelPrintf(fs.Config.StatsLogLevel, nil, "%v\n", s)
   295  }
   296  
   297  // Bytes updates the stats for bytes bytes
   298  func (s *StatsInfo) Bytes(bytes int64) {
   299  	s.mu.Lock()
   300  	defer s.mu.Unlock()
   301  	s.bytes += bytes
   302  }
   303  
   304  // GetBytes returns the number of bytes transferred so far
   305  func (s *StatsInfo) GetBytes() int64 {
   306  	s.mu.RLock()
   307  	defer s.mu.RUnlock()
   308  	return s.bytes
   309  }
   310  
   311  // Errors updates the stats for errors
   312  func (s *StatsInfo) Errors(errors int64) {
   313  	s.mu.Lock()
   314  	defer s.mu.Unlock()
   315  	s.errors += errors
   316  }
   317  
   318  // GetErrors reads the number of errors
   319  func (s *StatsInfo) GetErrors() int64 {
   320  	s.mu.RLock()
   321  	defer s.mu.RUnlock()
   322  	return s.errors
   323  }
   324  
   325  // GetLastError returns the lastError
   326  func (s *StatsInfo) GetLastError() error {
   327  	s.mu.RLock()
   328  	defer s.mu.RUnlock()
   329  	return s.lastError
   330  }
   331  
   332  // GetChecks returns the number of checks
   333  func (s *StatsInfo) GetChecks() int64 {
   334  	s.mu.RLock()
   335  	defer s.mu.RUnlock()
   336  	return s.checks
   337  }
   338  
   339  // FatalError sets the fatalError flag
   340  func (s *StatsInfo) FatalError() {
   341  	s.mu.Lock()
   342  	defer s.mu.Unlock()
   343  	s.fatalError = true
   344  }
   345  
   346  // HadFatalError returns whether there has been at least one FatalError
   347  func (s *StatsInfo) HadFatalError() bool {
   348  	s.mu.RLock()
   349  	defer s.mu.RUnlock()
   350  	return s.fatalError
   351  }
   352  
   353  // RetryError sets the retryError flag
   354  func (s *StatsInfo) RetryError() {
   355  	s.mu.Lock()
   356  	defer s.mu.Unlock()
   357  	s.retryError = true
   358  }
   359  
   360  // HadRetryError returns whether there has been at least one non-NoRetryError
   361  func (s *StatsInfo) HadRetryError() bool {
   362  	s.mu.RLock()
   363  	defer s.mu.RUnlock()
   364  	return s.retryError
   365  }
   366  
   367  // Deletes updates the stats for deletes
   368  func (s *StatsInfo) Deletes(deletes int64) int64 {
   369  	s.mu.Lock()
   370  	defer s.mu.Unlock()
   371  	s.deletes += deletes
   372  	return s.deletes
   373  }
   374  
   375  // ResetCounters sets the counters (bytes, checks, errors, transfers, deletes) to 0 and resets lastError, fatalError and retryError
   376  func (s *StatsInfo) ResetCounters() {
   377  	s.mu.Lock()
   378  	defer s.mu.Unlock()
   379  	s.bytes = 0
   380  	s.errors = 0
   381  	s.lastError = nil
   382  	s.fatalError = false
   383  	s.retryError = false
   384  	s.retryAfter = time.Time{}
   385  	s.checks = 0
   386  	s.transfers = 0
   387  	s.deletes = 0
   388  }
   389  
   390  // ResetErrors sets the errors count to 0 and resets lastError, fatalError and retryError
   391  func (s *StatsInfo) ResetErrors() {
   392  	s.mu.Lock()
   393  	defer s.mu.Unlock()
   394  	s.errors = 0
   395  	s.lastError = nil
   396  	s.fatalError = false
   397  	s.retryError = false
   398  	s.retryAfter = time.Time{}
   399  }
   400  
   401  // Errored returns whether there have been any errors
   402  func (s *StatsInfo) Errored() bool {
   403  	s.mu.RLock()
   404  	defer s.mu.RUnlock()
   405  	return s.errors != 0
   406  }
   407  
   408  // Error adds a single error into the stats, assigns lastError and eventually sets fatalError or retryError
   409  func (s *StatsInfo) Error(err error) {
   410  	if err == nil {
   411  		return
   412  	}
   413  	s.mu.Lock()
   414  	defer s.mu.Unlock()
   415  	s.errors++
   416  	s.lastError = err
   417  	switch {
   418  	case fserrors.IsFatalError(err):
   419  		s.fatalError = true
   420  	case fserrors.IsRetryAfterError(err):
   421  		retryAfter := fserrors.RetryAfterErrorTime(err)
   422  		if s.retryAfter.IsZero() || retryAfter.Sub(s.retryAfter) > 0 {
   423  			s.retryAfter = retryAfter
   424  		}
   425  		s.retryError = true
   426  	case !fserrors.IsNoRetryError(err):
   427  		s.retryError = true
   428  	}
   429  }
   430  
   431  // RetryAfter returns the time to retry after if it is set.  It will
   432  // be Zero if it isn't set.
   433  func (s *StatsInfo) RetryAfter() time.Time {
   434  	s.mu.Lock()
   435  	defer s.mu.Unlock()
   436  	return s.retryAfter
   437  }
   438  
   439  // Checking adds a check into the stats
   440  func (s *StatsInfo) Checking(remote string) {
   441  	s.checking.add(remote)
   442  }
   443  
   444  // DoneChecking removes a check from the stats
   445  func (s *StatsInfo) DoneChecking(remote string) {
   446  	s.checking.del(remote)
   447  	s.mu.Lock()
   448  	s.checks++
   449  	s.mu.Unlock()
   450  }
   451  
   452  // GetTransfers reads the number of transfers
   453  func (s *StatsInfo) GetTransfers() int64 {
   454  	s.mu.RLock()
   455  	defer s.mu.RUnlock()
   456  	return s.transfers
   457  }
   458  
   459  // Transferring adds a transfer into the stats
   460  func (s *StatsInfo) Transferring(remote string) {
   461  	s.transferring.add(remote)
   462  }
   463  
   464  // DoneTransferring removes a transfer from the stats
   465  //
   466  // if ok is true then it increments the transfers count
   467  func (s *StatsInfo) DoneTransferring(remote string, ok bool) {
   468  	s.transferring.del(remote)
   469  	if ok {
   470  		s.mu.Lock()
   471  		s.transfers++
   472  		s.mu.Unlock()
   473  	}
   474  }
   475  
   476  // SetCheckQueue sets the number of queued checks
   477  func (s *StatsInfo) SetCheckQueue(n int, size int64) {
   478  	s.mu.Lock()
   479  	s.checkQueue = n
   480  	s.checkQueueSize = size
   481  	s.mu.Unlock()
   482  }
   483  
   484  // SetTransferQueue sets the number of queued transfers
   485  func (s *StatsInfo) SetTransferQueue(n int, size int64) {
   486  	s.mu.Lock()
   487  	s.transferQueue = n
   488  	s.transferQueueSize = size
   489  	s.mu.Unlock()
   490  }
   491  
   492  // SetRenameQueue sets the number of queued transfers
   493  func (s *StatsInfo) SetRenameQueue(n int, size int64) {
   494  	s.mu.Lock()
   495  	s.renameQueue = n
   496  	s.renameQueueSize = size
   497  	s.mu.Unlock()
   498  }