@@ -14,19 +14,19 @@ import (
1414type LogProgressFunc [T int | uint | int32 | uint32 | uint64 | int64 ] func (addProgress T )
1515
1616type 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
0 commit comments