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 }