Decouple eta from core
Vladimir Bauer
8 years ago
| 8 | 8 | "time" |
| 9 | 9 | "unicode/utf8" |
| 10 | 10 | |
| 11 | "github.com/VividCortex/ewma" | |
| 12 | 11 | "github.com/vbauerster/mpb/decor" |
| 13 | 12 | ) |
| 14 | 13 | |
| 35 | 34 | cacheState *bState |
| 36 | 35 | operateState chan func(*bState) |
| 37 | 36 | frameReaderCh chan io.Reader |
| 38 | startBlockCh <-chan time.Time | |
| 39 | 37 | |
| 40 | 38 | // done is closed by Bar's goroutine, after cacheState is written |
| 41 | 39 | done chan struct{} |
| 64 | 62 | timeElapsed time.Duration |
| 65 | 63 | aDecorators []decor.Decorator |
| 66 | 64 | pDecorators []decor.Decorator |
| 65 | amountReceivers []decor.AmountReceiver | |
| 66 | shutdownListeners []decor.ShutdownListener | |
| 67 | 67 | refill *refill |
| 68 | 68 | bufP, bufB, bufA *bytes.Buffer |
| 69 | 69 | panicMsg string |
| 70 | 70 | |
| 71 | ewmAverage ewma.MovingAverage | |
| 72 | ||
| 73 | 71 | // following options are assigned to the *Bar |
| 74 | priority int | |
| 75 | runningBar *Bar | |
| 76 | startBlockCh chan time.Time | |
| 72 | priority int | |
| 73 | runningBar *Bar | |
| 77 | 74 | } |
| 78 | 75 | refill struct { |
| 79 | 76 | char rune |
| 112 | 109 | b := &Bar{ |
| 113 | 110 | priority: s.priority, |
| 114 | 111 | runningBar: s.runningBar, |
| 115 | startBlockCh: s.startBlockCh, | |
| 116 | 112 | operateState: make(chan func(*bState)), |
| 117 | 113 | frameReaderCh: make(chan io.Reader, 1), |
| 118 | 114 | done: make(chan struct{}), |
| 119 | 115 | shutdown: make(chan struct{}), |
| 120 | 116 | } |
| 121 | ||
| 122 | s.startBlockCh = nil | |
| 123 | 117 | |
| 124 | 118 | if b.runningBar != nil { |
| 125 | 119 | b.priority = b.runningBar.priority |
| 155 | 149 | proxyReader.startBlockCh = startBlock[0] |
| 156 | 150 | } |
| 157 | 151 | return proxyReader |
| 158 | } | |
| 159 | ||
| 160 | // Increment is a shorthand for b.IncrBy(1) | |
| 161 | func (b *Bar) Increment() { | |
| 162 | b.IncrBy(1) | |
| 163 | 152 | } |
| 164 | 153 | |
| 165 | 154 | // ResumeFill fills bar with different r rune, |
| 240 | 229 | } |
| 241 | 230 | } |
| 242 | 231 | |
| 232 | // Increment is a shorthand for b.IncrBy(1) | |
| 233 | func (b *Bar) Increment() { | |
| 234 | b.IncrBy(1) | |
| 235 | } | |
| 236 | ||
| 243 | 237 | // IncrBy increments progress bar by amount of n |
| 244 | 238 | func (b *Bar) IncrBy(n int) { |
| 245 | now := time.Now() | |
| 246 | 239 | select { |
| 247 | 240 | case b.operateState <- func(s *bState) { |
| 248 | 241 | s.current += int64(n) |
| 249 | s.timeElapsed = now.Sub(s.startTime) | |
| 250 | if s.ewmAverage != nil { | |
| 251 | lastBlockTime := now.Sub(s.blockStartTime) | |
| 252 | lastItemEstimate := float64(lastBlockTime) / float64(n) | |
| 253 | s.ewmAverage.Add(lastItemEstimate) | |
| 254 | } | |
| 255 | 242 | if s.dynamic { |
| 256 | 243 | curp := decor.CalcPercentage(s.total, s.current, 100) |
| 257 | 244 | if 100-curp <= s.totalAutoIncrTrigger { |
| 261 | 248 | s.current = s.total |
| 262 | 249 | s.toComplete = true |
| 263 | 250 | } |
| 251 | for _, ar := range s.amountReceivers { | |
| 252 | ar.NextAmount(n) | |
| 253 | } | |
| 254 | s.timeElapsed = time.Since(s.startTime) | |
| 264 | 255 | }: |
| 265 | 256 | case <-b.done: |
| 266 | 257 | } |
| 280 | 271 | select { |
| 281 | 272 | case op := <-b.operateState: |
| 282 | 273 | op(s) |
| 283 | case now := <-b.startBlockCh: | |
| 284 | s.blockStartTime = now | |
| 285 | 274 | case <-cancel: |
| 286 | 275 | s.toComplete = true |
| 287 | 276 | cancel = nil |
| 288 | 277 | case <-b.shutdown: |
| 289 | 278 | b.cacheState = s |
| 290 | 279 | close(b.done) |
| 280 | for _, sl := range s.shutdownListeners { | |
| 281 | sl.Shutdown() | |
| 282 | } | |
| 291 | 283 | return |
| 292 | 284 | } |
| 293 | 285 | } |
| 11 | 11 | func AppendDecorators(appenders ...decor.Decorator) BarOption { |
| 12 | 12 | return func(s *bState) { |
| 13 | 13 | for _, decorator := range appenders { |
| 14 | if t, ok := decorator.(*decor.EwmaETA); ok { | |
| 15 | s.ewmAverage = t | |
| 16 | s.startBlockCh = t.StartBlockCh | |
| 14 | if ar, ok := decorator.(decor.AmountReceiver); ok { | |
| 15 | s.amountReceivers = append(s.amountReceivers, ar) | |
| 16 | } | |
| 17 | if sl, ok := decorator.(decor.ShutdownListener); ok { | |
| 18 | s.shutdownListeners = append(s.shutdownListeners, sl) | |
| 17 | 19 | } |
| 18 | 20 | s.aDecorators = append(s.aDecorators, decorator) |
| 19 | 21 | } |
| 24 | 26 | func PrependDecorators(prependers ...decor.Decorator) BarOption { |
| 25 | 27 | return func(s *bState) { |
| 26 | 28 | for _, decorator := range prependers { |
| 27 | if t, ok := decorator.(*decor.EwmaETA); ok { | |
| 28 | s.ewmAverage = t | |
| 29 | s.startBlockCh = t.StartBlockCh | |
| 29 | if ar, ok := decorator.(decor.AmountReceiver); ok { | |
| 30 | s.amountReceivers = append(s.amountReceivers, ar) | |
| 31 | } | |
| 32 | if sl, ok := decorator.(decor.ShutdownListener); ok { | |
| 33 | s.shutdownListeners = append(s.shutdownListeners, sl) | |
| 30 | 34 | } |
| 31 | 35 | s.pDecorators = append(s.pDecorators, decorator) |
| 32 | 36 | } |
| 4 | 4 | "math" |
| 5 | 5 | "time" |
| 6 | 6 | "unicode/utf8" |
| 7 | ||
| 8 | "github.com/VividCortex/ewma" | |
| 9 | 7 | ) |
| 10 | 8 | |
| 11 | 9 | const ( |
| 59 | 57 | Decor(*Statistics, chan<- int, <-chan int) string |
| 60 | 58 | } |
| 61 | 59 | |
| 62 | // CompleteMessenger is an interface with one method: | |
| 63 | // | |
| 64 | // OnComplete(message string, wc ...WC) | |
| 60 | // OnCompleteMessenger is an interface with one method: | |
| 61 | // | |
| 62 | // OnCompleteMessage(message string, wc ...WC) | |
| 65 | 63 | // |
| 66 | 64 | // Decorators implementing this interface suppose to return provided string on complete event. |
| 67 | type CompleteMessenger interface { | |
| 68 | OnComplete(string, ...WC) | |
| 65 | type OnCompleteMessenger interface { | |
| 66 | OnCompleteMessage(string, ...WC) | |
| 67 | } | |
| 68 | ||
| 69 | type AmountReceiver interface { | |
| 70 | NextAmount(int) | |
| 71 | } | |
| 72 | ||
| 73 | type ShutdownListener interface { | |
| 74 | Shutdown() | |
| 69 | 75 | } |
| 70 | 76 | |
| 71 | 77 | // DecoratorFunc is an adapter for Decorator interface |
| 128 | 134 | // |
| 129 | 135 | // `wc` optional WC config |
| 130 | 136 | func OnComplete(decorator Decorator, message string, wc ...WC) Decorator { |
| 131 | if cm, ok := decorator.(CompleteMessenger); ok { | |
| 132 | cm.OnComplete(message, wc...) | |
| 137 | if cm, ok := decorator.(OnCompleteMessenger); ok { | |
| 138 | cm.OnCompleteMessage(message, wc...) | |
| 133 | 139 | return decorator |
| 134 | 140 | } |
| 135 | 141 | msgDecorator := Name(message, wc...) |
| 217 | 223 | } |
| 218 | 224 | return wc0.formatMsg(str, widthAccumulator, widthDistributor) |
| 219 | 225 | }) |
| 220 | } | |
| 221 | ||
| 222 | // ETA returns exponential-weighted-moving-average ETA decorator. | |
| 223 | // | |
| 224 | // `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] | |
| 225 | // | |
| 226 | // `age` is related to the decay factor alpha by the formula given for the DECAY constant. | |
| 227 | // It signifies the average age of the samples as time goes to infinity. Basically age is | |
| 228 | // the previous N samples to average over. If zero value provided, it defaults to 30. | |
| 229 | // | |
| 230 | // `startBlock` is channel, user suppose to send time.Now() on each iteration of block start. | |
| 231 | // | |
| 232 | // `wc` optional WC config | |
| 233 | func ETA(style int, age float64, startBlock chan time.Time, wc ...WC) Decorator { | |
| 234 | var wc0 WC | |
| 235 | if len(wc) > 0 { | |
| 236 | wc0 = wc[0] | |
| 237 | } | |
| 238 | if age == .0 { | |
| 239 | age = ewma.AVG_METRIC_AGE | |
| 240 | } | |
| 241 | return &EwmaETA{ | |
| 242 | MovingAverage: ewma.NewMovingAverage(age), | |
| 243 | StartBlockCh: startBlock, | |
| 244 | style: style, | |
| 245 | wc: wc0, | |
| 246 | } | |
| 247 | } | |
| 248 | ||
| 249 | // EwmaETA is a struct, which implements ewma based ETA decorator. | |
| 250 | // Normally should not be used directly, use helper func instead: | |
| 251 | // | |
| 252 | // decor.ETA(int, float64, chan time.Time, ...decor.WC) | |
| 253 | type EwmaETA struct { | |
| 254 | ewma.MovingAverage | |
| 255 | StartBlockCh chan time.Time | |
| 256 | style int | |
| 257 | wc WC | |
| 258 | onComplete *struct { | |
| 259 | msg string | |
| 260 | wc WC | |
| 261 | } | |
| 262 | } | |
| 263 | ||
| 264 | func (s *EwmaETA) Decor(st *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 265 | if st.Completed && s.onComplete != nil { | |
| 266 | return s.onComplete.wc.formatMsg(s.onComplete.msg, widthAccumulator, widthDistributor) | |
| 267 | } | |
| 268 | ||
| 269 | var str string | |
| 270 | timeRemaining := time.Duration(st.Total-st.Current) * time.Duration(round(s.Value())) | |
| 271 | hours := int64((timeRemaining / time.Hour) % 60) | |
| 272 | minutes := int64((timeRemaining / time.Minute) % 60) | |
| 273 | seconds := int64((timeRemaining / time.Second) % 60) | |
| 274 | ||
| 275 | switch s.style { | |
| 276 | case ET_STYLE_GO: | |
| 277 | str = fmt.Sprint(time.Duration(timeRemaining.Seconds()) * time.Second) | |
| 278 | case ET_STYLE_HHMMSS: | |
| 279 | str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) | |
| 280 | case ET_STYLE_HHMM: | |
| 281 | str = fmt.Sprintf("%02d:%02d", hours, minutes) | |
| 282 | case ET_STYLE_MMSS: | |
| 283 | str = fmt.Sprintf("%02d:%02d", minutes, seconds) | |
| 284 | } | |
| 285 | ||
| 286 | return s.wc.formatMsg(str, widthAccumulator, widthDistributor) | |
| 287 | } | |
| 288 | ||
| 289 | func (s *EwmaETA) OnComplete(msg string, wc ...WC) { | |
| 290 | var wc0 WC | |
| 291 | if len(wc) > 0 { | |
| 292 | wc0 = wc[0] | |
| 293 | } | |
| 294 | s.onComplete = &struct { | |
| 295 | msg string | |
| 296 | wc WC | |
| 297 | }{msg, wc0} | |
| 298 | 226 | } |
| 299 | 227 | |
| 300 | 228 | // Elapsed returns elapsed time decorator. |
| 0 | package decor | |
| 1 | ||
| 2 | import ( | |
| 3 | "fmt" | |
| 4 | "time" | |
| 5 | ||
| 6 | "github.com/VividCortex/ewma" | |
| 7 | ) | |
| 8 | ||
| 9 | // ETA returns exponential-weighted-moving-average ETA decorator. | |
| 10 | // | |
| 11 | // `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] | |
| 12 | // | |
| 13 | // `age` is related to the decay factor alpha by the formula given for the DECAY constant. | |
| 14 | // It signifies the average age of the samples as time goes to infinity. Basically age is | |
| 15 | // the previous N samples to average over. If zero value provided, it defaults to 30. | |
| 16 | // | |
| 17 | // `startBlock` is a time.Time receive channel. User suppose to send time.Now() | |
| 18 | // to this channel on each iteration of block start, right before actual job. | |
| 19 | // The channel will be closed automatically on bar shutdown event, so there is | |
| 20 | // no need to close from user side. | |
| 21 | // | |
| 22 | // `wc` optional WC config | |
| 23 | func ETA(style int, age float64, startBlock chan time.Time, wc ...WC) Decorator { | |
| 24 | var wc0 WC | |
| 25 | if len(wc) > 0 { | |
| 26 | wc0 = wc[0] | |
| 27 | } | |
| 28 | if age == .0 { | |
| 29 | age = ewma.AVG_METRIC_AGE | |
| 30 | } | |
| 31 | eta := &ewmaETA{ | |
| 32 | mAverage: ewma.NewMovingAverage(age), | |
| 33 | startBlockReceiver: startBlock, | |
| 34 | startBlockStreamer: make(chan time.Time), | |
| 35 | style: style, | |
| 36 | wc: wc0, | |
| 37 | } | |
| 38 | go eta.serve() | |
| 39 | return eta | |
| 40 | } | |
| 41 | ||
| 42 | type ewmaETA struct { | |
| 43 | mAverage ewma.MovingAverage | |
| 44 | startBlockReceiver chan time.Time | |
| 45 | startBlockStreamer chan time.Time | |
| 46 | style int | |
| 47 | wc WC | |
| 48 | onComplete *struct { | |
| 49 | msg string | |
| 50 | wc WC | |
| 51 | } | |
| 52 | } | |
| 53 | ||
| 54 | func (s *ewmaETA) Decor(st *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 55 | if st.Completed && s.onComplete != nil { | |
| 56 | return s.onComplete.wc.formatMsg(s.onComplete.msg, widthAccumulator, widthDistributor) | |
| 57 | } | |
| 58 | ||
| 59 | var str string | |
| 60 | timeRemaining := time.Duration(st.Total-st.Current) * time.Duration(round(s.mAverage.Value())) | |
| 61 | hours := int64((timeRemaining / time.Hour) % 60) | |
| 62 | minutes := int64((timeRemaining / time.Minute) % 60) | |
| 63 | seconds := int64((timeRemaining / time.Second) % 60) | |
| 64 | ||
| 65 | switch s.style { | |
| 66 | case ET_STYLE_GO: | |
| 67 | str = fmt.Sprint(time.Duration(timeRemaining.Seconds()) * time.Second) | |
| 68 | case ET_STYLE_HHMMSS: | |
| 69 | str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) | |
| 70 | case ET_STYLE_HHMM: | |
| 71 | str = fmt.Sprintf("%02d:%02d", hours, minutes) | |
| 72 | case ET_STYLE_MMSS: | |
| 73 | str = fmt.Sprintf("%02d:%02d", minutes, seconds) | |
| 74 | } | |
| 75 | ||
| 76 | return s.wc.formatMsg(str, widthAccumulator, widthDistributor) | |
| 77 | } | |
| 78 | ||
| 79 | func (s *ewmaETA) NextAmount(n int) { | |
| 80 | sb := <-s.startBlockStreamer | |
| 81 | lastBlockTime := time.Since(sb) | |
| 82 | lastItemEstimate := float64(lastBlockTime) / float64(n) | |
| 83 | s.mAverage.Add(lastItemEstimate) | |
| 84 | } | |
| 85 | ||
| 86 | func (s *ewmaETA) OnCompleteMessage(msg string, wc ...WC) { | |
| 87 | var wc0 WC | |
| 88 | if len(wc) > 0 { | |
| 89 | wc0 = wc[0] | |
| 90 | } | |
| 91 | s.onComplete = &struct { | |
| 92 | msg string | |
| 93 | wc WC | |
| 94 | }{msg, wc0} | |
| 95 | } | |
| 96 | ||
| 97 | func (s *ewmaETA) Shutdown() { | |
| 98 | close(s.startBlockReceiver) | |
| 99 | } | |
| 100 | ||
| 101 | func (s *ewmaETA) serve() { | |
| 102 | for now := range s.startBlockReceiver { | |
| 103 | s.startBlockStreamer <- now | |
| 104 | } | |
| 105 | } |