penguin/monok8s

k8s image for Mono Gateway Dev Kit

clitools/pkg/controller/osimage/progress.go

raw ยท 2506 bytes

package osimage

import (
	"k8s.io/klog/v2"
	"sync"
	"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 {
	mu       sync.Mutex
	interval time.Duration
	lastRun  time.Time
	inFlight bool
}

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 {
	u.mu.Lock()
	now := time.Now()

	if u.inFlight {
		u.mu.Unlock()
		return nil
	}

	if !u.lastRun.IsZero() && now.Sub(u.lastRun) < u.interval {
		u.mu.Unlock()
		return nil
	}

	u.lastRun = now
	u.inFlight = true
	u.mu.Unlock()

	defer func() {
		u.mu.Lock()
		u.inFlight = false
		u.mu.Unlock()
	}()

	return fn()
}