github.com/atlassian/git-lob@v0.0.0-20150806085256-2386a5ed291a/util/progress.go (about) 1 package util 2 3 import ( 4 "bytes" 5 "fmt" 6 "time" 7 ) 8 9 type ProgressCallbackType int 10 11 const ( 12 // Process is figuring out what to do 13 ProgressCalculate ProgressCallbackType = iota 14 // Process is transferring data 15 ProgressTransferBytes ProgressCallbackType = iota 16 // Process is skipping data because it's already up to date 17 ProgressSkip ProgressCallbackType = iota 18 // Process did not find the requested data, moving on 19 ProgressNotFound ProgressCallbackType = iota 20 // Non-fatal error 21 ProgressError ProgressCallbackType = iota 22 ) 23 24 // Collected callback data for a progress operation 25 type ProgressCallbackData struct { 26 // What stage of the process this is for, preparing, transferring or skipping something 27 Type ProgressCallbackType 28 // Either a general message or an item name (e.g. file name in download stage) 29 Desc string 30 // If applicable, how many bytes transferred for this item 31 ItemBytesDone int64 32 // If applicable, how many bytes comprise this item 33 ItemBytes int64 34 // The number of bytes transferred for all items 35 TotalBytesDone int64 36 // The number of bytes needed to transfer all of this process 37 TotalBytes int64 38 } 39 40 // Summarised results of some progress action 41 type ProgressResults struct { 42 // Items transferred fully 43 TransferredCount int 44 // Items skipped (not needed) 45 SkippedCount int 46 // Items that failed (but did not stop process) 47 ErrorCount int 48 // Items which were not found in source 49 NotFoundCount int 50 } 51 52 // Callback when progress is made during process 53 // return true to abort the (entire) process 54 type ProgressCallback func(data *ProgressCallbackData) (abort bool) 55 56 // Function to periodically (based on freq) report progress of a transfer process to the console 57 // callbackChan must be a channel of updates which is being populated with ProgressCallbackData 58 // from a goroutine at an unknown frequency. This function will then print updates every freq seconds 59 // of the updates received so far, collapsing duplicates (in the case of very frequent transfer updates) 60 // and filling in the blanks with an updated transfer rate in the case of no updates in the time. 61 func ReportProgressToConsole(callbackChan <-chan *ProgressCallbackData, op string, freq time.Duration) *ProgressResults { 62 // Update the console once every half second regardless of how many callbacks 63 // (or zero callbacks, so we can reduce xfer rate) 64 tickChan := time.Tick(freq) 65 // samples of data transferred over the last 4 ticks (2s average) 66 transferRate := NewTransferRateCalculator(4) 67 68 var lastTotalBytesDone int64 69 var lastTime = time.Now() 70 var lastProgress *ProgressCallbackData 71 complete := false 72 lastConsoleLineLen := 0 73 results := &ProgressResults{} 74 for _ = range tickChan { 75 // We run this every 0.5s 76 var finalDownloadProgress *ProgressCallbackData 77 for stop := false; !stop && !complete; { 78 select { 79 case data := <-callbackChan: 80 if data == nil { 81 // channel was closed, we've finished 82 stop = true 83 complete = true 84 break 85 } 86 // Some progress data is available 87 // May get many of these and we only want to display the last one 88 // unless it's general infoo or we're in verbose mode 89 switch data.Type { 90 case ProgressCalculate: 91 finalDownloadProgress = nil 92 LogConsole(data.Desc) 93 case ProgressError: 94 finalDownloadProgress = nil 95 LogConsole(data.Desc) 96 case ProgressSkip: 97 finalDownloadProgress = nil 98 results.SkippedCount++ 99 // Only print if verbose 100 LogConsoleDebugf("Skipped: %v (Up to date)\n", data.Desc) 101 case ProgressNotFound: 102 finalDownloadProgress = nil 103 results.NotFoundCount++ 104 LogConsolef("Not found: %v (Continuing)\n", data.Desc) 105 case ProgressTransferBytes: 106 finalDownloadProgress = data 107 // Print completion in verbose mode 108 if data.ItemBytesDone == data.ItemBytes { 109 results.TransferredCount++ 110 if GlobalOptions.Verbose { 111 msg := fmt.Sprintf("%ved: %v 100%%", op, data.Desc) 112 LogConsoleOverwrite(msg, lastConsoleLineLen) 113 lastConsoleLineLen = len(msg) 114 // Clear line on completion in verbose mode 115 // Don't do this as \n in string above since we need to clear spaces after 116 LogConsole("") 117 finalDownloadProgress = nil 118 lastProgress = nil 119 } 120 } 121 } 122 default: 123 // No (more) progress data 124 stop = true 125 } 126 } 127 // Write progress data for this 0.5s if relevant 128 // If either we have new progress data, or unfinished progress data from previous 129 if finalDownloadProgress != nil || lastProgress != nil { 130 var bytesPerSecond int64 131 if finalDownloadProgress != nil && finalDownloadProgress.ItemBytes != 0 && finalDownloadProgress.TotalBytes != 0 { 132 lastProgress = finalDownloadProgress 133 bytesDoneThisTick := finalDownloadProgress.TotalBytesDone - lastTotalBytesDone 134 lastTotalBytesDone = finalDownloadProgress.TotalBytesDone 135 seconds := float32(time.Since(lastTime).Seconds()) 136 if seconds > 0 { 137 bytesPerSecond = int64(float32(bytesDoneThisTick) / seconds) 138 } 139 } else { 140 // Actually the default but lets be specific 141 bytesPerSecond = 0 142 } 143 // Calculate transfer rate 144 transferRate.AddSample(bytesPerSecond) 145 avgRate := transferRate.Average() 146 lastTime = time.Now() 147 148 if lastProgress.ItemBytes != 0 || lastProgress.TotalBytes != 0 { 149 buf := bytes.NewBufferString(fmt.Sprintf("%ving: ", op)) 150 if lastProgress.ItemBytes > 0 && GlobalOptions.Verbose { 151 itemPercent := int((100 * lastProgress.ItemBytesDone) / lastProgress.ItemBytes) 152 buf.WriteString(fmt.Sprintf("%v %d%%", lastProgress.Desc, itemPercent)) 153 if lastProgress.TotalBytes != 0 { 154 buf.WriteString(" Overall: ") 155 } 156 } 157 if lastProgress.TotalBytes > 0 && avgRate > 0 { 158 overallPercent := int((100 * lastProgress.TotalBytesDone) / lastProgress.TotalBytes) 159 bytesRemaining := lastProgress.TotalBytes - lastProgress.TotalBytesDone 160 secondsRemaining := bytesRemaining / avgRate 161 timeRemaining := time.Duration(secondsRemaining) * time.Second 162 163 buf.WriteString(fmt.Sprintf("%d%% (%v ETA %v)", overallPercent, FormatTransferRate(avgRate), timeRemaining)) 164 } 165 msg := buf.String() 166 LogConsoleOverwrite(msg, lastConsoleLineLen) 167 lastConsoleLineLen = len(msg) 168 } 169 } 170 171 if complete { 172 break 173 } 174 175 } 176 if !GlobalOptions.Verbose && lastConsoleLineLen > 0 { 177 // Write final line with newline 178 LogConsoleOverwrite(fmt.Sprintf("%ving: 100%%", op), lastConsoleLineLen) 179 LogConsole("") 180 } 181 return results 182 183 }