rc: add a full set of stats to core/stats

This patch adds the missing stats to the output of core/stats

- totalChecks
- totalTransfers
- totalBytes
- eta

This now includes enough information to rebuild the normal stats
output from rclone including percentage completions and ETAs.

Fixes #5116
This commit is contained in:
Nick Craig-Wood 2021-03-15 15:50:04 +00:00
parent b47d6001a9
commit 268a7ff7b8
4 changed files with 96 additions and 49 deletions

View File

@ -527,14 +527,11 @@ func (acc *Account) rcStats() (out rc.Params) {
out["speed"] = spd out["speed"] = spd
out["speedAvg"] = cur out["speedAvg"] = cur
eta, etaok := acc.eta() eta, etaOK := acc.eta()
out["eta"] = nil if etaOK {
if etaok { out["eta"] = eta.Seconds()
if eta > 0 { } else {
out["eta"] = eta.Seconds() out["eta"] = nil
} else {
out["eta"] = 0
}
} }
out["name"] = acc.name out["name"] = acc.name

View File

@ -90,7 +90,7 @@ func (c *RcloneCollector) Collect(ch chan<- prometheus.Metric) {
s.mu.RLock() s.mu.RLock()
ch <- prometheus.MustNewConstMetric(c.bytesTransferred, prometheus.CounterValue, float64(s.bytes)) ch <- prometheus.MustNewConstMetric(c.bytesTransferred, prometheus.CounterValue, float64(s.bytes))
ch <- prometheus.MustNewConstMetric(c.transferSpeed, prometheus.GaugeValue, s.Speed()) ch <- prometheus.MustNewConstMetric(c.transferSpeed, prometheus.GaugeValue, s.speed())
ch <- prometheus.MustNewConstMetric(c.numOfErrors, prometheus.CounterValue, float64(s.errors)) ch <- prometheus.MustNewConstMetric(c.numOfErrors, prometheus.CounterValue, float64(s.errors))
ch <- prometheus.MustNewConstMetric(c.numOfCheckFiles, prometheus.CounterValue, float64(s.checks)) ch <- prometheus.MustNewConstMetric(c.numOfCheckFiles, prometheus.CounterValue, float64(s.checks))
ch <- prometheus.MustNewConstMetric(c.transferredFiles, prometheus.CounterValue, float64(s.transfers)) ch <- prometheus.MustNewConstMetric(c.transferredFiles, prometheus.CounterValue, float64(s.transfers))

View File

@ -65,9 +65,19 @@ func NewStats(ctx context.Context) *StatsInfo {
// RemoteStats returns stats for rc // RemoteStats returns stats for rc
func (s *StatsInfo) RemoteStats() (out rc.Params, err error) { func (s *StatsInfo) RemoteStats() (out rc.Params, err error) {
// NB if adding values here - make sure you update the docs in
// stats_groups.go
out = make(rc.Params) out = make(rc.Params)
ts := s.calculateTransferStats()
out["totalChecks"] = ts.totalChecks
out["totalTransfers"] = ts.totalTransfers
out["totalBytes"] = ts.totalBytes
out["transferTime"] = ts.transferTime
out["speed"] = ts.speed
s.mu.RLock() s.mu.RLock()
out["speed"] = s.Speed()
out["bytes"] = s.bytes out["bytes"] = s.bytes
out["errors"] = s.errors out["errors"] = s.errors
out["fatalError"] = s.fatalError out["fatalError"] = s.fatalError
@ -77,9 +87,15 @@ func (s *StatsInfo) RemoteStats() (out rc.Params, err error) {
out["deletes"] = s.deletes out["deletes"] = s.deletes
out["deletedDirs"] = s.deletedDirs out["deletedDirs"] = s.deletedDirs
out["renames"] = s.renames out["renames"] = s.renames
out["transferTime"] = s.totalDuration().Seconds()
out["elapsedTime"] = time.Since(startTime).Seconds() out["elapsedTime"] = time.Since(startTime).Seconds()
eta, etaOK := eta(s.bytes, ts.totalBytes, ts.speed)
if etaOK {
out["eta"] = eta.Seconds()
} else {
out["eta"] = nil
}
s.mu.RUnlock() s.mu.RUnlock()
if !s.checking.empty() { if !s.checking.empty() {
out["checking"] = s.checking.remotes() out["checking"] = s.checking.remotes()
} }
@ -89,11 +105,14 @@ func (s *StatsInfo) RemoteStats() (out rc.Params, err error) {
if s.errors > 0 { if s.errors > 0 {
out["lastError"] = s.lastError.Error() out["lastError"] = s.lastError.Error()
} }
return out, nil return out, nil
} }
// Speed returns the average speed of the transfer in bytes/second // Speed returns the average speed of the transfer in bytes/second
func (s *StatsInfo) Speed() float64 { //
// Call with lock held
func (s *StatsInfo) speed() float64 {
dt := s.totalDuration() dt := s.totalDuration()
dtSeconds := dt.Seconds() dtSeconds := dt.Seconds()
speed := 0.0 speed := 0.0
@ -202,6 +221,9 @@ func eta(size, total int64, rate float64) (eta time.Duration, ok bool) {
return 0, false return 0, false
} }
seconds := float64(remaining) / rate seconds := float64(remaining) / rate
if seconds < 0 {
seconds = 0
}
return time.Second * time.Duration(seconds), true return time.Second * time.Duration(seconds), true
} }
@ -227,36 +249,60 @@ func percent(a int64, b int64) string {
return fmt.Sprintf("%d%%", int(float64(a)*100/float64(b)+0.5)) return fmt.Sprintf("%d%%", int(float64(a)*100/float64(b)+0.5))
} }
// String convert the StatsInfo to a string for printing // returned from calculateTransferStats
func (s *StatsInfo) String() string { type transferStats struct {
totalChecks int64
totalTransfers int64
totalBytes int64
transferTime float64
speed float64
}
// calculateTransferStats calculates some addtional transfer stats not
// stored directly in StatsInfo
func (s *StatsInfo) calculateTransferStats() (ts transferStats) {
// checking and transferring have their own locking so read // checking and transferring have their own locking so read
// here before lock to prevent deadlock on GetBytes // here before lock to prevent deadlock on GetBytes
transferring, checking := s.transferring.count(), s.checking.count() transferring, checking := s.transferring.count(), s.checking.count()
transferringBytesDone, transferringBytesTotal := s.transferring.progress(s) transferringBytesDone, transferringBytesTotal := s.transferring.progress(s)
s.mu.RLock()
defer s.mu.RUnlock()
ts.totalChecks = int64(s.checkQueue) + s.checks + int64(checking)
ts.totalTransfers = int64(s.transferQueue) + s.transfers + int64(transferring)
// note that s.bytes already includes transferringBytesDone so
// we take it off here to avoid double counting
ts.totalBytes = s.transferQueueSize + s.bytes + transferringBytesTotal - transferringBytesDone
dt := s.totalDuration()
ts.transferTime = dt.Seconds()
ts.speed = 0.0
if dt > 0 {
ts.speed = float64(s.bytes) / ts.transferTime
}
return ts
}
// String convert the StatsInfo to a string for printing
func (s *StatsInfo) String() string {
// NB if adding more stats in here, remember to add them into
// RemoteStats() too.
ts := s.calculateTransferStats()
s.mu.RLock() s.mu.RLock()
elapsedTime := time.Since(startTime) elapsedTime := time.Since(startTime)
elapsedTimeSecondsOnly := elapsedTime.Truncate(time.Second/10) % time.Minute elapsedTimeSecondsOnly := elapsedTime.Truncate(time.Second/10) % time.Minute
dt := s.totalDuration()
dtSeconds := dt.Seconds()
speed := 0.0
if dt > 0 {
speed = float64(s.bytes) / dtSeconds
}
displaySpeed := speed displaySpeed := ts.speed
if s.ci.DataRateUnit == "bits" { if s.ci.DataRateUnit == "bits" {
displaySpeed *= 8 displaySpeed *= 8
} }
var ( var (
totalChecks = int64(s.checkQueue) + s.checks + int64(checking)
totalTransfer = int64(s.transferQueue) + s.transfers + int64(transferring)
// note that s.bytes already includes transferringBytesDone so
// we take it off here to avoid double counting
totalSize = s.transferQueueSize + s.bytes + transferringBytesTotal - transferringBytesDone
currentSize = s.bytes
buf = &bytes.Buffer{} buf = &bytes.Buffer{}
xfrchkString = "" xfrchkString = ""
dateString = "" dateString = ""
@ -266,11 +312,11 @@ func (s *StatsInfo) String() string {
_, _ = fmt.Fprintf(buf, "\nTransferred: ") _, _ = fmt.Fprintf(buf, "\nTransferred: ")
} else { } else {
xfrchk := []string{} xfrchk := []string{}
if totalTransfer > 0 && s.transferQueue > 0 { if ts.totalTransfers > 0 && s.transferQueue > 0 {
xfrchk = append(xfrchk, fmt.Sprintf("xfr#%d/%d", s.transfers, totalTransfer)) xfrchk = append(xfrchk, fmt.Sprintf("xfr#%d/%d", s.transfers, ts.totalTransfers))
} }
if totalChecks > 0 && s.checkQueue > 0 { if ts.totalChecks > 0 && s.checkQueue > 0 {
xfrchk = append(xfrchk, fmt.Sprintf("chk#%d/%d", s.checks, totalChecks)) xfrchk = append(xfrchk, fmt.Sprintf("chk#%d/%d", s.checks, ts.totalChecks))
} }
if len(xfrchk) > 0 { if len(xfrchk) > 0 {
xfrchkString = fmt.Sprintf(" (%s)", strings.Join(xfrchk, ", ")) xfrchkString = fmt.Sprintf(" (%s)", strings.Join(xfrchk, ", "))
@ -284,16 +330,16 @@ func (s *StatsInfo) String() string {
_, _ = fmt.Fprintf(buf, "%s%10s / %s, %s, %s, ETA %s%s", _, _ = fmt.Fprintf(buf, "%s%10s / %s, %s, %s, ETA %s%s",
dateString, dateString,
fs.SizeSuffix(s.bytes), fs.SizeSuffix(s.bytes),
fs.SizeSuffix(totalSize).Unit("Bytes"), fs.SizeSuffix(ts.totalBytes).Unit("Bytes"),
percent(s.bytes, totalSize), percent(s.bytes, ts.totalBytes),
fs.SizeSuffix(displaySpeed).Unit(strings.Title(s.ci.DataRateUnit)+"/s"), fs.SizeSuffix(displaySpeed).Unit(strings.Title(s.ci.DataRateUnit)+"/s"),
etaString(currentSize, totalSize, speed), etaString(s.bytes, ts.totalBytes, ts.speed),
xfrchkString, xfrchkString,
) )
if s.ci.ProgressTerminalTitle { if s.ci.ProgressTerminalTitle {
// Writes ETA to the terminal title // Writes ETA to the terminal title
terminal.WriteTerminalTitle("ETA: " + etaString(currentSize, totalSize, speed)) terminal.WriteTerminalTitle("ETA: " + etaString(s.bytes, ts.totalBytes, ts.speed))
} }
if !s.ci.StatsOneLine { if !s.ci.StatsOneLine {
@ -314,9 +360,9 @@ func (s *StatsInfo) String() string {
_, _ = fmt.Fprintf(buf, "Errors: %10d%s\n", _, _ = fmt.Fprintf(buf, "Errors: %10d%s\n",
s.errors, errorDetails) s.errors, errorDetails)
} }
if s.checks != 0 || totalChecks != 0 { if s.checks != 0 || ts.totalChecks != 0 {
_, _ = fmt.Fprintf(buf, "Checks: %10d / %d, %s\n", _, _ = fmt.Fprintf(buf, "Checks: %10d / %d, %s\n",
s.checks, totalChecks, percent(s.checks, totalChecks)) s.checks, ts.totalChecks, percent(s.checks, ts.totalChecks))
} }
if s.deletes != 0 || s.deletedDirs != 0 { if s.deletes != 0 || s.deletedDirs != 0 {
_, _ = fmt.Fprintf(buf, "Deleted: %10d (files), %d (dirs)\n", s.deletes, s.deletedDirs) _, _ = fmt.Fprintf(buf, "Deleted: %10d (files), %d (dirs)\n", s.deletes, s.deletedDirs)
@ -324,9 +370,9 @@ func (s *StatsInfo) String() string {
if s.renames != 0 { if s.renames != 0 {
_, _ = fmt.Fprintf(buf, "Renamed: %10d\n", s.renames) _, _ = fmt.Fprintf(buf, "Renamed: %10d\n", s.renames)
} }
if s.transfers != 0 || totalTransfer != 0 { if s.transfers != 0 || ts.totalTransfers != 0 {
_, _ = fmt.Fprintf(buf, "Transferred: %10d / %d, %s\n", _, _ = fmt.Fprintf(buf, "Transferred: %10d / %d, %s\n",
s.transfers, totalTransfer, percent(s.transfers, totalTransfer)) s.transfers, ts.totalTransfers, percent(s.transfers, ts.totalTransfers))
} }
_, _ = fmt.Fprintf(buf, "Elapsed time: %10ss\n", strings.TrimRight(elapsedTime.Truncate(time.Minute).String(), "0s")+fmt.Sprintf("%.1f", elapsedTimeSecondsOnly.Seconds())) _, _ = fmt.Fprintf(buf, "Elapsed time: %10ss\n", strings.TrimRight(elapsedTime.Truncate(time.Minute).String(), "0s")+fmt.Sprintf("%.1f", elapsedTimeSecondsOnly.Seconds()))
} }

View File

@ -86,18 +86,22 @@ Returns the following values:
` + "```" + ` ` + "```" + `
{ {
"speed": average speed in bytes/sec since start of the process, "bytes": total transferred bytes since the start of the group,
"bytes": total transferred bytes since the start of the process, "checks": number of files checked,
"deletes" : number of files deleted,
"elapsedTime": time in floating point seconds since rclone was started,
"errors": number of errors, "errors": number of errors,
"fatalError": whether there has been at least one FatalError, "eta": estimated time in seconds until the group completes,
"retryError": whether there has been at least one non-NoRetryError, "fatalError": boolean whether there has been at least one fatal error,
"checks": number of checked files, "lastError": last error string,
"transfers": number of transferred files, "renames" : number of files renamed,
"deletes" : number of deleted files, "retryError": boolean showing whether there has been at least one non-NoRetryError,
"renames" : number of renamed files, "speed": average speed in bytes/sec since start of the group,
"totalBytes": total number of bytes in the group,
"totalChecks": total number of checks in the group,
"totalTransfers": total number of transfers in the group,
"transferTime" : total time spent on running jobs, "transferTime" : total time spent on running jobs,
"elapsedTime": time in seconds since the start of the process, "transfers": number of transferred files,
"lastError": last occurred error,
"transferring": an array of currently active file transfers: "transferring": an array of currently active file transfers:
[ [
{ {