@@ -28,13 +28,23 @@ const (
2828 SyncRangeMin int32 = 10
2929 SyncRangeMax int32 = 90
3030
31- bulkCeiling = 48.0
32- iterativeFloor = 46.0
33- iterativeCeiling = 90.0
34- bulkStallWindow = 45 * time .Second
35- iterStallWindow = 25 * time .Second
36- bulkUpdateInterval = time .Second
37- iterUpdateInterval = 2 * time .Second
31+ bulkCeiling = 45.0
32+ iterativeFloor = 45.0
33+ iterativeCeiling = 90.0
34+ thresholdFactor = 0.05
35+
36+ bulkTimeRate = 0.55
37+ iterBaseTimeRate = 0.022
38+ iterThrottleRate = 0.0012
39+ bulkMetricWeight = 0.80
40+ bulkTimeWeight = 0.20
41+ iterMetricWeight = 0.76
42+ iterTimeWeight = 0.24
43+ smoothAlphaUp = 0.18
44+ smoothAlphaDown = 0.34
45+ bulkStallSeconds = 10.0
46+ iterStallSeconds = 8.0
47+ finalStallSeconds = 6.0
3848)
3949
4050type Strategy interface {
@@ -76,6 +86,7 @@ func (p *Progress) Forget(uid types.UID) {
7686
7787func (p * Progress ) SyncProgress (record Record ) int32 {
7888 state := p .getState (record )
89+
7990 prev := clampSyncRange (record .PreviousProgress )
8091 if state .Progress > prev {
8192 prev = state .Progress
@@ -85,22 +96,57 @@ func (p *Progress) SyncProgress(record Record) int32 {
8596 if elapsed < 0 {
8697 elapsed = 0
8798 }
99+ elapsedSec := elapsed .Seconds ()
88100
89101 iterative := isIterative (record )
90102 if iterative && ! state .Iterative {
91103 state .Iterative = true
92104 state .IterativeSince = record .Now
105+ p .initIterative (record , & state , elapsedSec )
106+ }
107+
108+ if iterative {
109+ observeRemaining (record , & state )
110+ }
111+
112+ target := bulkTarget (record , elapsedSec )
113+ if iterative {
114+ target = iterativeTarget (record , & state , elapsedSec )
93115 }
94116
95- target := bulkTarget ( record , elapsed )
117+ maxStep := int32 ( 10 )
96118 if iterative {
97- target = iterativeTarget (record , & state , prev )
119+ maxStep = 5
120+ }
121+
122+ cap := stageCap (iterative )
123+ progress := math .Max (float64 (prev ), math .Min (target , cap ))
124+ next := clampPercent (progress )
125+ if next < prev {
126+ next = prev
127+ }
128+ if next > prev + maxStep {
129+ next = prev + maxStep
130+ }
131+
132+ if next == prev && float64 (next ) < cap {
133+ lastIncrease := state .LastIncreaseAt
134+ if lastIncrease .IsZero () {
135+ lastIncrease = record .StartedAt
136+ }
137+ stallWin := stallWindow (record , & state , iterative )
138+ if record .Now .Sub (lastIncrease ).Seconds () >= stallWin {
139+ next ++
140+ }
98141 }
99142
100- metricAdvanced := metricChanged (record , & state )
101- next := smoothProgress (prev , target , iterative , record .Throttle , record .Now .Sub (state .LastUpdatedAt ), metricAdvanced , state .LastUpdatedAt .IsZero ())
102- next = applyStatefulStall (record , & state , next , iterative )
103- next = clampSyncRange (maxInt32 (prev , next ))
143+ if float64 (next ) > cap {
144+ next = int32 (cap )
145+ }
146+
147+ if next > prev {
148+ state .LastIncreaseAt = record .Now
149+ }
104150
105151 updateMetricState (record , & state )
106152 state .Progress = next
@@ -121,143 +167,161 @@ func (p *Progress) getState(record Record) State {
121167 }
122168 state , ok := p .store .Load (record .OperationUID )
123169 if ! ok {
124- state = State {Progress : clampSyncRange (record .PreviousProgress ), LastMetricAt : record .Now }
170+ state = State {
171+ Progress : clampSyncRange (record .PreviousProgress ),
172+ LastMetricAt : record .Now ,
173+ }
125174 }
126175 return state
127176}
128177
129- func metricPercent (record Record ) (float64 , bool ) {
130- if record .DataTotalMiB <= 0 {
131- return 0 , false
178+ func (p * Progress ) initIterative (record Record , state * State , _ float64 ) {
179+ total := record .DataTotalMiB
180+ if total <= 0 {
181+ total = 1
182+ }
183+ if total > state .InitialTotal {
184+ state .InitialTotal = total
185+ }
186+ if state .InitialTotal <= 0 {
187+ state .InitialTotal = total
132188 }
133189
134- processed , hasProcessed := normalizedProcessedMiB (record )
135- if ! hasProcessed {
136- return 0 , false
190+ remaining := maxRemaining (record )
191+ if remaining <= 0 {
192+ remaining = state . InitialTotal
137193 }
138194
139- return clampFloat ((processed / record .DataTotalMiB )* 100.0 , 0 , 100 ), true
195+ state .Threshold = math .Max (math .Ceil (state .InitialTotal * thresholdFactor ), 1 )
196+ state .InitialRemaining = math .Max (remaining , state .Threshold )
197+ state .SmoothedRemaining = state .InitialRemaining
140198}
141199
142- func normalizedProcessedMiB (record Record ) (float64 , bool ) {
143- if record .DataTotalMiB <= 0 {
144- return 0 , false
200+ func observeRemaining (record Record , state * State ) {
201+ remaining := maxRemaining (record )
202+ if remaining <= 0 {
203+ return
204+ }
205+
206+ alpha := smoothAlphaUp
207+ if remaining < state .SmoothedRemaining {
208+ alpha = smoothAlphaDown
145209 }
146- if record .DataProcessedMiB >= 0 {
147- return clampFloat ( record . DataProcessedMiB , 0 , record . DataTotalMiB ), true
210+ if record .Throttle >= 0.80 {
211+ alpha += 0.08
148212 }
149- if record . DataRemainingMiB >= 0 {
150- return clampFloat ( record . DataTotalMiB - record . DataRemainingMiB , 0 , record . DataTotalMiB ), true
213+ if alpha > 0.90 {
214+ alpha = 0.90
151215 }
152- return 0 , false
153- }
154216
155- func bulkTarget (record Record , elapsed time.Duration ) float64 {
156- timeTarget := float64 (SyncRangeMin ) + math .Min (14 , elapsed .Seconds ()/ 8 )
157- metricPct , hasMetric := metricPercent (record )
158- if ! hasMetric {
159- return clampFloat (timeTarget , float64 (SyncRangeMin ), bulkCeiling )
217+ if state .SmoothedRemaining <= 0 {
218+ state .SmoothedRemaining = remaining
219+ } else {
220+ state .SmoothedRemaining = alpha * remaining + (1 - alpha )* state .SmoothedRemaining
160221 }
161- metricTarget := float64 (SyncRangeMin ) + (metricPct / 100.0 )* (bulkCeiling - float64 (SyncRangeMin ))
162- mixed := metricTarget * 0.78 + timeTarget * 0.22
163- return clampFloat (mixed , float64 (SyncRangeMin ), bulkCeiling )
164222}
165223
166- func iterativeTarget (record Record , state * State , current int32 ) float64 {
167- baseline := float64 (current )
168- if record .HasIteration {
169- iterationBoost := math .Min (float64 (record .Iteration ), 6 ) * 1.5
170- baseline = math .Max (baseline , float64 (current )+ iterationBoost )
224+ func bulkTarget (record Record , elapsedSec float64 ) float64 {
225+ total := record .DataTotalMiB
226+ if total <= 0 {
227+ total = 1
171228 }
172229
173- target := baseline
174- metricPct , hasMetric := metricPercent (record )
175- if hasMetric {
176- target = math .Max (target , iterativeMetricTarget (record , metricPct ))
177- }
230+ processed := math .Max (record .DataProcessedMiB , 0 )
231+ metricRatio := clampFloat (processed / total , 0 , 1 )
232+ metricPct := float64 (SyncRangeMin ) + (bulkCeiling - float64 (SyncRangeMin ))* metricRatio
178233
179- iterativeSince := state . IterativeSince
180- if iterativeSince . IsZero () {
181- iterativeSince = record . Now
234+ timePct := float64 ( SyncRangeMin ) + elapsedSec * bulkTimeRate
235+ if timePct > bulkCeiling {
236+ timePct = bulkCeiling
182237 }
183- iterElapsed := record .Now .Sub (iterativeSince )
184- if iterElapsed < 0 {
185- iterElapsed = 0
186- }
187- target += math .Min (10 , iterElapsed .Seconds ()/ 12 )
188- if record .HasThrottle {
189- target += record .Throttle * 6
238+
239+ return bulkMetricWeight * metricPct + bulkTimeWeight * timePct
240+ }
241+
242+ func iterativeTarget (record Record , state * State , elapsedSec float64 ) float64 {
243+ metricRatio := iterativeMetricRatio (state )
244+ metricPct := iterativeFloor + (iterativeCeiling - 5 - iterativeFloor )* metricRatio
245+
246+ throttle := record .Throttle
247+ iterSince := state .IterativeSince
248+ if iterSince .IsZero () {
249+ iterSince = record .Now
190250 }
191- if ! hasMetric {
192- target += math .Min (34 , iterElapsed .Seconds ()/ 20 )
251+ iterElapsed := math .Max (0 , elapsedSec - record .Now .Sub (iterSince ).Seconds ()+ record .Now .Sub (iterSince ).Seconds ())
252+ iterElapsedSec := math .Max (0 , record .Now .Sub (iterSince ).Seconds ())
253+
254+ timeRate := iterBaseTimeRate + throttle * iterThrottleRate
255+ timePct := iterativeFloor + iterElapsedSec * timeRate
256+ if timePct > iterativeCeiling {
257+ timePct = iterativeCeiling
193258 }
259+ _ = iterElapsed
194260
195- return clampFloat (target , float64 (current ), iterativeCeiling )
261+ target := iterMetricWeight * metricPct + iterTimeWeight * timePct
262+ return math .Min (target , iterativeCeiling )
196263}
197264
198- func iterativeMetricTarget (record Record , metricPct float64 ) float64 {
199- if record .DataTotalMiB > 0 && record .DataRemainingMiB >= 0 {
200- remainingRatio := clampFloat (record .DataRemainingMiB / record .DataTotalMiB , 0.0001 , 1 )
201- shaped := 1 - math .Log1p (remainingRatio * 9 )/ math .Log (10 )
202- return clampFloat (iterativeFloor + shaped * (iterativeCeiling - iterativeFloor ), iterativeFloor , iterativeCeiling )
265+ func iterativeMetricRatio (state * State ) float64 {
266+ if state .InitialRemaining <= state .Threshold {
267+ return 1
203268 }
204- return clampFloat (iterativeFloor + (metricPct / 100.0 )* (iterativeCeiling - iterativeFloor ), iterativeFloor , iterativeCeiling )
205- }
206269
207- func isIterative (record Record ) bool {
208- return record .HasIteration && record .Iteration > 0
270+ current := math .Max (state .SmoothedRemaining , state .Threshold )
271+ initial := math .Max (state .InitialRemaining , state .Threshold )
272+ base := math .Log (initial / state .Threshold )
273+ if base <= 0 {
274+ return 1
275+ }
276+
277+ ratio := 1 - math .Log (current / state .Threshold )/ base
278+ return clampFloat (ratio , 0 , 1 )
209279}
210280
211- func smoothProgress (current int32 , target float64 , iterative bool , throttle float64 , sinceLast time.Duration , metricAdvanced , initial bool ) int32 {
212- delta := target - float64 (current )
213- if delta <= 0 {
214- return current
281+ func stageCap (iterative bool ) float64 {
282+ if ! iterative {
283+ return bulkCeiling
215284 }
285+ return iterativeCeiling
286+ }
216287
217- if ! initial {
218- minInterval := bulkUpdateInterval
219- if iterative {
220- minInterval = iterUpdateInterval
221- }
222- if sinceLast > 0 && sinceLast < minInterval {
223- return current
224- }
288+ func stallWindow (record Record , state * State , iterative bool ) float64 {
289+ if ! iterative {
290+ return bulkStallSeconds
225291 }
226292
227- factor := 0.40
228- maxStep := 6.0
229- if iterative {
230- factor = 0.28
231- maxStep = 4
232- if throttle > 0 {
233- maxStep += math .Round (throttle * 2 )
234- }
293+ if state .Progress >= int32 (iterativeCeiling )- 2 {
294+ return 24.0
235295 }
236- if metricAdvanced {
237- maxStep += 1
296+ if state . Progress >= int32 ( iterativeCeiling ) - 5 {
297+ return 14.0
238298 }
239- step := math .Max (1 , math .Round (delta * factor ))
240- step = math .Min (step , maxStep )
241- return current + int32 (step )
242- }
243-
244- func applyStatefulStall (record Record , state * State , current int32 , iterative bool ) int32 {
245- window := bulkStallWindow
246- if iterative {
247- window = iterStallWindow
299+ if state .SmoothedRemaining > 0 && state .SmoothedRemaining <= state .Threshold {
300+ return finalStallSeconds
248301 }
249- lastMetricAt := state .LastMetricAt
250- if lastMetricAt .IsZero () {
251- lastMetricAt = record .Now
302+
303+ window := iterStallSeconds - 3 * record .Throttle
304+ if window < finalStallSeconds {
305+ return finalStallSeconds
252306 }
253- if record .Now .Sub (lastMetricAt ) < window {
254- return current
307+ return window
308+ }
309+
310+ func isIterative (record Record ) bool {
311+ return record .HasIteration && record .Iteration > 0
312+ }
313+
314+ func maxRemaining (record Record ) float64 {
315+ if record .DataRemainingMiB > 0 {
316+ return record .DataRemainingMiB
255317 }
256- bump := int32 (1 )
257- if iterative && record .HasThrottle && record .Throttle >= 0.5 {
258- bump = 2
318+ if record .DataTotalMiB > 0 && record .DataProcessedMiB >= 0 {
319+ r := record .DataTotalMiB - record .DataProcessedMiB
320+ if r > 0 {
321+ return r
322+ }
259323 }
260- return clampSyncRange ( current + bump )
324+ return 0
261325}
262326
263327func updateMetricState (record Record , state * State ) {
@@ -286,11 +350,15 @@ func almostEqual(a, b float64) bool {
286350 return math .Abs (a - b ) < 0.01
287351}
288352
289- func maxInt32 (a , b int32 ) int32 {
290- if a > b {
291- return a
353+ func clampPercent (v float64 ) int32 {
354+ i := int32 (v )
355+ if i < SyncRangeMin {
356+ return SyncRangeMin
357+ }
358+ if i > SyncRangeMax {
359+ return SyncRangeMax
292360 }
293- return b
361+ return i
294362}
295363
296364func clampFloat (v , minV , maxV float64 ) float64 {
0 commit comments