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 }