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

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