github.com/jfrog/jfrog-cli-core/v2@v2.51.0/artifactory/commands/transferfiles/state/timeestimation.go (about) 1 package state 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/transferfiles/api" 8 9 "github.com/jfrog/jfrog-client-go/artifactory/services/utils" 10 "github.com/jfrog/jfrog-client-go/utils/log" 11 ) 12 13 const ( 14 milliSecsInSecond = 1000 15 bytesPerMilliSecToMBPerSec = float64(milliSecsInSecond) / float64(utils.SizeMiB) 16 minTransferTimeToShowEstimation = time.Minute * 5 17 ) 18 19 type timeTypeSingular string 20 21 const ( 22 day timeTypeSingular = "day" 23 hour timeTypeSingular = "hour" 24 minute timeTypeSingular = "minute" 25 ) 26 27 var numOfSpeedsToKeepPerWorkingThread = 10 28 29 type TimeEstimationManager struct { 30 // Speeds of the last done chunks, in bytes/ms 31 LastSpeeds []float64 `json:"last_speeds,omitempty"` 32 // Sum of the speeds in LastSpeeds. The speeds are in bytes/ms. 33 LastSpeedsSum float64 `json:"last_speeds_sum,omitempty"` 34 // The last calculated sum of speeds, in bytes/ms 35 SpeedsAverage float64 `json:"speeds_average,omitempty"` 36 // Total transferred bytes since the beginning of the current transfer execution 37 CurrentTotalTransferredBytes uint64 `json:"current_total_transferred_bytes,omitempty"` 38 // The state manager 39 stateManager *TransferStateManager 40 } 41 42 func (tem *TimeEstimationManager) AddChunkStatus(chunkStatus api.ChunkStatus, durationMillis int64) { 43 if durationMillis == 0 { 44 return 45 } 46 47 tem.addDataChunkStatus(chunkStatus, durationMillis) 48 } 49 50 func (tem *TimeEstimationManager) addDataChunkStatus(chunkStatus api.ChunkStatus, durationMillis int64) { 51 var chunkSizeBytes int64 52 for _, file := range chunkStatus.Files { 53 if file.Status != api.Fail { 54 tem.CurrentTotalTransferredBytes += uint64(file.SizeBytes) 55 } 56 if (file.Status == api.Success || file.Status == api.SkippedLargeProps) && !file.ChecksumDeployed { 57 chunkSizeBytes += file.SizeBytes 58 } 59 } 60 61 // If no files were uploaded regularly (with no errors and not checksum-deployed), don't use this chunk for the time estimation calculation. 62 if chunkSizeBytes == 0 { 63 return 64 } 65 66 workingThreads, err := tem.stateManager.GetWorkingThreads() 67 if err != nil { 68 log.Error("Couldn't calculate time estimation:", err.Error()) 69 return 70 } 71 speed := calculateChunkSpeed(workingThreads, chunkSizeBytes, durationMillis) 72 tem.LastSpeeds = append(tem.LastSpeeds, speed) 73 tem.LastSpeedsSum += speed 74 lastSpeedsSliceLen := workingThreads * numOfSpeedsToKeepPerWorkingThread 75 for len(tem.LastSpeeds) > lastSpeedsSliceLen { 76 // Remove the oldest calculated speed 77 tem.LastSpeedsSum -= tem.LastSpeeds[0] 78 tem.LastSpeeds = tem.LastSpeeds[1:] 79 } 80 if len(tem.LastSpeeds) == 0 { 81 tem.SpeedsAverage = 0 82 return 83 } 84 // Calculate speed in bytes/ms 85 tem.SpeedsAverage = tem.LastSpeedsSum / float64(len(tem.LastSpeeds)) 86 } 87 88 func calculateChunkSpeed(workingThreads int, chunkSizeSum, chunkDuration int64) float64 { 89 return float64(workingThreads) * float64(chunkSizeSum) / float64(chunkDuration) 90 } 91 92 // getSpeed gets the transfer speed, in MB/s. 93 func (tem *TimeEstimationManager) getSpeed() float64 { 94 // Convert from bytes/ms to MB/s 95 return tem.SpeedsAverage * bytesPerMilliSecToMBPerSec 96 } 97 98 // GetSpeedString gets the transfer speed as an easy-to-read string. 99 func (tem *TimeEstimationManager) GetSpeedString() string { 100 if len(tem.LastSpeeds) == 0 { 101 return "Not available yet" 102 } 103 return fmt.Sprintf("%.3f MB/s", tem.getSpeed()) 104 } 105 106 // GetEstimatedRemainingTimeString gets the estimated remaining time as an easy-to-read string. 107 // Return "Not available yet" in the following cases: 108 // 1. 5 minutes not passed since the beginning of the transfer 109 // 2. No files transferred 110 // 3. The transfer speed is less than 1 byte per second 111 func (tem *TimeEstimationManager) GetEstimatedRemainingTimeString() string { 112 remainingTimeSec := tem.getEstimatedRemainingSeconds() 113 if remainingTimeSec == 0 { 114 return "Not available yet" 115 } 116 117 return SecondsToLiteralTime(int64(remainingTimeSec), "About ") 118 } 119 120 func (tem *TimeEstimationManager) getEstimatedRemainingSeconds() uint64 { 121 if tem.CurrentTotalTransferredBytes == 0 { 122 // No files transferred 123 return 0 124 } 125 duration := time.Since(tem.stateManager.startTimestamp) 126 if duration < minTransferTimeToShowEstimation { 127 // 5 minutes not yet passed 128 return 0 129 } 130 131 transferredBytesInSeconds := tem.CurrentTotalTransferredBytes / uint64(duration.Seconds()) 132 if transferredBytesInSeconds == 0 { 133 // Less than 1 byte per second 134 return 0 135 } 136 remainingBytes := tem.stateManager.OverallTransfer.TotalSizeBytes - tem.stateManager.OverallTransfer.TransferredSizeBytes 137 return uint64(remainingBytes) / transferredBytesInSeconds 138 }