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
|
package progress
import (
"time"
"lindenii.org/go/lgo/intconv"
"lindenii.org/go/lgo/iowrap"
)
const (
updateInterval = time.Second
throughputInterval = 500 * time.Millisecond
)
// Meter renders one in-place progress line.
type Meter struct {
writer iowrap.WriteFlusher
title string
total uint64
delay time.Duration
sparse bool
throughput bool
startedAt time.Time
nextUpdateAt time.Time
nextThroughput time.Time
lastDone uint64
lastBytes uint64
lastPercent int
lastCounterW int
sawValue bool
throughputSuffix string
}
// New creates one progress meter.
func New(opts Options) *Meter {
now := time.Now()
return &Meter{
writer: opts.Writer,
title: opts.Title,
total: opts.Total,
delay: max(opts.Delay, time.Duration(0)),
sparse: opts.Sparse,
throughput: opts.Throughput,
startedAt: now,
nextUpdateAt: now.Add(updateInterval),
nextThroughput: now.Add(throughputInterval),
lastPercent: -1,
}
}
// Options configures one progress meter.
type Options struct {
Writer iowrap.WriteFlusher
Title string
Total uint64
// Delay suppresses progress output until Delay has elapsed since Start.
Delay time.Duration
// Sparse forces one final 100% line at Stop when the caller sampled updates.
Sparse bool
// Throughput appends ", <total> | <rate>/s" and refreshes rate every 500ms.
Throughput bool
}
// Set records current progress
// and renders when percent changed or the 1s tick elapsed.
func (meter *Meter) Set(done uint64, bytes uint64) {
meter.lastDone = done
meter.lastBytes = bytes
meter.sawValue = true
if meter.writer == nil {
return
}
now := time.Now()
forced := meter.consumeUpdateTick(now)
percentChanged := false
if meter.total > 0 {
percent, err := intconv.Uint64ToInt(done * 100 / meter.total)
if err != nil {
return // TODO
}
percentChanged = percent != meter.lastPercent
}
if !percentChanged && !forced {
return
}
meter.render(now, "\r")
}
// Stop forces the final progress line and appends ", <msg>.".
func (meter *Meter) Stop(msg string) {
if !meter.sawValue || meter.writer == nil {
return
}
if msg == "" {
msg = "done"
}
if meter.sparse && meter.total > 0 && meter.lastDone != meter.total {
meter.lastDone = meter.total
}
meter.render(time.Now(), ", "+msg+".\n")
}
func (meter *Meter) consumeUpdateTick(now time.Time) bool {
if now.Before(meter.nextUpdateAt) {
return false
}
for !now.Before(meter.nextUpdateAt) {
meter.nextUpdateAt = meter.nextUpdateAt.Add(updateInterval)
}
return true
}
|