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  }