github.com/Microsoft/azure-vhd-utils@v0.0.0-20230613175315-7c30a3748a1b/upload/progress/status.go (about) 1 package progress 2 3 import ( 4 "time" 5 ) 6 7 // Status can be used by a collection of workers (reporters) to report the amount of work done when they need, 8 // Status compute the overall progress at regular interval and report it. 9 // 10 type Status struct { 11 bytesProcessedCountChan chan int64 12 doneChan chan bool 13 bytesProcessed int64 14 totalBytes int64 15 alreadyProcessedBytes int64 16 startTime time.Time 17 throughputStats *ComputeStats 18 } 19 20 // Record type is used by the ProgressStatus to report the progress at regular interval. 21 // 22 type Record struct { 23 PercentComplete float64 24 AverageThroughputMbPerSecond float64 25 RemainingDuration time.Duration 26 BytesProcessed int64 27 } 28 29 // oneMB is one MegaByte 30 // 31 const oneMB = float64(1048576) 32 33 // nanosecondsInOneSecond is 1 second expressed as nano-second unit 34 // 35 const nanosecondsInOneSecond = 1000 * 1000 * 1000 36 37 // NewStatus creates a new instance of Status. reporterCount is the number of concurrent goroutines that want to 38 // report processed bytes count, alreadyProcessedBytes is the bytes already processed if any, the parameter 39 // totalBytes is the total number of bytes that the reports will be process eventually, the parameter computeStats 40 // is used to calculate the running average. 41 // 42 func NewStatus(reportersCount int, alreadyProcessedBytes, totalBytes int64, computeStats *ComputeStats) *Status { 43 return &Status{ 44 bytesProcessedCountChan: make(chan int64, reportersCount), 45 doneChan: make(chan bool, 0), 46 totalBytes: totalBytes, 47 alreadyProcessedBytes: alreadyProcessedBytes, 48 startTime: time.Now(), 49 throughputStats: computeStats, 50 } 51 } 52 53 // ReportBytesProcessedCount method is used to report the number of bytes processed. 54 // 55 func (s *Status) ReportBytesProcessedCount(count int64) { 56 s.bytesProcessedCountChan <- count 57 } 58 59 // Run starts counting the reported processed bytes count and compute the progress, this method returns a channel, 60 // the computed progress will be send to this channel in regular interval. Once done with using ProgressStatus 61 // instance, you must call Dispose method otherwise there will be go routine leak. 62 // 63 func (s *Status) Run() <-chan *Record { 64 go s.bytesProcessedCountReceiver() 65 var outChan = make(chan *Record, 0) 66 go s.progressRecordSender(outChan) 67 return outChan 68 } 69 70 // Close disposes this ProgressStatus instance, an attempt to invoke ReportBytesProcessedCount method on a closed 71 // instance will be panic. Close also stops sending progress to the channel returned by Run method. Not calling 72 // Close will cause goroutine leak. 73 // 74 func (s *Status) Close() { 75 close(s.bytesProcessedCountChan) 76 } 77 78 // bytesProcessedCountReceiver read the channel containing the collection of reported bytes count and update the total 79 // bytes processed. This method signal doneChan when there is no more data to read. 80 // 81 func (s *Status) bytesProcessedCountReceiver() { 82 for c := range s.bytesProcessedCountChan { 83 s.bytesProcessed += c 84 } 85 s.doneChan <- true 86 } 87 88 // progressRecordSender compute the progress information at regular interval and send it to channel outChan which is 89 // returned by the Run method 90 // 91 func (s *Status) progressRecordSender(outChan chan<- *Record) { 92 progressRecord := &Record{} 93 tickerChan := time.NewTicker(500 * time.Millisecond) 94 Loop: 95 for { 96 select { 97 case <-tickerChan.C: 98 computeAvg := s.throughputStats.ComputeAvg(s.throughputMBs()) 99 avtThroughputMbps := 8.0 * computeAvg 100 remainingSeconds := (s.remainingMB() / computeAvg) 101 102 progressRecord.PercentComplete = s.percentComplete() 103 progressRecord.RemainingDuration = time.Duration(nanosecondsInOneSecond * remainingSeconds) 104 progressRecord.AverageThroughputMbPerSecond = avtThroughputMbps 105 progressRecord.BytesProcessed = s.bytesProcessed 106 107 outChan <- progressRecord 108 case <-s.doneChan: 109 tickerChan.Stop() 110 break Loop 111 } 112 } 113 close(outChan) 114 } 115 116 // remainingMB returns remaining bytes to be processed as MB. 117 // 118 func (s *Status) remainingMB() float64 { 119 return float64(s.totalBytes-s.bytesProcessed) / oneMB 120 } 121 122 // percentComplete returns the percentage of bytes processed out of total bytes. 123 // 124 func (s *Status) percentComplete() float64 { 125 return float64(100.0) * (float64(s.bytesProcessed) / float64(s.totalBytes)) 126 } 127 128 // processTime returns the Duration representing the time taken to process the bytes so far. 129 // 130 func (s *Status) processTime() time.Duration { 131 return time.Since(s.startTime) 132 } 133 134 // throughputMBs returns the throughput in MB 135 // 136 func (s *Status) throughputMBs() float64 { 137 return float64(s.bytesProcessed) / oneMB / s.processTime().Seconds() 138 }