address issue #105
Vladimir Bauer
3 years ago
| 0 | module github.com/vbauerster/mpb/_examples/progressAsWriter | |
| 1 | ||
| 2 | go 1.17 | |
| 3 | ||
| 4 | require github.com/vbauerster/mpb/v8 v8.0.0 | |
| 5 | ||
| 6 | require ( | |
| 7 | github.com/VividCortex/ewma v1.2.0 // indirect | |
| 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.13 // indirect | |
| 10 | github.com/rivo/uniseg v0.2.0 // indirect | |
| 11 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect | |
| 12 | ) |
| 0 | package main | |
| 1 | ||
| 2 | import ( | |
| 3 | "fmt" | |
| 4 | "log" | |
| 5 | "math/rand" | |
| 6 | "sync" | |
| 7 | "time" | |
| 8 | ||
| 9 | "github.com/vbauerster/mpb/v8" | |
| 10 | "github.com/vbauerster/mpb/v8/decor" | |
| 11 | ) | |
| 12 | ||
| 13 | func main() { | |
| 14 | total, numBars := 100, 2 | |
| 15 | var wg sync.WaitGroup | |
| 16 | wg.Add(numBars) | |
| 17 | done := make(chan struct{}) | |
| 18 | p := mpb.New( | |
| 19 | mpb.WithWidth(64), | |
| 20 | mpb.WithWaitGroup(&wg), | |
| 21 | mpb.WithShutdownNotifier(done), | |
| 22 | ) | |
| 23 | ||
| 24 | log.SetOutput(p) | |
| 25 | ||
| 26 | for i := 0; i < numBars; i++ { | |
| 27 | name := fmt.Sprintf("Bar#%d:", i) | |
| 28 | bar := p.AddBar(int64(total), | |
| 29 | mpb.PrependDecorators( | |
| 30 | decor.Name(name), | |
| 31 | decor.Percentage(decor.WCSyncSpace), | |
| 32 | ), | |
| 33 | mpb.AppendDecorators( | |
| 34 | decor.OnComplete( | |
| 35 | decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncWidth), "done", | |
| 36 | ), | |
| 37 | ), | |
| 38 | ) | |
| 39 | // simulating some work | |
| 40 | go func() { | |
| 41 | defer wg.Done() | |
| 42 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) | |
| 43 | max := 100 * time.Millisecond | |
| 44 | for i := 0; i < total; i++ { | |
| 45 | // start variable is solely for EWMA calculation | |
| 46 | // EWMA's unit of measure is an iteration's duration | |
| 47 | start := time.Now() | |
| 48 | time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) | |
| 49 | bar.Increment() | |
| 50 | // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract | |
| 51 | bar.DecoratorEwmaUpdate(time.Since(start)) | |
| 52 | } | |
| 53 | log.Println(name, "done") | |
| 54 | }() | |
| 55 | } | |
| 56 | ||
| 57 | var qwg sync.WaitGroup | |
| 58 | qwg.Add(1) | |
| 59 | go func() { | |
| 60 | quit: | |
| 61 | for { | |
| 62 | select { | |
| 63 | case <-done: | |
| 64 | // after done, underlying io.Writer returns mpb.DoneError | |
| 65 | // so following isn't printed | |
| 66 | log.Println("all done") | |
| 67 | break quit | |
| 68 | default: | |
| 69 | log.Println("waiting for done") | |
| 70 | time.Sleep(100 * time.Millisecond) | |
| 71 | } | |
| 72 | } | |
| 73 | qwg.Done() | |
| 74 | }() | |
| 75 | ||
| 76 | p.Wait() | |
| 77 | qwg.Wait() | |
| 78 | } |
| 17 | 17 | prr = 150 * time.Millisecond // default RefreshRate |
| 18 | 18 | ) |
| 19 | 19 | |
| 20 | // DoneError represents an error when `*mpb.Progress` is done but its functionality is requested. | |
| 21 | var DoneError = fmt.Errorf("%T instance can't be reused after it's done!", (*Progress)(nil)) | |
| 22 | ||
| 20 | 23 | // Progress represents a container that renders one or more progress bars. |
| 21 | 24 | type Progress struct { |
| 22 | 25 | ctx context.Context |
| 24 | 27 | cwg *sync.WaitGroup |
| 25 | 28 | bwg *sync.WaitGroup |
| 26 | 29 | operateState chan func(*pState) |
| 30 | interceptIo chan func(io.Writer) | |
| 27 | 31 | done chan struct{} |
| 28 | 32 | refreshCh chan time.Time |
| 29 | 33 | once sync.Once |
| 82 | 86 | cwg: new(sync.WaitGroup), |
| 83 | 87 | bwg: new(sync.WaitGroup), |
| 84 | 88 | operateState: make(chan func(*pState)), |
| 89 | interceptIo: make(chan func(io.Writer)), | |
| 85 | 90 | done: make(chan struct{}), |
| 86 | 91 | } |
| 87 | 92 | |
| 131 | 136 | return bar |
| 132 | 137 | case <-p.done: |
| 133 | 138 | p.bwg.Done() |
| 134 | panic(fmt.Sprintf("%T instance can't be reused after it's done!", p)) | |
| 139 | panic(DoneError) | |
| 135 | 140 | } |
| 136 | 141 | } |
| 137 | 142 | |
| 177 | 182 | } |
| 178 | 183 | } |
| 179 | 184 | |
| 185 | // Write is implementation of io.Writer. | |
| 186 | // Writing to `*mpb.Progress` will print lines above a running bar. | |
| 187 | // Writes aren't flushed immediatly, but at next refresh cycle. | |
| 188 | // If Write is called after `*mpb.Progress` is done, `mpb.DoneError` | |
| 189 | // is returned. | |
| 190 | func (p *Progress) Write(b []byte) (int, error) { | |
| 191 | type result struct { | |
| 192 | n int | |
| 193 | err error | |
| 194 | } | |
| 195 | ch := make(chan *result) | |
| 196 | select { | |
| 197 | case p.interceptIo <- func(w io.Writer) { | |
| 198 | n, err := w.Write(b) | |
| 199 | ch <- &result{n, err} | |
| 200 | }: | |
| 201 | res := <-ch | |
| 202 | return res.n, res.err | |
| 203 | case <-p.done: | |
| 204 | return 0, DoneError | |
| 205 | } | |
| 206 | } | |
| 207 | ||
| 180 | 208 | // Wait waits for all bars to complete and finally shutdowns container. |
| 181 | 209 | // After this method has been called, there is no way to reuse *Progress |
| 182 | 210 | // instance. |
| 220 | 248 | select { |
| 221 | 249 | case op := <-p.operateState: |
| 222 | 250 | op(s) |
| 251 | case fn := <-p.interceptIo: | |
| 252 | fn(cw) | |
| 223 | 253 | case <-p.refreshCh: |
| 224 | 254 | render(s.debugOut) |
| 225 | 255 | case <-s.shutdownNotifier: |