diff --git a/bar.go b/bar.go index e875975..1996dd2 100644 --- a/bar.go +++ b/bar.go @@ -58,8 +58,6 @@ removeOnComplete bool barClearOnComplete bool completeFlushed bool - startTime time.Time - timeElapsed time.Duration aDecorators []decor.Decorator pDecorators []decor.Decorator amountReceivers []decor.AmountReceiver @@ -140,13 +138,15 @@ } // ProxyReader wrapper for io operations, like io.Copy -func (b *Bar) ProxyReader(r io.Reader, startBlock ...chan<- time.Time) *Reader { +// +// `r` io.Reader to be wrapped +// +// `sbChannels` optional start block channels +func (b *Bar) ProxyReader(r io.Reader, sbChannels ...chan<- time.Time) *Reader { proxyReader := &Reader{ - Reader: r, - bar: b, - } - if len(startBlock) > 0 { - proxyReader.startBlockCh = startBlock[0] + Reader: r, + bar: b, + sbChannels: sbChannels, } return proxyReader } @@ -251,7 +251,6 @@ for _, ar := range s.amountReceivers { ar.NextAmount(n) } - s.timeElapsed = time.Since(s.startTime) }: case <-b.done: } @@ -266,7 +265,6 @@ func (b *Bar) serve(wg *sync.WaitGroup, s *bState, cancel <-chan struct{}) { defer wg.Done() - s.startTime = time.Now() for { select { case op := <-b.operateState: @@ -415,12 +413,10 @@ func newStatistics(s *bState) *decor.Statistics { return &decor.Statistics{ - ID: s.id, - Completed: s.completeFlushed, - Total: s.total, - Current: s.current, - StartTime: s.startTime, - TimeElapsed: s.timeElapsed, + ID: s.id, + Completed: s.completeFlushed, + Total: s.total, + Current: s.current, } } diff --git a/decor/counters.go b/decor/counters.go index 41a1975..66fd04a 100644 --- a/decor/counters.go +++ b/decor/counters.go @@ -135,3 +135,58 @@ io.WriteString(st, res) } + +// CountersNoUnit returns raw counters decorator +// +// `pairFormat` printf compatible verbs for current and total, like "%f" or "%d" +// +// `wcc` optional WC config +func CountersNoUnit(pairFormat string, wcc ...WC) Decorator { + return counters(0, pairFormat, wcc...) +} + +// CountersKibiByte returns human friendly byte counters decorator, where counters unit is multiple by 1024. +// +// `pairFormat` printf compatible verbs for current and total, like "%f" or "%d" +// +// `wcc` optional WC config +// +// pairFormat example: +// +// "%.1f / %.1f" = "1.0MiB / 12.0MiB" or "% .1f / % .1f" = "1.0 MiB / 12.0 MiB" +func CountersKibiByte(pairFormat string, wcc ...WC) Decorator { + return counters(unitKiB, pairFormat, wcc...) +} + +// CountersKiloByte returns human friendly byte counters decorator, where counters unit is multiple by 1000. +// +// `pairFormat` printf compatible verbs for current and total, like "%f" or "%d" +// +// `wcc` optional WC config +// +// pairFormat example: +// +// "%.1f / %.1f" = "1.0MB / 12.0MB" or "% .1f / % .1f" = "1.0 MB / 12.0 MB" +func CountersKiloByte(pairFormat string, wcc ...WC) Decorator { + return counters(unitKB, pairFormat, wcc...) +} + +func counters(unit int, pairFormat string, wcc ...WC) Decorator { + var wc WC + for _, widthConf := range wcc { + wc = widthConf + } + wc.BuildFormat() + return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { + var str string + switch unit { + case unitKiB: + str = fmt.Sprintf(pairFormat, CounterKiB(s.Current), CounterKiB(s.Total)) + case unitKB: + str = fmt.Sprintf(pairFormat, CounterKB(s.Current), CounterKB(s.Total)) + default: + str = fmt.Sprintf(pairFormat, s.Current, s.Total) + } + return wc.FormatMsg(str, widthAccumulator, widthDistributor) + }) +} diff --git a/decor/decorator.go b/decor/decorator.go new file mode 100644 index 0000000..32552c6 --- /dev/null +++ b/decor/decorator.go @@ -0,0 +1,143 @@ +package decor + +import ( + "fmt" + "unicode/utf8" +) + +const ( + // DidentRight bit specifies identation direction. + // |foo |b | With DidentRight + // | foo| b| Without DidentRight + DidentRight = 1 << iota + + // DextraSpace bit adds extra space, makes sense with DSyncWidth only. + // When DidentRight bit set, the space will be added to the right, + // otherwise to the left. + DextraSpace + + // DSyncWidth bit enables same column width synchronization. + // Effective with multiple bars only. + DSyncWidth + + // DSyncWidthR is shortcut for DSyncWidth|DidentRight + DSyncWidthR = DSyncWidth | DidentRight + + // DSyncSpace is shortcut for DSyncWidth|DextraSpace + DSyncSpace = DSyncWidth | DextraSpace + + // DSyncSpaceR is shortcut for DSyncWidth|DextraSpace|DidentRight + DSyncSpaceR = DSyncWidth | DextraSpace | DidentRight +) + +const ( + ET_STYLE_GO = iota + ET_STYLE_HHMMSS + ET_STYLE_HHMM + ET_STYLE_MMSS +) + +// Statistics is a struct, which gets passed to a Decorator. +type Statistics struct { + ID int + Completed bool + Total int64 + Current int64 +} + +// Decorator is an interface with one method: +// +// Decor(st *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string +// +// All decorators in this package implement this interface. +type Decorator interface { + Decor(*Statistics, chan<- int, <-chan int) string +} + +// OnCompleteMessenger is an interface with one method: +// +// OnCompleteMessage(message string, wc ...WC) +// +// Decorators implementing this interface suppose to return provided string on complete event. +type OnCompleteMessenger interface { + OnCompleteMessage(string, ...WC) +} + +type AmountReceiver interface { + NextAmount(int) +} + +type ShutdownListener interface { + Shutdown() +} + +// DecoratorFunc is an adapter for Decorator interface +type DecoratorFunc func(*Statistics, chan<- int, <-chan int) string + +func (f DecoratorFunc) Decor(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { + return f(s, widthAccumulator, widthDistributor) +} + +// Global convenience shortcuts +var ( + WCSyncWidth = WC{C: DSyncWidth} + WCSyncWidthR = WC{C: DSyncWidthR} + WCSyncSpace = WC{C: DSyncSpace} + WCSyncSpaceR = WC{C: DSyncSpaceR} +) + +// WC is a struct with two public fields W and C, both of int type. +// W represents width and C represents bit set of width related config. +type WC struct { + W int + C int + format string +} + +// FormatMsg formats final message according to WC.W and WC.C. +// Should be called by any Decorator implementation. +func (wc WC) FormatMsg(msg string, widthAccumulator chan<- int, widthDistributor <-chan int) string { + if (wc.C & DSyncWidth) != 0 { + widthAccumulator <- utf8.RuneCountInString(msg) + max := <-widthDistributor + if max == 0 { + max = wc.W + } + if (wc.C & DextraSpace) != 0 { + max++ + } + return fmt.Sprintf(fmt.Sprintf(wc.format, max), msg) + } + return fmt.Sprintf(fmt.Sprintf(wc.format, wc.W), msg) +} + +// BuildFormat builds initial format according to WC.C +func (wc *WC) BuildFormat() { + wc.format = "%%" + if (wc.C & DidentRight) != 0 { + wc.format += "-" + } + wc.format += "%ds" +} + +// OnComplete returns decorator, which wraps provided decorator, with sole +// purpose to display provided message on complete event. +// +// `decorator` Decorator to wrap +// +// `message` message to display on complete event +// +// `wcc` optional WC config +func OnComplete(decorator Decorator, message string, wcc ...WC) Decorator { + if cm, ok := decorator.(OnCompleteMessenger); ok { + cm.OnCompleteMessage(message, wcc...) + return decorator + } + msgDecorator := Name(message, wcc...) + return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { + if s.Completed { + return msgDecorator.Decor(s, widthAccumulator, widthDistributor) + } + return decorator.Decor(s, widthAccumulator, widthDistributor) + }) +} diff --git a/decor/decorators.go b/decor/decorators.go deleted file mode 100644 index 71ae544..0000000 --- a/decor/decorators.go +++ /dev/null @@ -1,346 +0,0 @@ -package decor - -import ( - "fmt" - "math" - "time" - "unicode/utf8" -) - -const ( - // DidentRight bit specifies identation direction. - // |foo |b | With DidentRight - // | foo| b| Without DidentRight - DidentRight = 1 << iota - - // DextraSpace bit adds extra space, makes sense with DSyncWidth only. - // When DidentRight bit set, the space will be added to the right, - // otherwise to the left. - DextraSpace - - // DSyncWidth bit enables same column width synchronization. - // Effective with multiple bars only. - DSyncWidth - - // DSyncWidthR is shortcut for DSyncWidth|DidentRight - DSyncWidthR = DSyncWidth | DidentRight - - // DSyncSpace is shortcut for DSyncWidth|DextraSpace - DSyncSpace = DSyncWidth | DextraSpace - - // DSyncSpaceR is shortcut for DSyncWidth|DextraSpace|DidentRight - DSyncSpaceR = DSyncWidth | DextraSpace | DidentRight -) - -const ( - ET_STYLE_GO = iota - ET_STYLE_HHMMSS - ET_STYLE_HHMM - ET_STYLE_MMSS -) - -// Statistics is a struct, which gets passed to a Decorator. -type Statistics struct { - ID int - Completed bool - Total int64 - Current int64 - StartTime time.Time - TimeElapsed time.Duration -} - -// Decorator is an interface with one method: -// -// Decor(st *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string -// -// All decorators in this package implement this interface. -type Decorator interface { - Decor(*Statistics, chan<- int, <-chan int) string -} - -// OnCompleteMessenger is an interface with one method: -// -// OnCompleteMessage(message string, wc ...WC) -// -// Decorators implementing this interface suppose to return provided string on complete event. -type OnCompleteMessenger interface { - OnCompleteMessage(string, ...WC) -} - -type AmountReceiver interface { - NextAmount(int) -} - -type ShutdownListener interface { - Shutdown() -} - -// DecoratorFunc is an adapter for Decorator interface -type DecoratorFunc func(*Statistics, chan<- int, <-chan int) string - -func (f DecoratorFunc) Decor(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { - return f(s, widthAccumulator, widthDistributor) -} - -// WC is a struct with two public fields W and C, both of int type. -// W represents width and C represents bit set of width related config. -type WC struct { - W int - C int - format string -} - -// FormatMsg formats final message according to WC.W and WC.C. -// Should be called by any Decorator implementation. -func (wc WC) FormatMsg(msg string, widthAccumulator chan<- int, widthDistributor <-chan int) string { - if (wc.C & DSyncWidth) != 0 { - widthAccumulator <- utf8.RuneCountInString(msg) - max := <-widthDistributor - if max == 0 { - max = wc.W - } - if (wc.C & DextraSpace) != 0 { - max++ - } - return fmt.Sprintf(fmt.Sprintf(wc.format, max), msg) - } - return fmt.Sprintf(fmt.Sprintf(wc.format, wc.W), msg) -} - -// BuildFormat builds initial format according to WC.C -func (wc *WC) BuildFormat() { - wc.format = "%%" - if (wc.C & DidentRight) != 0 { - wc.format += "-" - } - wc.format += "%ds" -} - -// Global convenience shortcuts -var ( - WCSyncWidth = WC{C: DSyncWidth} - WCSyncWidthR = WC{C: DSyncWidthR} - WCSyncSpace = WC{C: DSyncSpace} - WCSyncSpaceR = WC{C: DSyncSpaceR} -) - -// OnComplete returns decorator, which wraps provided decorator, with sole -// purpose to display provided message on complete event. -// -// `decorator` Decorator to wrap -// -// `message` message to display on complete event -// -// `wc` optional WC config -func OnComplete(decorator Decorator, message string, wc ...WC) Decorator { - if cm, ok := decorator.(OnCompleteMessenger); ok { - cm.OnCompleteMessage(message, wc...) - return decorator - } - msgDecorator := Name(message, wc...) - return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { - if s.Completed { - return msgDecorator.Decor(s, widthAccumulator, widthDistributor) - } - return decorator.Decor(s, widthAccumulator, widthDistributor) - }) -} - -// StaticName returns name decorator. -// -// `name` string to display -// -// `wc` optional WC config -func StaticName(name string, wc ...WC) Decorator { - return Name(name, wc...) -} - -// Name returns name decorator. -// -// `name` string to display -// -// `wc` optional WC config -func Name(name string, wc ...WC) Decorator { - var wc0 WC - if len(wc) > 0 { - wc0 = wc[0] - } - wc0.BuildFormat() - return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { - return wc0.FormatMsg(name, widthAccumulator, widthDistributor) - }) -} - -// CountersNoUnit returns raw counters decorator -// -// `pairFormat` printf compatible verbs for current and total, like "%f" or "%d" -// -// `wc` optional WC config -func CountersNoUnit(pairFormat string, wc ...WC) Decorator { - return counters(pairFormat, 0, wc...) -} - -// CountersKibiByte returns human friendly byte counters decorator, where counters unit is multiple by 1024. -// -// `pairFormat` printf compatible verbs for current and total, like "%f" or "%d" -// -// `wc` optional WC config -// -// pairFormat example: -// -// "%.1f / %.1f" = "1.0MiB / 12.0MiB" or "% .1f / % .1f" = "1.0 MiB / 12.0 MiB" -func CountersKibiByte(pairFormat string, wc ...WC) Decorator { - return counters(pairFormat, unitKiB, wc...) -} - -// CountersKiloByte returns human friendly byte counters decorator, where counters unit is multiple by 1000. -// -// `pairFormat` printf compatible verbs for current and total, like "%f" or "%d" -// -// `wc` optional WC config -// -// pairFormat example: -// -// "%.1f / %.1f" = "1.0MB / 12.0MB" or "% .1f / % .1f" = "1.0 MB / 12.0 MB" -func CountersKiloByte(pairFormat string, wc ...WC) Decorator { - return counters(pairFormat, unitKB, wc...) -} - -func counters(pairFormat string, unit int, wc ...WC) Decorator { - var wc0 WC - if len(wc) > 0 { - wc0 = wc[0] - } - wc0.BuildFormat() - return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { - var str string - switch unit { - case unitKiB: - str = fmt.Sprintf(pairFormat, CounterKiB(s.Current), CounterKiB(s.Total)) - case unitKB: - str = fmt.Sprintf(pairFormat, CounterKB(s.Current), CounterKB(s.Total)) - default: - str = fmt.Sprintf(pairFormat, s.Current, s.Total) - } - return wc0.FormatMsg(str, widthAccumulator, widthDistributor) - }) -} - -// Elapsed returns elapsed time decorator. -// -// `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] -// -// `wc` optional WC config -func Elapsed(style int, wc ...WC) Decorator { - var wc0 WC - if len(wc) > 0 { - wc0 = wc[0] - } - wc0.BuildFormat() - return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { - var str string - hours := int64((s.TimeElapsed / time.Hour) % 60) - minutes := int64((s.TimeElapsed / time.Minute) % 60) - seconds := int64((s.TimeElapsed / time.Second) % 60) - - switch style { - case ET_STYLE_GO: - str = fmt.Sprint(time.Duration(s.TimeElapsed.Seconds()) * time.Second) - case ET_STYLE_HHMMSS: - str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) - case ET_STYLE_HHMM: - str = fmt.Sprintf("%02d:%02d", hours, minutes) - case ET_STYLE_MMSS: - str = fmt.Sprintf("%02d:%02d", minutes, seconds) - } - return wc0.FormatMsg(str, widthAccumulator, widthDistributor) - }) -} - -// Percentage returns percentage decorator. -// -// `wc` optional WC config -func Percentage(wc ...WC) Decorator { - var wc0 WC - if len(wc) > 0 { - wc0 = wc[0] - } - wc0.BuildFormat() - return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { - str := fmt.Sprintf("%d %%", CalcPercentage(s.Total, s.Current, 100)) - return wc0.FormatMsg(str, widthAccumulator, widthDistributor) - }) -} - -// CalcPercentage is a helper function, to calculate percentage. -func CalcPercentage(total, current, width int64) int64 { - if total <= 0 { - return 0 - } - p := float64(width*current) / float64(total) - return int64(round(p)) -} - -// SpeedNoUnit returns raw I/O operation speed decorator. -// -// `unitFormat` printf compatible verb for value, like "%f" or "%d" -// -// `wc` optional WC config -// -// unitFormat example: -// -// "%.1f" = "1.0" or "% .1f" = "1.0" -func SpeedNoUnit(unitFormat string, wc ...WC) Decorator { - return speed(unitFormat, 0, wc...) -} - -// SpeedKibiByte returns human friendly I/O operation speed decorator, -// -// `unitFormat` printf compatible verb for value, like "%f" or "%d" -// -// `wc` optional WC config -// -// unitFormat example: -// -// "%.1f" = "1.0MiB/s" or "% .1f" = "1.0 MiB/s" -func SpeedKibiByte(unitFormat string, wc ...WC) Decorator { - return speed(unitFormat, unitKiB, wc...) -} - -// SpeedKiloByte returns human friendly I/O operation speed decorator, -// -// `unitFormat` printf compatible verb for value, like "%f" or "%d" -// -// `wc` optional WC config -// -// unitFormat example: -// -// "%.1f" = "1.0MB/s" or "% .1f" = "1.0 MB/s" -func SpeedKiloByte(unitFormat string, wc ...WC) Decorator { - return speed(unitFormat, unitKB, wc...) -} - -func speed(unitFormat string, unit int, wc ...WC) Decorator { - var wc0 WC - if len(wc) > 0 { - wc0 = wc[0] - } - wc0.BuildFormat() - return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { - var str string - speed := float64(s.Current) / s.TimeElapsed.Seconds() - if math.IsNaN(speed) || math.IsInf(speed, 0) { - speed = .0 - } - - switch unit { - case unitKiB: - str = fmt.Sprintf(unitFormat, SpeedKiB(speed)) - case unitKB: - str = fmt.Sprintf(unitFormat, SpeedKB(speed)) - default: - str = fmt.Sprintf(unitFormat, speed) - } - return wc0.FormatMsg(str, widthAccumulator, widthDistributor) - }) -} diff --git a/decor/elapsed.go b/decor/elapsed.go new file mode 100644 index 0000000..5087f74 --- /dev/null +++ b/decor/elapsed.go @@ -0,0 +1,39 @@ +package decor + +import ( + "fmt" + "time" +) + +// Elapsed returns elapsed time decorator. +// +// `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] +// +// `wcc` optional WC config +func Elapsed(style int, wcc ...WC) Decorator { + var wc WC + for _, widthConf := range wcc { + wc = widthConf + } + wc.BuildFormat() + startTime := time.Now() + return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { + var str string + timeElapsed := time.Since(startTime) + hours := int64((timeElapsed / time.Hour) % 60) + minutes := int64((timeElapsed / time.Minute) % 60) + seconds := int64((timeElapsed / time.Second) % 60) + + switch style { + case ET_STYLE_GO: + str = fmt.Sprint(time.Duration(timeElapsed.Seconds()) * time.Second) + case ET_STYLE_HHMMSS: + str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) + case ET_STYLE_HHMM: + str = fmt.Sprintf("%02d:%02d", hours, minutes) + case ET_STYLE_MMSS: + str = fmt.Sprintf("%02d:%02d", minutes, seconds) + } + return wc.FormatMsg(str, widthAccumulator, widthDistributor) + }) +} diff --git a/decor/eta.go b/decor/eta.go index eb2fdda..3bb7fa1 100644 --- a/decor/eta.go +++ b/decor/eta.go @@ -11,43 +11,42 @@ // // `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] // -// `age` is related to the decay factor alpha by the formula given for the DECAY constant. -// It signifies the average age of the samples as time goes to infinity. Basically age is -// the previous N samples to average over. If zero value provided, it defaults to 30. +// `age` is the previous N samples to average over. +// If zero value provided, it defaults to 30. // -// `startBlock` is a time.Time receive channel. User suppose to send time.Now() -// to this channel on each iteration of block start, right before actual job. -// The channel will be closed automatically on bar shutdown event, so there is -// no need to close from user side. +// `sbCh` is a start block receive channel. User suppose to send time.Now() +// to this channel on each iteration of a start block, right before actual job. +// The channel will be auto closed on bar shutdown event, so there is no need +// to close from user side. // -// `wc` optional WC config -func ETA(style int, age float64, startBlock chan time.Time, wc ...WC) Decorator { - var wc0 WC - if len(wc) > 0 { - wc0 = wc[0] +// `wcc` optional WC config +func ETA(style int, age float64, sbCh chan time.Time, wcc ...WC) Decorator { + var wc WC + for _, widthConf := range wcc { + wc = widthConf } - wc0.BuildFormat() + wc.BuildFormat() if age == .0 { age = ewma.AVG_METRIC_AGE } - eta := &ewmaETA{ - mAverage: ewma.NewMovingAverage(age), - startBlockReceiver: startBlock, - startBlockStreamer: make(chan time.Time), - style: style, - wc: wc0, + d := &ewmaETA{ + style: style, + wc: wc, + mAverage: ewma.NewMovingAverage(age), + sbReceiver: sbCh, + sbStreamer: make(chan time.Time), } - go eta.serve() - return eta + go d.serve() + return d } type ewmaETA struct { - mAverage ewma.MovingAverage - startBlockReceiver chan time.Time - startBlockStreamer chan time.Time - style int - wc WC - onComplete *struct { + style int + wc WC + mAverage ewma.MovingAverage + sbReceiver chan time.Time + sbStreamer chan time.Time + onComplete *struct { msg string wc WC } @@ -79,30 +78,30 @@ } func (s *ewmaETA) NextAmount(n int) { - sb := <-s.startBlockStreamer + sb := <-s.sbStreamer lastBlockTime := time.Since(sb) lastItemEstimate := float64(lastBlockTime) / float64(n) s.mAverage.Add(lastItemEstimate) } -func (s *ewmaETA) OnCompleteMessage(msg string, wc ...WC) { - var wc0 WC - if len(wc) > 0 { - wc0 = wc[0] +func (s *ewmaETA) OnCompleteMessage(msg string, wcc ...WC) { + var wc WC + for _, widthConf := range wcc { + wc = widthConf } - wc0.BuildFormat() + wc.BuildFormat() s.onComplete = &struct { msg string wc WC - }{msg, wc0} + }{msg, wc} } func (s *ewmaETA) Shutdown() { - close(s.startBlockReceiver) + close(s.sbReceiver) } func (s *ewmaETA) serve() { - for now := range s.startBlockReceiver { - s.startBlockStreamer <- now + for now := range s.sbReceiver { + s.sbStreamer <- now } } diff --git a/decor/name.go b/decor/name.go new file mode 100644 index 0000000..2bb48b2 --- /dev/null +++ b/decor/name.go @@ -0,0 +1,26 @@ +package decor + +// StaticName returns name decorator. +// +// `name` string to display +// +// `wcc` optional WC config +func StaticName(name string, wcc ...WC) Decorator { + return Name(name, wcc...) +} + +// Name returns name decorator. +// +// `name` string to display +// +// `wcc` optional WC config +func Name(name string, wcc ...WC) Decorator { + var wc WC + for _, widthConf := range wcc { + wc = widthConf + } + wc.BuildFormat() + return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { + return wc.FormatMsg(name, widthAccumulator, widthDistributor) + }) +} diff --git a/decor/percentage.go b/decor/percentage.go new file mode 100644 index 0000000..474826c --- /dev/null +++ b/decor/percentage.go @@ -0,0 +1,27 @@ +package decor + +import "fmt" + +// Percentage returns percentage decorator. +// +// `wcc` optional WC config +func Percentage(wcc ...WC) Decorator { + var wc WC + for _, widthConf := range wcc { + wc = widthConf + } + wc.BuildFormat() + return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { + str := fmt.Sprintf("%d %%", CalcPercentage(s.Total, s.Current, 100)) + return wc.FormatMsg(str, widthAccumulator, widthDistributor) + }) +} + +// CalcPercentage is a helper function, to calculate percentage. +func CalcPercentage(total, current, width int64) int64 { + if total <= 0 { + return 0 + } + p := float64(width*current) / float64(total) + return int64(round(p)) +} diff --git a/decor/speed.go b/decor/speed.go index dc70c43..01f6c2b 100644 --- a/decor/speed.go +++ b/decor/speed.go @@ -5,6 +5,9 @@ "io" "strconv" "strings" + "time" + + "github.com/VividCortex/ewma" ) type SpeedKiB float64 @@ -114,3 +117,145 @@ io.WriteString(st, res) } + +// SpeedNoUnit returns raw I/O operation speed decorator. +// +// `unitFormat` printf compatible verb for value, like "%f" or "%d" +// +// `age` is the previous N samples to average over. +// If zero value provided, it defaults to 30. +// +// `sbCh` is a start block receive channel. User suppose to send time.Now() +// to this channel on each iteration of a start block, right before actual job. +// The channel will be auto closed on bar shutdown event, so there is no need +// to close from user side. +// +// `wcc` optional WC config +// +// unitFormat example: +// +// "%.1f" = "1.0" or "% .1f" = "1.0" +func SpeedNoUnit(unitFormat string, age float64, sbCh chan time.Time, wcc ...WC) Decorator { + return speed(0, unitFormat, age, sbCh, wcc...) +} + +// SpeedKibiByte returns human friendly I/O operation speed decorator, +// +// `unitFormat` printf compatible verb for value, like "%f" or "%d" +// +// `age` is the previous N samples to average over. +// If zero value provided, it defaults to 30. +// +// `sbCh` is a start block receive channel. User suppose to send time.Now() +// to this channel on each iteration of a start block, right before actual job. +// The channel will be auto closed on bar shutdown event, so there is no need +// to close from user side. +// +// `wcc` optional WC config +// +// unitFormat example: +// +// "%.1f" = "1.0MiB/s" or "% .1f" = "1.0 MiB/s" +func SpeedKibiByte(unitFormat string, age float64, sbCh chan time.Time, wcc ...WC) Decorator { + return speed(unitKiB, unitFormat, age, sbCh, wcc...) +} + +// SpeedKiloByte returns human friendly I/O operation speed decorator, +// +// `unitFormat` printf compatible verb for value, like "%f" or "%d" +// +// `age` is the previous N samples to average over. +// If zero value provided, it defaults to 30. +// +// `sbCh` is a start block receive channel. User suppose to send time.Now() +// to this channel on each iteration of a start block, right before actual job. +// The channel will be auto closed on bar shutdown event, so there is no need +// to close from user side. +// +// `wcc` optional WC config +// +// unitFormat example: +// +// "%.1f" = "1.0MB/s" or "% .1f" = "1.0 MB/s" +func SpeedKiloByte(unitFormat string, age float64, sbCh chan time.Time, wcc ...WC) Decorator { + return speed(unitKB, unitFormat, age, sbCh, wcc...) +} + +func speed(unit int, unitFormat string, age float64, sbCh chan time.Time, wcc ...WC) Decorator { + var wc WC + for _, widthConf := range wcc { + wc = widthConf + } + wc.BuildFormat() + if age == .0 { + age = ewma.AVG_METRIC_AGE + } + d := &ewmaSpeed{ + unit: unit, + unitFormat: unitFormat, + wc: wc, + mAverage: ewma.NewMovingAverage(age), + sbReceiver: sbCh, + sbStreamer: make(chan time.Time), + } + go d.serve() + return d +} + +type ewmaSpeed struct { + unit int + unitFormat string + wc WC + mAverage ewma.MovingAverage + sbReceiver chan time.Time + sbStreamer chan time.Time + onComplete *struct { + msg string + wc WC + } +} + +func (s *ewmaSpeed) Decor(st *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { + if st.Completed && s.onComplete != nil { + return s.onComplete.wc.FormatMsg(s.onComplete.msg, widthAccumulator, widthDistributor) + } + var str string + speed := round(s.mAverage.Value()) + switch s.unit { + case unitKiB: + str = fmt.Sprintf(s.unitFormat, SpeedKiB(speed)) + case unitKB: + str = fmt.Sprintf(s.unitFormat, SpeedKB(speed)) + default: + str = fmt.Sprintf(s.unitFormat, speed) + } + return s.wc.FormatMsg(str, widthAccumulator, widthDistributor) +} + +func (s *ewmaSpeed) NextAmount(n int) { + sb := <-s.sbStreamer + speed := float64(n) / time.Since(sb).Seconds() + s.mAverage.Add(speed) +} + +func (s *ewmaSpeed) OnCompleteMessage(msg string, wcc ...WC) { + var wc WC + for _, widthConf := range wcc { + wc = widthConf + } + wc.BuildFormat() + s.onComplete = &struct { + msg string + wc WC + }{msg, wc} +} + +func (s *ewmaSpeed) Shutdown() { + close(s.sbReceiver) +} + +func (s *ewmaSpeed) serve() { + for now := range s.sbReceiver { + s.sbStreamer <- now + } +} diff --git a/examples/io/multiple/main.go b/examples/io/multiple/main.go index 689c449..2669bc1 100644 --- a/examples/io/multiple/main.go +++ b/examples/io/multiple/main.go @@ -58,8 +58,8 @@ return } - startBlock := make(chan time.Time) - + sbEta := make(chan time.Time) + sbSpeed := make(chan time.Time) // create bar with appropriate decorators bar := p.AddBar(size, mpb.BarPriority(n), mpb.PrependDecorators( @@ -67,13 +67,13 @@ decor.CountersKibiByte("%6.1f / %6.1f", decor.WCSyncWidth), ), mpb.AppendDecorators( - decor.ETA(decor.ET_STYLE_HHMMSS, 60, startBlock, decor.WCSyncWidth), - decor.SpeedKibiByte("%6.1f", decor.WCSyncSpace), + decor.ETA(decor.ET_STYLE_HHMMSS, 60, sbEta, decor.WCSyncWidth), + decor.SpeedKibiByte("% .2f", 60, sbSpeed, decor.WCSyncSpace), ), ) // create proxy reader - reader := bar.ProxyReader(resp.Body, startBlock) + reader := bar.ProxyReader(resp.Body, sbEta, sbSpeed) // and copy from reader _, err = io.Copy(dest, reader) diff --git a/examples/io/single/main.go b/examples/io/single/main.go index 5a90707..3d9a3ba 100644 --- a/examples/io/single/main.go +++ b/examples/io/single/main.go @@ -39,19 +39,20 @@ p := mpb.New(mpb.WithWidth(64)) - startBlock := make(chan time.Time) + sbEta := make(chan time.Time) + sbSpeed := make(chan time.Time) bar := p.AddBar(size, mpb.PrependDecorators( decor.CountersKibiByte("% 6.1f / % 6.1f", decor.WC{W: 18}), ), mpb.AppendDecorators( - decor.ETA(decor.ET_STYLE_HHMMSS, 120, startBlock), - decor.SpeedKibiByte("% 6.1f", decor.WC{W: 14}), + decor.ETA(decor.ET_STYLE_HHMMSS, 120, sbEta), + decor.SpeedKibiByte("% .2f", 120, sbSpeed, decor.WC{W: 14}), ), ) // create proxy reader - reader := bar.ProxyReader(resp.Body, startBlock) + reader := bar.ProxyReader(resp.Body, sbEta, sbSpeed) // and copy from reader, ignoring errors io.Copy(dest, reader) diff --git a/proxyreader.go b/proxyreader.go index da9c2ca..aa70ac8 100644 --- a/proxyreader.go +++ b/proxyreader.go @@ -8,13 +8,14 @@ // Reader is io.Reader wrapper, for proxy read bytes type Reader struct { io.Reader - bar *Bar - startBlockCh chan<- time.Time + bar *Bar + sbChannels []chan<- time.Time } func (r *Reader) Read(p []byte) (int, error) { - if r.startBlockCh != nil { - r.startBlockCh <- time.Now() + now := time.Now() + for _, ch := range r.sbChannels { + ch <- now } n, err := r.Reader.Read(p) r.bar.IncrBy(n)