Skip to content

Commit 7396328

Browse files
address review comments
1 parent 6f839bc commit 7396328

File tree

2 files changed

+172
-34
lines changed

2 files changed

+172
-34
lines changed

module/util/log.go

Lines changed: 74 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,19 @@ import (
1414
type LogProgressFunc[T int | uint | int32 | uint32 | uint64 | int64] func(addProgress T)
1515

1616
type LogProgressConfig[T int | uint | int32 | uint32 | uint64 | int64] struct {
17-
// Message is part of the messages that will be logged.
17+
// message is part of the messages that will be logged.
1818
// The full template is: `%s progress %d/%d (%.1f%%) total time %s`.
19-
Message string
20-
// Total is the total value of progress expected.
21-
// When Total is added to LogProgressFunc the progress is considered to be 100%.
22-
Total T
23-
// NoDataLogDuration. If the last log line was more than this duration ago and a new data point is added, a new log line is logged.
24-
// No line is logged if no data is received. The minimum resolution for NoDataLogDuration is 1 millisecond.
25-
NoDataLogDuration time.Duration
26-
// Ticks is the number of increments to log at. If Total is > 0 there will be at least 2 ticks. One at 0 and one at Total.
27-
// If you want to log at every 10% set Ticks to 11 (one is at 0%).
28-
// If the number of ticks is more than Total, it will be set to Total + 1.
29-
Ticks uint64
19+
message string
20+
// total is the total value of progress expected.
21+
// When total is added to LogProgressFunc the progress is considered to be 100%.
22+
total T
23+
// noDataLogDuration. If the last log line was more than this duration ago and a new data point is added, a new log line is logged.
24+
// No line is logged if no data is received. The minimum resolution for noDataLogDuration is 1 millisecond.
25+
noDataLogDuration time.Duration
26+
// ticks is the number of increments to log at. If total is > 0 there will be at least 2 ticks. One at 0 and one at total.
27+
// If you want to log at every 10% set ticks to 11 (one is at 0%).
28+
// If the number of ticks is more than total, it will be set to total + 1.
29+
ticks uint64
3030
}
3131

3232
// DefaultLogProgressConfig returns a LogProgressConfig with default values.
@@ -36,15 +36,46 @@ func DefaultLogProgressConfig[T int | uint | int32 | uint32 | uint64 | int64](
3636
message string,
3737
total T,
3838
) LogProgressConfig[T] {
39+
return NewLogProgressConfig[T](
40+
message,
41+
total,
42+
60*time.Second,
43+
10,
44+
)
45+
}
46+
47+
// NewLogProgressConfig creates and returns a new LogProgressConfig with the specified message, total, duration, and ticks.
48+
// The duration is rounded to the nearest millisecond.
49+
// The number of ticks is the number of increments to log at. Logging at 0% is always done.
50+
// If you want to log at 10% increments, set ticks to 10.
51+
func NewLogProgressConfig[T int | uint | int32 | uint32 | uint64 | int64](
52+
message string,
53+
total T,
54+
noDataLogDuration time.Duration,
55+
ticks uint64,
56+
) LogProgressConfig[T] {
57+
// sanitize total
58+
if total < 0 {
59+
total = 0
60+
}
61+
62+
// add the tick at 0%
63+
ticks = ticks + 1
64+
65+
// sanitize ticks
66+
// number of ticks should be at most total + 1
67+
if uint64(total+1) < ticks {
68+
ticks = uint64(total + 1)
69+
}
70+
3971
return LogProgressConfig[T]{
40-
Message: message,
41-
Total: total,
42-
Ticks: 11,
43-
NoDataLogDuration: 60 * time.Second,
72+
message: message,
73+
total: total,
74+
noDataLogDuration: noDataLogDuration,
75+
ticks: ticks,
4476
}
45-
}
4677

47-
type LogProgressOption[T int | uint | int32 | uint32 | uint64 | int64] func(config *LogProgressConfig[T])
78+
}
4879

