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 }