package decor
import (
"fmt"
"math"
"time"
"github.com/VividCortex/ewma"
"github.com/vbauerster/mpb/internal"
)
type TimeNormalizer func(time.Duration) time.Duration
// EwmaETA exponential-weighted-moving-average based ETA decorator.
//
// `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS]
//
// `age` is the previous N samples to average over.
//
// `wcc` optional WC config
func EwmaETA(style int, age float64, wcc ...WC) Decorator {
return MovingAverageETA(style, ewma.NewMovingAverage(age), NopNormalizer(), wcc...)
}
// MovingAverageETA decorator relies on MovingAverage implementation to calculate its average.
//
// `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS]
//
// `average` available implementations of MovingAverage [ewma.MovingAverage|NewMedian|NewMedianEwma]
//
// `normalizer` available implementations are [NopNormalizer|FixedIntervalTimeNormalizer|MaxTolerateTimeNormalizer]
//
// `wcc` optional WC config
func MovingAverageETA(style int, average MovingAverage, normalizer TimeNormalizer, wcc ...WC) Decorator {
var wc WC
for _, widthConf := range wcc {
wc = widthConf
}
wc.Init()
d := &movingAverageETA{
WC: wc,
style: style,
average: average,
normalizer: normalizer,
}
return d
}
type movingAverageETA struct {
WC
style int
average ewma.MovingAverage
completeMsg *string
normalizer TimeNormalizer
}
func (d *movingAverageETA) Decor(st *Statistics) string {
if st.Completed && d.completeMsg != nil {
return d.FormatMsg(*d.completeMsg)
}
v := internal.Round(d.average.Value())
remaining := d.normalizer(time.Duration((st.Total - st.Current) * int64(v)))
hours := int64((remaining / time.Hour) % 60)
minutes := int64((remaining / time.Minute) % 60)
seconds := int64((remaining / time.Second) % 60)
var str string
switch d.style {
case ET_STYLE_GO:
str = fmt.Sprint(time.Duration(remaining.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 d.FormatMsg(str)
}
func (d *movingAverageETA) NextAmount(n int, wdd ...time.Duration) {
var workDuration time.Duration
for _, wd := range wdd {
workDuration = wd
}
lastItemEstimate := float64(workDuration) / float64(n)
if math.IsInf(lastItemEstimate, 0) || math.IsNaN(lastItemEstimate) {
return
}
d.average.Add(lastItemEstimate)
}
func (d *movingAverageETA) OnCompleteMessage(msg string) {
d.completeMsg = &msg
}
// AverageETA decorator.
//
// `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS]
//
// `wcc` optional WC config
func AverageETA(style int, wcc ...WC) Decorator {
var wc WC
for _, widthConf := range wcc {
wc = widthConf
}
wc.Init()
d := &averageETA{
WC: wc,
style: style,
startTime: time.Now(),
}
return d
}
type averageETA struct {
WC
style int
startTime time.Time
completeMsg *string
}
func (d *averageETA) Decor(st *Statistics) string {
if st.Completed && d.completeMsg != nil {
return d.FormatMsg(*d.completeMsg)
}
var str string
timeElapsed := time.Since(d.startTime)
v := internal.Round(float64(timeElapsed) / float64(st.Current))
if math.IsInf(v, 0) || math.IsNaN(v) {
v = 0
}
remaining := time.Duration((st.Total - st.Current) * int64(v))
hours := int64((remaining / time.Hour) % 60)
minutes := int64((remaining / time.Minute) % 60)
seconds := int64((remaining / time.Second) % 60)
switch d.style {
case ET_STYLE_GO:
str = fmt.Sprint(time.Duration(remaining.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 d.FormatMsg(str)
}
func (d *averageETA) OnCompleteMessage(msg string) {
d.completeMsg = &msg
}
func MaxTolerateTimeNormalizer(maxTolerate time.Duration) TimeNormalizer {
var normalized time.Duration
var lastCall time.Time
return func(remaining time.Duration) time.Duration {
if diff := normalized - remaining; diff <= 0 || diff > maxTolerate || remaining < maxTolerate/2 {
normalized = remaining
lastCall = time.Now()
return remaining
}
normalized -= time.Since(lastCall)
lastCall = time.Now()
return normalized
}
}
func FixedIntervalTimeNormalizer(updInterval int) TimeNormalizer {
var normalized time.Duration
var lastCall time.Time
var count int
return func(remaining time.Duration) time.Duration {
if count == 0 || remaining <= time.Duration(15*time.Second) {
count = updInterval
normalized = remaining
lastCall = time.Now()
return remaining
}
count--
normalized -= time.Since(lastCall)
lastCall = time.Now()
if normalized > 0 {
return normalized
}
return remaining
}
}
func NopNormalizer() TimeNormalizer {
return func(remaining time.Duration) time.Duration {
return remaining
}
}