summaryrefslogtreecommitdiffstats
path: root/decor/speed.go
blob: 5879d06042a26e3876040917eeb024dd451135eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package decor

import (
	"fmt"
	"io"
	"math"
	"time"

	"github.com/VividCortex/ewma"
)

var (
	_ Decorator        = (*movingAverageSpeed)(nil)
	_ EwmaDecorator    = (*movingAverageSpeed)(nil)
	_ Decorator        = (*averageSpeed)(nil)
	_ AverageDecorator = (*averageSpeed)(nil)
)

// FmtAsSpeed adds "/s" to the end of the input formatter. To be
// used with SizeB1000 or SizeB1024 types, for example:
//
//	fmt.Printf("%.1f", FmtAsSpeed(SizeB1024(2048)))
func FmtAsSpeed(input fmt.Formatter) fmt.Formatter {
	return speedFormatter{input}
}

type speedFormatter struct {
	fmt.Formatter
}

func (self speedFormatter) Format(st fmt.State, verb rune) {
	self.Formatter.Format(st, verb)
	_, err := io.WriteString(st, "/s")
	if err != nil {
		panic(err)
	}
}

// EwmaSpeed exponential-weighted-moving-average based speed decorator.
// For this decorator to work correctly you have to measure each iteration's
// duration and pass it to one of the (*Bar).EwmaIncr... family methods.
func EwmaSpeed(unit interface{}, format string, age float64, wcc ...WC) Decorator {
	var average ewma.MovingAverage
	if age == 0 {
		average = ewma.NewMovingAverage()
	} else {
		average = ewma.NewMovingAverage(age)
	}
	return MovingAverageSpeed(unit, format, NewThreadSafeMovingAverage(average), wcc...)
}

// MovingAverageSpeed decorator relies on MovingAverage implementation
// to calculate its average.
//
//	`unit` one of [0|SizeB1024(0)|SizeB1000(0)]
//
//	`format` printf compatible verb for value, like "%f" or "%d"
//
//	`average` MovingAverage implementation
//
//	`wcc` optional WC config
//
// format examples:
//
//	unit=SizeB1024(0), format="%.1f"  output: "1.0MiB/s"
//	unit=SizeB1024(0), format="% .1f" output: "1.0 MiB/s"
//	unit=SizeB1000(0), format="%.1f"  output: "1.0MB/s"
//	unit=SizeB1000(0), format="% .1f" output: "1.0 MB/s"
func MovingAverageSpeed(unit interface{}, format string, average ewma.MovingAverage, wcc ...WC) Decorator {
	d := &movingAverageSpeed{
		WC:       initWC(wcc...),
		average:  average,
		producer: chooseSpeedProducer(unit, format),
	}
	return d
}

type movingAverageSpeed struct {
	WC
	producer func(float64) string
	average  ewma.MovingAverage
	msg      string
}

func (d *movingAverageSpeed) Decor(s Statistics) (string, int) {
	if !s.Completed {
		var speed float64
		if v := d.average.Value(); v > 0 {
			speed = 1 / v
		}
		d.msg = d.producer(speed * 1e9)
	}
	return d.Format(d.msg)
}

func (d *movingAverageSpeed) EwmaUpdate(n int64, dur time.Duration) {
	durPerByte := float64(dur) / float64(n)
	if math.IsInf(durPerByte, 0) || math.IsNaN(durPerByte) {
		return
	}
	d.average.Add(durPerByte)
}

// AverageSpeed decorator with dynamic unit measure adjustment. It's
// a wrapper of NewAverageSpeed.
func AverageSpeed(unit interface{}, format string, wcc ...WC) Decorator {
	return NewAverageSpeed(unit, format, time.Now(), wcc...)
}

// NewAverageSpeed decorator with dynamic unit measure adjustment and
// user provided start time.
//
//	`unit` one of [0|SizeB1024(0)|SizeB1000(0)]
//
//	`format` printf compatible verb for value, like "%f" or "%d"
//
//	`startTime` start time
//
//	`wcc` optional WC config
//
// format examples:
//
//	unit=SizeB1024(0), format="%.1f"  output: "1.0MiB/s"
//	unit=SizeB1024(0), format="% .1f" output: "1.0 MiB/s"
//	unit=SizeB1000(0), format="%.1f"  output: "1.0MB/s"
//	unit=SizeB1000(0), format="% .1f" output: "1.0 MB/s"
func NewAverageSpeed(unit interface{}, format string, startTime time.Time, wcc ...WC) Decorator {
	d := &averageSpeed{
		WC:        initWC(wcc...),
		startTime: startTime,
		producer:  chooseSpeedProducer(unit, format),
	}
	return d
}

type averageSpeed struct {
	WC
	startTime time.Time
	producer  func(float64) string
	msg       string
}

func (d *averageSpeed) Decor(s Statistics) (string, int) {
	if !s.Completed {
		speed := float64(s.Current) / float64(time.Since(d.startTime))
		d.msg = d.producer(speed * 1e9)
	}
	return d.Format(d.msg)
}

func (d *averageSpeed) AverageAdjust(startTime time.Time) {
	d.startTime = startTime
}

func chooseSpeedProducer(unit interface{}, format string) func(float64) string {
	switch unit.(type) {
	case SizeB1024:
		if format == "" {
			format = "% d"
		}
		return func(speed float64) string {
			return fmt.Sprintf(format, FmtAsSpeed(SizeB1024(math.Round(speed))))
		}
	case SizeB1000:
		if format == "" {
			format = "% d"
		}
		return func(speed float64) string {
			return fmt.Sprintf(format, FmtAsSpeed(SizeB1000(math.Round(speed))))
		}
	default:
		if format == "" {
			format = "%f"
		}
		return func(speed float64) string {
			return fmt.Sprintf(format, speed)
		}
	}
}