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