4980
// LogProgress takes a LogProgressConfig and return function such that when called adds the given
5081
// number to the progress and logs the progress in defined increments or there is a time gap between progress
@@ -67,7 +98,7 @@ func LogProgress[T int | uint | int32 | uint32 | uint64 | int64](
6798
// usage anyway.
6899
var mux sync.Mutex
69100

70-
total := uint64(config.Total)
101+
total := uint64(config.total)
71102
logProgress := func(current uint64) {
72103
mux.Lock()
73104
defer mux.Unlock()
@@ -76,8 +107,8 @@ func LogProgress[T int | uint | int32 | uint32 | uint64 | int64](
76107
elapsedString := elapsed.Round(1 * time.Second).String()
77108

78109
percentage := float64(100)
79-
if config.Total > 0 {
80-
percentage = (float64(current) / float64(config.Total)) * 100.
110+
if config.total > 0 {
111+
percentage = (float64(current) / float64(config.total)) * 100.
81112
}
82113

83114
etaString := "unknown"
@@ -90,17 +121,17 @@ func LogProgress[T int | uint | int32 | uint32 | uint64 | int64](
90121
}
91122

92123
if current < total {
93-
log.Info().Msgf("%s progress %d/%d (%.1f%%) elapsed: %s, eta %s", config.Message, current, config.Total, percentage, elapsedString, etaString)
124+
log.Info().Msgf("%s progress %d/%d (%.1f%%) elapsed: %s, eta %s", config.message, current, config.total, percentage, elapsedString, etaString)
94125
} else {
95-
log.Info().Msgf("%s progress %d/%d (%.1f%%) total time %s", config.Message, current, config.Total, percentage, elapsedString)
126+
log.Info().Msgf("%s progress %d/%d (%.1f%%) total time %s", config.message, current, config.total, percentage, elapsedString)
96127
}
97128
}
98129

99130
// log 0% progress
100131
logProgress(0)
101132

102133
// sanitize inputs and calculate increment
103-
ticksIncludingZero := config.Ticks
134+
ticksIncludingZero := config.ticks
104135
if ticksIncludingZero < 2 {
105136
ticksIncludingZero = 2
106137
}
@@ -111,32 +142,47 @@ func LogProgress[T int | uint | int32 | uint32 | uint64 | int64](
111142
increment = 1
112143
}
113144

114-
// increment doesn't necessarily divide config.Total
145+
// increment doesn't necessarily divide config.total
115146
// Because we want 100% to mean 100% we need to deduct this overflow from the current value
116147
// before checking if it is a multiple of the increment.
117148
incrementsOverflow := total % increment
118-
noLogDurationMillis := config.NoDataLogDuration.Milliseconds()
149+
noLogDurationMillis := config.noDataLogDuration.Milliseconds()
119150

120151
return func(add T) {
152+
if total == 0 {
153+
return
154+
}
121155
if add < 0 {
122156
return
123157
}
124158
diff := uint64(add)
125159
now := time.Now().UnixMilli()
126160

161+
// it can technically happen that current > total. In this case we continue to log
162+
// the progress using the calculated increments
127163
current := currentIndex.Add(diff)
128164
lastTime := lastDataTime.Swap(now)
129165

130166
// if the diff went over one or more increments, log the progress for each increment
131-
fromTick := (current - diff - incrementsOverflow) / increment
132-
toTick := (current - incrementsOverflow) / increment
167+
fromTick := uint64(0)
168+
if current-diff >= incrementsOverflow {
169+
fromTick = (current - diff - incrementsOverflow) / increment
170+
}
171+
toTick := uint64(0)
172+
if current >= incrementsOverflow {
173+
toTick = (current - incrementsOverflow) / increment
174+
}
133175

134176
if fromTick == toTick && now-lastTime > noLogDurationMillis {
135177
// no data for a while, log whatever we are at now
136178
logProgress(current)
137179
return
138180
}
139181

182+
if toTick <= fromTick {
183+
return
184+
}
185+
140186
for t := fromTick; t < toTick; t++ {
141187
// (t+1) because we want to log the progress for the increment reached
142188
// not the increment past

module/util/log_test.go

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,98 @@ func TestLogProgress1000(t *testing.T) {
218218
}
219219
}
220220

221+
func TestLogProgressWhenTotalIs0(t *testing.T) {
222+
t.Parallel()
223+
224+
buf := bytes.NewBufferString("")
225+
lg := zerolog.New(buf)
226+
logger := LogProgress(
227+
lg,
228+
DefaultLogProgressConfig(
229+
"test",
230+
0,
231+
),
232+
)
233+
234+
for i := 0; i < 10; i++ {
235+
logger(1)
236+
}
237+
238+
expectedLogs := []string{
239+
fmt.Sprintf(`test progress %d/%d (100.0%%)`, 0, 0),
240+
}
241+
242+
for _, log := range expectedLogs {
243+
require.Contains(t, buf.String(), log, 0)
244+
}
245+
246+
lines := strings.Count(buf.String(), "\n")
247+
// log only once
248+
require.Equal(t, 1, lines)
249+
}
250+
251+
func TestLogProgressMoreTicksThenTotal(t *testing.T) {
252+
t.Parallel()
253+
254+
buf := bytes.NewBufferString("")
255+
lg := zerolog.New(buf)
256+
logger := LogProgress(
257+
lg,
258+
DefaultLogProgressConfig(
259+
"test",
260+
5,
261+
),
262+
)
263+
264+
for i := 0; i < 5; i++ {
265+
logger(1)
266+
}
267+
268+
expectedLogs := []string{
269+
fmt.Sprintf(`test progress %d/%d (100.0%%)`, 5, 5),
270+
}
271+
272+
for _, log := range expectedLogs {
273+
require.Contains(t, buf.String(), log, 0)
274+
}
275+
276+
lines := strings.Count(buf.String(), "\n")
277+
// log only once
278+
require.Equal(t, 6, lines)
279+
}
280+
281+
func TestLogProgressContinueLoggingAfter100(t *testing.T) {
282+
t.Parallel()
283+
284+
buf := bytes.NewBufferString("")
285+
lg := zerolog.New(buf)
286+
logger := LogProgress(
287+
lg,
288+
DefaultLogProgressConfig(
289+
"test",
290+
100,
291+
),
292+
)
293+
294+
for i := 0; i < 15; i++ {
295+
logger(10)
296+
}
297+
298+
expectedLogs := []string{
299+
fmt.Sprintf(`test progress %d/%d (100.0%%)`, 100, 100),
300+
fmt.Sprintf(`test progress %d/%d (110.0%%)`, 110, 100),
301+
fmt.Sprintf(`test progress %d/%d (150.0%%)`, 150, 100),
302+
}
303+
304+
for _, log := range expectedLogs {
305+
require.Contains(t, buf.String(), log, 0)
306+
}
307+
308+
lines := strings.Count(buf.String(), "\n")
309+
// log only once
310+
require.Equal(t, 16, lines)
311+
}
312+
221313
func TestLogProgressNoDataForAWhile(t *testing.T) {
222314
t.Parallel()
223315

@@ -227,12 +319,12 @@ func TestLogProgressNoDataForAWhile(t *testing.T) {
227319
lg := zerolog.New(buf)
228320
logger := LogProgress(
229321
lg,
230-
LogProgressConfig[uint64]{
231-
Message: "test",
232-
Total: uint64(total),
233-
Ticks: 11,
234-
NoDataLogDuration: 1 * time.Millisecond,
235-
},
322+
NewLogProgressConfig[uint64](
323+
"test",
324+
uint64(total),
325+
1*time.Millisecond,
326+
10,
327+
),
236328
)
237329

238330
for i := 0; i < total; i++ {

0 commit comments

Comments
 (0)