github.com/whamcloud/lemur@v0.0.0-20190827193804-4655df8a52af/dmplugin/dmio/progress.go (about)

     1  // Copyright (c) 2018 DDN. All rights reserved.
     2  // Use of this source code is governed by a MIT-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package dmio
     6  
     7  import (
     8  	"io"
     9  	"sync/atomic"
    10  	"time"
    11  
    12  	"github.com/intel-hpdd/logging/alert"
    13  )
    14  
    15  // The default buffer size in io.copyBuffer() is 32KB -- this is the
    16  // read size seen when the checksummer is running.
    17  const ckSumSig = 32 * 1024
    18  
    19  type (
    20  	progressFunc func(int64, int64) error
    21  
    22  	progressUpdater struct {
    23  		done        chan struct{}
    24  		bytesCopied int64
    25  	}
    26  
    27  	// ProgressReader wraps an io.ReaderAt and periodically invokes the
    28  	// supplied callback to provide progress updates.
    29  	ProgressReader struct {
    30  		progressUpdater
    31  
    32  		src io.ReadSeeker
    33  	}
    34  
    35  	// ProgressWriter wraps an io.Writer and periodically invokes the
    36  	// supplied callback to provide progress updates.
    37  	ProgressWriter struct {
    38  		progressUpdater
    39  
    40  		dst io.Writer
    41  	}
    42  
    43  	// ProgressWriterAt wraps an io.WriterAt and periodically invokes the
    44  	// supplied callback to provide progress updates.
    45  	ProgressWriterAt struct {
    46  		progressUpdater
    47  
    48  		dst io.WriterAt
    49  	}
    50  )
    51  
    52  // startUpdates creates a goroutine to periodically call the supplied
    53  // callback with updated progress information. The callback must accept
    54  // an int64 representing the last update value, and an int64 representing
    55  // the delta between the last update value and the current bytes-copied count.
    56  func (p *progressUpdater) startUpdates(updateEvery time.Duration, f progressFunc) {
    57  	p.done = make(chan struct{})
    58  
    59  	if updateEvery > 0 && f != nil {
    60  		var lastTotal int64
    61  		go func() {
    62  			for {
    63  				select {
    64  				case <-time.After(updateEvery):
    65  					copied := atomic.LoadInt64(&p.bytesCopied)
    66  					if err := f(lastTotal, copied-lastTotal); err != nil {
    67  						alert.Warnf("Error received from updater callback: %s", err)
    68  						// Should we return here?
    69  					}
    70  					lastTotal = copied
    71  				case <-p.done:
    72  					return
    73  				}
    74  			}
    75  		}()
    76  	}
    77  }
    78  
    79  // StopUpdates kills the updater goroutine
    80  func (p *progressUpdater) StopUpdates() {
    81  	p.done <- struct{}{}
    82  }
    83  
    84  // Seek calls the wrapped Seeker's Seek
    85  func (r *ProgressReader) Seek(offset int64, whence int) (int64, error) {
    86  	return r.src.Seek(offset, whence)
    87  }
    88  
    89  // Read calls internal Read and tracks how many bytes were read.
    90  func (r *ProgressReader) Read(p []byte) (n int, err error) {
    91  	n, err = r.src.Read(p)
    92  	atomic.AddInt64(&r.bytesCopied, int64(n))
    93  	return
    94  }
    95  
    96  // DISABLED
    97  //
    98  // The go http client package wraps the socket with a bufio.NewWriter() with
    99  // the default 4k buffer. When aws sdk sends our file data, it ends up using
   100  // io.Copy(w, src) to copy the data. This uses bufio Writer.ReadFrom() method
   101  // and this reads from from our file into a 4k buf. One way to fix this could
   102  // have been to implment a WriteTo method so we could read any size buffer we
   103  // wanted to, but this doesn't work because the aws sdk has wrapped our file
   104  // object with several others.
   105  //
   106  // One way to trick the sdk to read larger buffer sizes is to disable ReadAt and
   107  // force the sdk to fall back to Read each chunk with one call. This is much
   108  // better for lustre, but now read IO is single threaded, so this isn't so good
   109  // either.  On the positive side, now the file is only read once as the sdk is
   110  // able to sign each chunk from buffer in memeory, and also now we could
   111  // calculate the sha1 for the whole file like we do in the posix mover.
   112  //
   113  // It is a shame that go-aws-sdk doesn't provide a callback for updating status
   114  // like boto does.
   115  
   116  // ReadAt reads len(p) bytes into p starting at offset off in the underlying
   117  // input source. It returns the number of bytes read (0 <= n <= len(p)) and
   118  // any error encountered.
   119  /*
   120  func (r *ProgressReader) ReadAt(p []byte, off int64) (int, error) {
   121  	n, err := r.src.ReadAt(p, off)
   122  
   123  	// Stupid hack to work around double-counting for progress updates.
   124  	// Each file is read twice -- once for checksumming, then again
   125  	// to actually transfer the data.
   126  	if n != ckSumSig {
   127  		atomic.AddInt64(&r.bytesCopied, n)
   128  	}
   129  
   130  	return n, err
   131  }
   132  */
   133  
   134  // NewProgressReader returns a new *ProgressReader
   135  func NewProgressReader(src io.ReadSeeker, updateEvery time.Duration, f progressFunc) *ProgressReader {
   136  	r := &ProgressReader{
   137  		src: src,
   138  	}
   139  
   140  	r.startUpdates(updateEvery, f)
   141  
   142  	return r
   143  }
   144  
   145  // Write writes len(p) bytes from p to the underlying data stream at
   146  // offset off. It returns the number of bytes written from p (0 <= n <= len(p))
   147  // and any error encountered that caused the write to stop early. WriteAt
   148  // must return a non-nil error if it returns n < len(p).
   149  func (w *ProgressWriter) Write(p []byte) (int, error) {
   150  	n, err := w.dst.Write(p)
   151  
   152  	atomic.AddInt64(&w.bytesCopied, int64(n))
   153  	// debug.Printf("wrote %d bytes", n)
   154  	return n, err
   155  
   156  }
   157  
   158  // NewProgressWriter returns a new *ProgressWriter
   159  func NewProgressWriter(dst io.Writer, updateEvery time.Duration, f progressFunc) *ProgressWriter {
   160  	w := &ProgressWriter{
   161  		dst: dst,
   162  	}
   163  	w.startUpdates(updateEvery, f)
   164  
   165  	return w
   166  }
   167  
   168  // WriteAt writes len(p) bytes from p to the underlying data stream at
   169  // offset off. It returns the number of bytes written from p (0 <= n <= len(p))
   170  // and any error encountered that caused the write to stop early. WriteAt
   171  // must return a non-nil error if it returns n < len(p).
   172  func (w *ProgressWriterAt) WriteAt(p []byte, off int64) (int, error) {
   173  	n, err := w.dst.WriteAt(p, off)
   174  
   175  	atomic.AddInt64(&w.bytesCopied, int64(n))
   176  
   177  	return n, err
   178  }
   179  
   180  // NewProgressWriterAt returns a new *ProgressWriterAt
   181  func NewProgressWriterAt(dst io.WriterAt, updateEvery time.Duration, f progressFunc) *ProgressWriterAt {
   182  	w := &ProgressWriterAt{
   183  		dst: dst,
   184  	}
   185  	w.startUpdates(updateEvery, f)
   186  
   187  	return w
   188  }