package osimage import ( "k8s.io/klog/v2" "time" ) type progressState struct { lastTime time.Time lastPercent int64 lastBucket int64 } type ProgressLogger struct { minInterval time.Duration bucketSize int64 states map[string]*progressState } func NewProgressLogger(minSeconds int, bucketSize int64) *ProgressLogger { if minSeconds < 0 { minSeconds = 0 } if bucketSize <= 0 { bucketSize = 10 } return &ProgressLogger{ minInterval: time.Duration(minSeconds) * time.Second, bucketSize: bucketSize, states: make(map[string]*progressState), } } func (l *ProgressLogger) state(stage string) *progressState { s, ok := l.states[stage] if ok { return s } s = &progressState{ lastPercent: -1, lastBucket: -1, } l.states[stage] = s return s } func (l *ProgressLogger) Log(p Progress) { if p.BytesTotal <= 0 { return } percent := PercentOf(p.BytesComplete, p.BytesTotal) now := time.Now() bucket := percent / l.bucketSize s := l.state(p.Stage) // Always log first visible progress if s.lastPercent == -1 { s.lastPercent = percent s.lastBucket = bucket s.lastTime = now klog.V(4).InfoS(p.Stage, "progress", percent) return } // Always log completion once if percent == 100 && s.lastPercent < 100 { s.lastPercent = 100 s.lastBucket = 100 / l.bucketSize s.lastTime = now klog.V(4).InfoS(p.Stage, "progress", 100) return } // Log if we crossed a new milestone bucket if bucket > s.lastBucket { s.lastPercent = percent s.lastBucket = bucket s.lastTime = now klog.V(4).InfoS(p.Stage, "progress", percent) return } // Otherwise allow a timed refresh if progress moved if now.Sub(s.lastTime) >= l.minInterval && percent > s.lastPercent { s.lastPercent = percent s.lastTime = now klog.V(4).InfoS(p.Stage, "progress", percent) } } type TimeBasedUpdater struct { interval time.Duration lastRun time.Time } func NewTimeBasedUpdater(seconds int) *TimeBasedUpdater { if seconds <= 0 { seconds = 15 } return &TimeBasedUpdater{ interval: time.Duration(seconds) * time.Second, } } func (u *TimeBasedUpdater) Run(fn func() error) error { now := time.Now() if !u.lastRun.IsZero() && now.Sub(u.lastRun) < u.interval { return nil } if err := fn(); err != nil { return err } u.lastRun = now return nil }