MovingAverage interface
Vladimir Bauer
8 years ago
| 23 | 23 | |
| 24 | 24 | const ( |
| 25 | 25 | _ = iota |
| 26 | unitKiB | |
| 27 | unitKB | |
| 26 | UnitKiB | |
| 27 | UnitKB | |
| 28 | 28 | ) |
| 29 | 29 | |
| 30 | 30 | type CounterKiB int64 |
| 154 | 154 | // |
| 155 | 155 | // "%.1f / %.1f" = "1.0MiB / 12.0MiB" or "% .1f / % .1f" = "1.0 MiB / 12.0 MiB" |
| 156 | 156 | func CountersKibiByte(pairFormat string, wcc ...WC) Decorator { |
| 157 | return counters(unitKiB, pairFormat, wcc...) | |
| 157 | return counters(UnitKiB, pairFormat, wcc...) | |
| 158 | 158 | } |
| 159 | 159 | |
| 160 | 160 | // CountersKiloByte returns human friendly byte counters decorator, where counters unit is multiple by 1000. |
| 167 | 167 | // |
| 168 | 168 | // "%.1f / %.1f" = "1.0MB / 12.0MB" or "% .1f / % .1f" = "1.0 MB / 12.0 MB" |
| 169 | 169 | func CountersKiloByte(pairFormat string, wcc ...WC) Decorator { |
| 170 | return counters(unitKB, pairFormat, wcc...) | |
| 170 | return counters(UnitKB, pairFormat, wcc...) | |
| 171 | 171 | } |
| 172 | 172 | |
| 173 | 173 | func counters(unit int, pairFormat string, wcc ...WC) Decorator { |
| 179 | 179 | return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { |
| 180 | 180 | var str string |
| 181 | 181 | switch unit { |
| 182 | case unitKiB: | |
| 182 | case UnitKiB: | |
| 183 | 183 | str = fmt.Sprintf(pairFormat, CounterKiB(s.Current), CounterKiB(s.Total)) |
| 184 | case unitKB: | |
| 184 | case UnitKB: | |
| 185 | 185 | str = fmt.Sprintf(pairFormat, CounterKB(s.Current), CounterKB(s.Total)) |
| 186 | 186 | default: |
| 187 | 187 | str = fmt.Sprintf(pairFormat, s.Current, s.Total) |
| 15 | 15 | // `age` is the previous N samples to average over. |
| 16 | 16 | // If zero value provided, it defaults to 30. |
| 17 | 17 | // |
| 18 | // `sbCh` is a start block receive channel. User suppose to send time.Now() | |
| 18 | // `sb` is a start block receive channel. User suppose to send time.Now() | |
| 19 | 19 | // to this channel on each iteration of a start block, right before actual job. |
| 20 | 20 | // The channel will be auto closed on bar shutdown event, so there is no need |
| 21 | 21 | // to close from user side. |
| 22 | 22 | // |
| 23 | 23 | // `wcc` optional WC config |
| 24 | func ETA(style int, age float64, sbCh chan time.Time, wcc ...WC) Decorator { | |
| 25 | if sbCh == nil { | |
| 24 | func ETA(style int, age float64, sb chan time.Time, wcc ...WC) Decorator { | |
| 25 | return MovingAverageETA(style, ewma.NewMovingAverage(age), sb, wcc...) | |
| 26 | } | |
| 27 | ||
| 28 | // MovingAverageETA returns ETA decorator, which relies on MovingAverage implementation to calculate average. | |
| 29 | // Default ETA decorator relies on ewma implementation. However you're free to provide your own implementation | |
| 30 | // or use alternative one, which is provided by decor package: | |
| 31 | // | |
| 32 | // decor.MovingAverageETA(decor.ET_STYLE_GO, decor.NewMedianMovingAverage(), sb) | |
| 33 | func MovingAverageETA(style int, average MovingAverage, sb chan time.Time, wcc ...WC) Decorator { | |
| 34 | if sb == nil { | |
| 26 | 35 | panic("start block channel must not be nil") |
| 27 | 36 | } |
| 28 | 37 | var wc WC |
| 30 | 39 | wc = widthConf |
| 31 | 40 | } |
| 32 | 41 | wc.BuildFormat() |
| 33 | if age == .0 { | |
| 34 | age = ewma.AVG_METRIC_AGE | |
| 35 | } | |
| 36 | d := &ewmaETA{ | |
| 42 | d := &movingAverageETA{ | |
| 37 | 43 | style: style, |
| 38 | 44 | wc: wc, |
| 39 | mAverage: ewma.NewMovingAverage(age), | |
| 40 | sbReceiver: sbCh, | |
| 45 | average: average, | |
| 46 | sbReceiver: sb, | |
| 41 | 47 | sbStreamer: make(chan time.Time), |
| 42 | 48 | } |
| 43 | 49 | go d.serve() |
| 44 | 50 | return d |
| 45 | 51 | } |
| 46 | 52 | |
| 47 | type ewmaETA struct { | |
| 53 | type movingAverageETA struct { | |
| 48 | 54 | style int |
| 49 | 55 | wc WC |
| 50 | mAverage ewma.MovingAverage | |
| 56 | average ewma.MovingAverage | |
| 51 | 57 | sbReceiver chan time.Time |
| 52 | 58 | sbStreamer chan time.Time |
| 53 | 59 | onComplete *struct { |
| 56 | 62 | } |
| 57 | 63 | } |
| 58 | 64 | |
| 59 | func (s *ewmaETA) Decor(st *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 65 | func (s *movingAverageETA) Decor(st *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 60 | 66 | if st.Completed && s.onComplete != nil { |
| 61 | 67 | return s.onComplete.wc.FormatMsg(s.onComplete.msg, widthAccumulator, widthDistributor) |
| 62 | 68 | } |
| 63 | 69 | |
| 64 | v := internal.Round(s.mAverage.Value()) | |
| 70 | v := internal.Round(s.average.Value()) | |
| 65 | 71 | if math.IsInf(v, 0) || math.IsNaN(v) { |
| 66 | 72 | v = .0 |
| 67 | 73 | } |
| 85 | 91 | return s.wc.FormatMsg(str, widthAccumulator, widthDistributor) |
| 86 | 92 | } |
| 87 | 93 | |
| 88 | func (s *ewmaETA) NextAmount(n int) { | |
| 94 | func (s *movingAverageETA) NextAmount(n int) { | |
| 89 | 95 | sb := <-s.sbStreamer |
| 90 | 96 | lastBlockTime := time.Since(sb) |
| 91 | 97 | lastItemEstimate := float64(lastBlockTime) / float64(n) |
| 92 | s.mAverage.Add(lastItemEstimate) | |
| 98 | s.average.Add(lastItemEstimate) | |
| 93 | 99 | } |
| 94 | 100 | |
| 95 | func (s *ewmaETA) OnCompleteMessage(msg string, wcc ...WC) { | |
| 101 | func (s *movingAverageETA) OnCompleteMessage(msg string, wcc ...WC) { | |
| 96 | 102 | var wc WC |
| 97 | 103 | for _, widthConf := range wcc { |
| 98 | 104 | wc = widthConf |
| 104 | 110 | }{msg, wc} |
| 105 | 111 | } |
| 106 | 112 | |
| 107 | func (s *ewmaETA) Shutdown() { | |
| 113 | func (s *movingAverageETA) Shutdown() { | |
| 108 | 114 | close(s.sbReceiver) |
| 109 | 115 | } |
| 110 | 116 | |
| 111 | func (s *ewmaETA) serve() { | |
| 117 | func (s *movingAverageETA) serve() { | |
| 112 | 118 | for now := range s.sbReceiver { |
| 113 | 119 | s.sbStreamer <- now |
| 114 | 120 | } |
| 0 | package decor | |
| 1 | ||
| 2 | import ( | |
| 3 | "sort" | |
| 4 | ||
| 5 | "github.com/VividCortex/ewma" | |
| 6 | ) | |
| 7 | ||
| 8 | // MovingAverage is the interface that computes a moving average over a time- | |
| 9 | // series stream of numbers. The average may be over a window or exponentially | |
| 10 | // decaying. | |
| 11 | type MovingAverage interface { | |
| 12 | Add(float64) | |
| 13 | Value() float64 | |
| 14 | Set(float64) | |
| 15 | } | |
| 16 | ||
| 17 | type median struct { | |
| 18 | window [3]float64 | |
| 19 | dst []float64 | |
| 20 | } | |
| 21 | ||
| 22 | type sortable []float64 | |
| 23 | ||
| 24 | func (s sortable) Len() int { return len(s) } | |
| 25 | func (s sortable) Swap(i, j int) { s[i], s[j] = s[j], s[i] } | |
| 26 | func (s sortable) Less(i, j int) bool { return s[i] < s[j] } | |
| 27 | ||
| 28 | func (s *median) Add(v float64) { | |
| 29 | s.window[0], s.window[1] = s.window[1], s.window[2] | |
| 30 | s.window[2] = v | |
| 31 | } | |
| 32 | ||
| 33 | func (s *median) Value() float64 { | |
| 34 | copy(s.dst, s.window[:]) | |
| 35 | sort.Sort(sortable(s.dst)) | |
| 36 | return s.dst[1] | |
| 37 | } | |
| 38 | ||
| 39 | func (s *median) Set(value float64) { | |
| 40 | for i, _ := range s.window { | |
| 41 | s.window[i] = value | |
| 42 | } | |
| 43 | } | |
| 44 | ||
| 45 | // NewMedian is fixed last 3 samples median MovingAverage. | |
| 46 | func NewMedian() MovingAverage { | |
| 47 | return &median{ | |
| 48 | dst: make([]float64, 3), | |
| 49 | } | |
| 50 | } | |
| 51 | ||
| 52 | type medianEwma struct { | |
| 53 | MovingAverage | |
| 54 | median MovingAverage | |
| 55 | } | |
| 56 | ||
| 57 | func (s medianEwma) Add(v float64) { | |
| 58 | s.median.Add(v) | |
| 59 | s.MovingAverage.Add(s.median.Value()) | |
| 60 | } | |
| 61 | ||
| 62 | // NewMedianEwma is ewma based MovingAverage, which gets its values from median MovingAverage. | |
| 63 | func NewMedianEwma(age ...float64) MovingAverage { | |
| 64 | return medianEwma{ | |
| 65 | MovingAverage: ewma.NewMovingAverage(age...), | |
| 66 | median: NewMedian(), | |
| 67 | } | |
| 68 | } |
| 124 | 124 | // `age` is the previous N samples to average over. |
| 125 | 125 | // If zero value provided, it defaults to 30. |
| 126 | 126 | // |
| 127 | // `sbCh` is a start block receive channel. User suppose to send time.Now() | |
| 127 | // `sb` is a start block receive channel. User suppose to send time.Now() | |
| 128 | 128 | // to this channel on each iteration of a start block, right before actual job. |
| 129 | 129 | // The channel will be auto closed on bar shutdown event, so there is no need |
| 130 | 130 | // to close from user side. |
| 134 | 134 | // unitFormat example: |
| 135 | 135 | // |
| 136 | 136 | // "%.1f" = "1.0" or "% .1f" = "1.0" |
| 137 | func SpeedNoUnit(unitFormat string, age float64, sbCh chan time.Time, wcc ...WC) Decorator { | |
| 138 | return speed(0, unitFormat, age, sbCh, wcc...) | |
| 137 | func SpeedNoUnit(unitFormat string, age float64, sb chan time.Time, wcc ...WC) Decorator { | |
| 138 | return MovingAverageSpeed(0, unitFormat, ewma.NewMovingAverage(age), sb, wcc...) | |
| 139 | 139 | } |
| 140 | 140 | |
| 141 | 141 | // SpeedKibiByte returns human friendly I/O operation speed decorator, |
| 145 | 145 | // `age` is the previous N samples to average over. |
| 146 | 146 | // If zero value provided, it defaults to 30. |
| 147 | 147 | // |
| 148 | // `sbCh` is a start block receive channel. User suppose to send time.Now() | |
| 148 | // `sb` is a start block receive channel. User suppose to send time.Now() | |
| 149 | 149 | // to this channel on each iteration of a start block, right before actual job. |
| 150 | 150 | // The channel will be auto closed on bar shutdown event, so there is no need |
| 151 | 151 | // to close from user side. |
| 155 | 155 | // unitFormat example: |
| 156 | 156 | // |
| 157 | 157 | // "%.1f" = "1.0MiB/s" or "% .1f" = "1.0 MiB/s" |
| 158 | func SpeedKibiByte(unitFormat string, age float64, sbCh chan time.Time, wcc ...WC) Decorator { | |
| 159 | return speed(unitKiB, unitFormat, age, sbCh, wcc...) | |
| 158 | func SpeedKibiByte(unitFormat string, age float64, sb chan time.Time, wcc ...WC) Decorator { | |
| 159 | return MovingAverageSpeed(UnitKiB, unitFormat, ewma.NewMovingAverage(age), sb, wcc...) | |
| 160 | 160 | } |
| 161 | 161 | |
| 162 | 162 | // SpeedKiloByte returns human friendly I/O operation speed decorator, |
| 166 | 166 | // `age` is the previous N samples to average over. |
| 167 | 167 | // If zero value provided, it defaults to 30. |
| 168 | 168 | // |
| 169 | // `sbCh` is a start block receive channel. User suppose to send time.Now() | |
| 169 | // `sb` is a start block receive channel. User suppose to send time.Now() | |
| 170 | 170 | // to this channel on each iteration of a start block, right before actual job. |
| 171 | 171 | // The channel will be auto closed on bar shutdown event, so there is no need |
| 172 | 172 | // to close from user side. |
| 176 | 176 | // unitFormat example: |
| 177 | 177 | // |
| 178 | 178 | // "%.1f" = "1.0MB/s" or "% .1f" = "1.0 MB/s" |
| 179 | func SpeedKiloByte(unitFormat string, age float64, sbCh chan time.Time, wcc ...WC) Decorator { | |
| 180 | return speed(unitKB, unitFormat, age, sbCh, wcc...) | |
| 181 | } | |
| 182 | ||
| 183 | func speed(unit int, unitFormat string, age float64, sbCh chan time.Time, wcc ...WC) Decorator { | |
| 184 | if sbCh == nil { | |
| 179 | func SpeedKiloByte(unitFormat string, age float64, sb chan time.Time, wcc ...WC) Decorator { | |
| 180 | return MovingAverageSpeed(UnitKB, unitFormat, ewma.NewMovingAverage(age), sb, wcc...) | |
| 181 | } | |
| 182 | ||
| 183 | // MovingAverageSpeed returns Speed decorator, which relies on MovingAverage implementation to calculate average. | |
| 184 | // Default Speed decorator relies on ewma implementation. However you're free to provide your own implementation | |
| 185 | // or use alternative one, which is provided by decor package: | |
| 186 | // | |
| 187 | // decor.MovingAverageSpeed(decor.UnitKiB, "% .2f", decor.NewMedianMovingAverage(), sb) | |
| 188 | func MovingAverageSpeed(unit int, unitFormat string, average MovingAverage, sb chan time.Time, wcc ...WC) Decorator { | |
| 189 | if sb == nil { | |
| 185 | 190 | panic("start block channel must not be nil") |
| 186 | 191 | } |
| 187 | 192 | var wc WC |
| 189 | 194 | wc = widthConf |
| 190 | 195 | } |
| 191 | 196 | wc.BuildFormat() |
| 192 | if age == .0 { | |
| 193 | age = ewma.AVG_METRIC_AGE | |
| 194 | } | |
| 195 | d := &ewmaSpeed{ | |
| 197 | d := &movingAverageSpeed{ | |
| 196 | 198 | unit: unit, |
| 197 | 199 | unitFormat: unitFormat, |
| 198 | 200 | wc: wc, |
| 199 | mAverage: ewma.NewMovingAverage(age), | |
| 200 | sbReceiver: sbCh, | |
| 201 | average: average, | |
| 202 | sbReceiver: sb, | |
| 201 | 203 | sbStreamer: make(chan time.Time), |
| 202 | 204 | } |
| 203 | 205 | go d.serve() |
| 204 | 206 | return d |
| 205 | 207 | } |
| 206 | 208 | |
| 207 | type ewmaSpeed struct { | |
| 209 | type movingAverageSpeed struct { | |
| 208 | 210 | unit int |
| 209 | 211 | unitFormat string |
| 210 | 212 | wc WC |
| 211 | mAverage ewma.MovingAverage | |
| 213 | average ewma.MovingAverage | |
| 212 | 214 | sbReceiver chan time.Time |
| 213 | 215 | sbStreamer chan time.Time |
| 214 | 216 | onComplete *struct { |
| 217 | 219 | } |
| 218 | 220 | } |
| 219 | 221 | |
| 220 | func (s *ewmaSpeed) Decor(st *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 222 | func (s *movingAverageSpeed) Decor(st *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 221 | 223 | if st.Completed && s.onComplete != nil { |
| 222 | 224 | return s.onComplete.wc.FormatMsg(s.onComplete.msg, widthAccumulator, widthDistributor) |
| 223 | 225 | } |
| 224 | 226 | var str string |
| 225 | speed := s.mAverage.Value() | |
| 227 | speed := s.average.Value() | |
| 226 | 228 | switch s.unit { |
| 227 | case unitKiB: | |
| 229 | case UnitKiB: | |
| 228 | 230 | str = fmt.Sprintf(s.unitFormat, SpeedKiB(speed)) |
| 229 | case unitKB: | |
| 231 | case UnitKB: | |
| 230 | 232 | str = fmt.Sprintf(s.unitFormat, SpeedKB(speed)) |
| 231 | 233 | default: |
| 232 | 234 | str = fmt.Sprintf(s.unitFormat, speed) |
| 234 | 236 | return s.wc.FormatMsg(str, widthAccumulator, widthDistributor) |
| 235 | 237 | } |
| 236 | 238 | |
| 237 | func (s *ewmaSpeed) NextAmount(n int) { | |
| 239 | func (s *movingAverageSpeed) NextAmount(n int) { | |
| 238 | 240 | sb := <-s.sbStreamer |
| 239 | 241 | speed := float64(n) / time.Since(sb).Seconds() |
| 240 | s.mAverage.Add(speed) | |
| 241 | } | |
| 242 | ||
| 243 | func (s *ewmaSpeed) OnCompleteMessage(msg string, wcc ...WC) { | |
| 242 | s.average.Add(speed) | |
| 243 | } | |
| 244 | ||
| 245 | func (s *movingAverageSpeed) OnCompleteMessage(msg string, wcc ...WC) { | |
| 244 | 246 | var wc WC |
| 245 | 247 | for _, widthConf := range wcc { |
| 246 | 248 | wc = widthConf |
| 252 | 254 | }{msg, wc} |
| 253 | 255 | } |
| 254 | 256 | |
| 255 | func (s *ewmaSpeed) Shutdown() { | |
| 257 | func (s *movingAverageSpeed) Shutdown() { | |
| 256 | 258 | close(s.sbReceiver) |
| 257 | 259 | } |
| 258 | 260 | |
| 259 | func (s *ewmaSpeed) serve() { | |
| 261 | func (s *movingAverageSpeed) serve() { | |
| 260 | 262 | for now := range s.sbReceiver { |
| 261 | 263 | s.sbStreamer <- now |
| 262 | 264 | } |
| 49 | 49 | decor.CountersKibiByte("% 6.1f / % 6.1f"), |
| 50 | 50 | ), |
| 51 | 51 | mpb.AppendDecorators( |
| 52 | decor.ETA(decor.ET_STYLE_MMSS, 600, sbEta), | |
| 52 | decor.MovingAverageETA(decor.ET_STYLE_MMSS, decor.NewMedianEwma(300), sbEta), | |
| 53 | 53 | decor.Name(" ] "), |
| 54 | decor.SpeedKibiByte("% .2f", 600, sbSpeed), | |
| 54 | decor.MovingAverageSpeed(decor.UnitKiB, "% .2f", decor.NewMedianEwma(300), sbSpeed), | |
| 55 | 55 | ), |
| 56 | 56 | ) |
| 57 | 57 |