package decor
import (
"fmt"
"io"
"math"
"strconv"
"strings"
"time"
"github.com/VividCortex/ewma"
)
type SpeedKiB float64
func (s SpeedKiB) Format(st fmt.State, verb rune) {
prec, ok := st.Precision()
if verb == 'd' || !ok {
prec = 0
}
if verb == 'f' && !ok {
prec = 6
}
// retain old beahavior if s verb used
if verb == 's' {
prec = 1
}
var res, unit string
switch {
case s >= TiB:
unit = "TiB/s"
res = strconv.FormatFloat(float64(s)/TiB, 'f', prec, 64)
case s >= GiB:
unit = "GiB/s"
res = strconv.FormatFloat(float64(s)/GiB, 'f', prec, 64)
case s >= MiB:
unit = "MiB/s"
res = strconv.FormatFloat(float64(s)/MiB, 'f', prec, 64)
case s >= KiB:
unit = "KiB/s"
res = strconv.FormatFloat(float64(s)/KiB, 'f', prec, 64)
default:
unit = "b/s"
res = strconv.FormatInt(int64(s), 10)
}
if st.Flag(' ') {
res += " "
}
res += unit
if w, ok := st.Width(); ok {
if len(res) < w {
pad := strings.Repeat(" ", w-len(res))
if st.Flag(int('-')) {
res += pad
} else {
res = pad + res
}
}
}
io.WriteString(st, res)
}
type SpeedKB float64
func (s SpeedKB) Format(st fmt.State, verb rune) {
prec, ok := st.Precision()
if verb == 'd' || !ok {
prec = 0
}
if verb == 'f' && !ok {
prec = 6
}
// retain old beahavior if s verb used
if verb == 's' {
prec = 1
}
var res, unit string
switch {
case s >= TB:
unit = "TB/s"
res = strconv.FormatFloat(float64(s)/TB, 'f', prec, 64)
case s >= GB:
unit = "GB/s"
res = strconv.FormatFloat(float64(s)/GB, 'f', prec, 64)
case s >= MB:
unit = "MB/s"
res = strconv.FormatFloat(float64(s)/MB, 'f', prec, 64)
case s >= KB:
unit = "kB/s"
res = strconv.FormatFloat(float64(s)/KB, 'f', prec, 64)
default:
unit = "b/s"
res = strconv.FormatInt(int64(s), 10)
}
if st.Flag(' ') {
res += " "
}
res += unit
if w, ok := st.Width(); ok {
if len(res) < w {
pad := strings.Repeat(" ", w-len(res))
if st.Flag(int('-')) {
res += pad
} else {
res = pad + res
}
}
}
io.WriteString(st, res)
}
// EwmaSpeed exponential-weighted-moving-average based speed decorator,
// with dynamic unit measure adjustment.
//
// `unit` one of [0|UnitKiB|UnitKB] zero for no unit
//
// `unitFormat` printf compatible verb for value, like "%f" or "%d"
//
// `average` MovingAverage implementation
//
// `wcc` optional WC config
//
// unitFormat example if UnitKiB is chosen:
//
// "%.1f" = "1.0MiB/s" or "% .1f" = "1.0 MiB/s"
func EwmaSpeed(unit int, unitFormat string, age float64, wcc ...WC) Decorator {
return MovingAverageSpeed(unit, unitFormat, ewma.NewMovingAverage(age), wcc...)
}
// MovingAverageSpeed decorator relies on MovingAverage implementation
// to calculate its average.
//
// `unit` one of [0|UnitKiB|UnitKB] zero for no unit
//
// `unitFormat` printf compatible verb for value, like "%f" or "%d"
//
// `average` MovingAverage implementation
//
// `wcc` optional WC config
func MovingAverageSpeed(unit int, unitFormat string, average MovingAverage, wcc ...WC) Decorator {
var wc WC
for _, widthConf := range wcc {
wc = widthConf
}
wc.Init()
d := &movingAverageSpeed{
WC: wc,
unit: unit,
unitFormat: unitFormat,
average: average,
}
return d
}
type movingAverageSpeed struct {
WC
unit int
unitFormat string
average ewma.MovingAverage
msg string
completeMsg *string
}
func (d *movingAverageSpeed) Decor(st *Statistics) string {
if st.Completed {
if d.completeMsg != nil {
return d.FormatMsg(*d.completeMsg)
}
return d.FormatMsg(d.msg)
}
speed := d.average.Value()
switch d.unit {
case UnitKiB:
d.msg = fmt.Sprintf(d.unitFormat, SpeedKiB(speed))
case UnitKB:
d.msg = fmt.Sprintf(d.unitFormat, SpeedKB(speed))
default:
d.msg = fmt.Sprintf(d.unitFormat, speed)
}
return d.FormatMsg(d.msg)
}
func (s *movingAverageSpeed) NextAmount(n int, wdd ...time.Duration) {
var workDuration time.Duration
for _, wd := range wdd {
workDuration = wd
}
speed := float64(n) / workDuration.Seconds() / 1000
if math.IsInf(speed, 0) || math.IsNaN(speed) {
return
}
s.average.Add(speed)
}
func (d *movingAverageSpeed) OnCompleteMessage(msg string) {
d.completeMsg = &msg
}
// AverageSpeed decorator with dynamic unit measure adjustment.
//
// `unit` one of [0|UnitKiB|UnitKB] zero for no unit
//
// `unitFormat` printf compatible verb for value, like "%f" or "%d"
//
// `wcc` optional WC config
//
// unitFormat example if UnitKiB is chosen:
//
// "%.1f" = "1.0MiB/s" or "% .1f" = "1.0 MiB/s"
func AverageSpeed(unit int, unitFormat string, wcc ...WC) Decorator {
var wc WC
for _, widthConf := range wcc {
wc = widthConf
}
wc.Init()
d := &averageSpeed{
WC: wc,
unit: unit,
unitFormat: unitFormat,
startTime: time.Now(),
}
return d
}
type averageSpeed struct {
WC
unit int
unitFormat string
startTime time.Time
msg string
completeMsg *string
}
func (d *averageSpeed) Decor(st *Statistics) string {
if st.Completed {
if d.completeMsg != nil {
return d.FormatMsg(*d.completeMsg)
}
return d.FormatMsg(d.msg)
}
timeElapsed := time.Since(d.startTime)
speed := float64(st.Current) / timeElapsed.Seconds()
switch d.unit {
case UnitKiB:
d.msg = fmt.Sprintf(d.unitFormat, SpeedKiB(speed))
case UnitKB:
d.msg = fmt.Sprintf(d.unitFormat, SpeedKB(speed))
default:
d.msg = fmt.Sprintf(d.unitFormat, speed)
}
return d.FormatMsg(d.msg)
}
func (d *averageSpeed) OnCompleteMessage(msg string) {
d.completeMsg = &msg
}