github.com/vmware/govmomi@v0.51.0/vim25/progress/reader.go (about)

     1  // © Broadcom. All Rights Reserved.
     2  // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package progress
     6  
     7  import (
     8  	"container/list"
     9  	"context"
    10  	"fmt"
    11  	"io"
    12  	"sync/atomic"
    13  	"time"
    14  )
    15  
    16  type readerReport struct {
    17  	pos  int64   // Keep first to ensure 64-bit alignment
    18  	size int64   // Keep first to ensure 64-bit alignment
    19  	bps  *uint64 // Keep first to ensure 64-bit alignment
    20  
    21  	t time.Time
    22  
    23  	err error
    24  }
    25  
    26  func (r readerReport) Percentage() float32 {
    27  	if r.size <= 0 {
    28  		return 0
    29  	}
    30  	return 100.0 * float32(r.pos) / float32(r.size)
    31  }
    32  
    33  func (r readerReport) Detail() string {
    34  	const (
    35  		KiB = 1024
    36  		MiB = 1024 * KiB
    37  		GiB = 1024 * MiB
    38  	)
    39  
    40  	// Use the reader's bps field, so this report returns an up-to-date number.
    41  	//
    42  	// For example: if there hasn't been progress for the last 5 seconds, the
    43  	// most recent report should return "0B/s".
    44  	//
    45  	bps := atomic.LoadUint64(r.bps)
    46  
    47  	switch {
    48  	case bps >= GiB:
    49  		return fmt.Sprintf("%.1fGiB/s", float32(bps)/float32(GiB))
    50  	case bps >= MiB:
    51  		return fmt.Sprintf("%.1fMiB/s", float32(bps)/float32(MiB))
    52  	case bps >= KiB:
    53  		return fmt.Sprintf("%.1fKiB/s", float32(bps)/float32(KiB))
    54  	default:
    55  		return fmt.Sprintf("%dB/s", bps)
    56  	}
    57  }
    58  
    59  func (p readerReport) Error() error {
    60  	return p.err
    61  }
    62  
    63  // reader wraps an io.Reader and sends a progress report over a channel for
    64  // every read it handles.
    65  type reader struct {
    66  	r io.Reader
    67  
    68  	pos  int64
    69  	size int64
    70  	bps  uint64
    71  
    72  	ch  chan<- Report
    73  	ctx context.Context
    74  }
    75  
    76  func NewReader(ctx context.Context, s Sinker, r io.Reader, size int64) *reader {
    77  	pr := reader{
    78  		r:    r,
    79  		ctx:  ctx,
    80  		size: size,
    81  	}
    82  
    83  	// Reports must be sent downstream and to the bps computation loop.
    84  	pr.ch = Tee(s, newBpsLoop(&pr.bps)).Sink()
    85  
    86  	return &pr
    87  }
    88  
    89  // Read calls the Read function on the underlying io.Reader. Additionally,
    90  // every read causes a progress report to be sent to the progress reader's
    91  // underlying channel.
    92  func (r *reader) Read(b []byte) (int, error) {
    93  	n, err := r.r.Read(b)
    94  	r.pos += int64(n)
    95  
    96  	if err != nil && err != io.EOF {
    97  		return n, err
    98  	}
    99  
   100  	q := readerReport{
   101  		t:    time.Now(),
   102  		pos:  r.pos,
   103  		size: r.size,
   104  		bps:  &r.bps,
   105  	}
   106  
   107  	select {
   108  	case r.ch <- q:
   109  	case <-r.ctx.Done():
   110  	}
   111  
   112  	return n, err
   113  }
   114  
   115  // Done marks the progress reader as done, optionally including an error in the
   116  // progress report. After sending it, the underlying channel is closed.
   117  func (r *reader) Done(err error) {
   118  	q := readerReport{
   119  		t:    time.Now(),
   120  		pos:  r.pos,
   121  		size: r.size,
   122  		bps:  &r.bps,
   123  		err:  err,
   124  	}
   125  
   126  	select {
   127  	case r.ch <- q:
   128  		close(r.ch)
   129  	case <-r.ctx.Done():
   130  	}
   131  }
   132  
   133  // newBpsLoop returns a sink that monitors and stores throughput.
   134  func newBpsLoop(dst *uint64) SinkFunc {
   135  	fn := func() chan<- Report {
   136  		sink := make(chan Report)
   137  		go bpsLoop(sink, dst)
   138  		return sink
   139  	}
   140  
   141  	return fn
   142  }
   143  
   144  func bpsLoop(ch <-chan Report, dst *uint64) {
   145  	l := list.New()
   146  
   147  	for {
   148  		var tch <-chan time.Time
   149  
   150  		// Setup timer for front of list to become stale.
   151  		if e := l.Front(); e != nil {
   152  			dt := time.Second - time.Since(e.Value.(readerReport).t)
   153  			tch = time.After(dt)
   154  		}
   155  
   156  		select {
   157  		case q, ok := <-ch:
   158  			if !ok {
   159  				return
   160  			}
   161  
   162  			l.PushBack(q)
   163  		case <-tch:
   164  			l.Remove(l.Front())
   165  		}
   166  
   167  		// Compute new bps
   168  		if l.Len() == 0 {
   169  			atomic.StoreUint64(dst, 0)
   170  		} else {
   171  			f := l.Front().Value.(readerReport)
   172  			b := l.Back().Value.(readerReport)
   173  			atomic.StoreUint64(dst, uint64(b.pos-f.pos))
   174  		}
   175  	}
   176  }