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 }