a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
89 lines
2.6 KiB
Go
89 lines
2.6 KiB
Go
package progress
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// Phase enumerates the typed sub-phases of an index / enrich run, so
|
|
// progress labels are consistent across reporters and surfaces instead of
|
|
// ad-hoc strings. They double as human-readable stage labels.
|
|
type Phase string
|
|
|
|
const (
|
|
PhaseDiscover Phase = "discovering files"
|
|
PhaseParse Phase = "parsing"
|
|
PhaseResolve Phase = "resolving references"
|
|
PhaseInfer Phase = "linking symbols"
|
|
PhasePersist Phase = "persisting"
|
|
)
|
|
|
|
// FormatElapsed renders a duration as a compact human string: "45s",
|
|
// "5m 12s", "1h 20m".
|
|
func FormatElapsed(d time.Duration) string {
|
|
if d < 0 {
|
|
d = 0
|
|
}
|
|
switch {
|
|
case d < time.Minute:
|
|
return fmt.Sprintf("%ds", int(d.Seconds()+0.5))
|
|
case d < time.Hour:
|
|
m := int(d / time.Minute)
|
|
s := int((d % time.Minute) / time.Second)
|
|
return fmt.Sprintf("%dm %02ds", m, s)
|
|
default:
|
|
h := int(d / time.Hour)
|
|
m := int((d % time.Hour) / time.Minute)
|
|
return fmt.Sprintf("%dh %02dm", h, m)
|
|
}
|
|
}
|
|
|
|
// ETA returns the throughput (items/sec) and estimated time remaining given
|
|
// a start time and within-phase counters. A zero/unknown total yields a
|
|
// zero eta. Rate is 0 until some time and work have elapsed.
|
|
func ETA(start time.Time, current, total int) (itemsPerSec float64, eta time.Duration) {
|
|
elapsed := time.Since(start)
|
|
if elapsed <= 0 || current <= 0 {
|
|
return 0, 0
|
|
}
|
|
itemsPerSec = float64(current) / elapsed.Seconds()
|
|
if total > current && itemsPerSec > 0 {
|
|
remaining := float64(total - current)
|
|
eta = time.Duration(remaining/itemsPerSec) * time.Second
|
|
}
|
|
return itemsPerSec, eta
|
|
}
|
|
|
|
// ProgressStats bundles the derived progress metrics for a phase, ready to
|
|
// drop into a readiness / health payload.
|
|
type ProgressStats struct {
|
|
Phase string `json:"phase"`
|
|
Current int `json:"current"`
|
|
Total int `json:"total"`
|
|
Percent int `json:"percent"`
|
|
ItemsPerSec float64 `json:"items_per_sec"`
|
|
Elapsed string `json:"elapsed"`
|
|
ETA string `json:"eta,omitempty"`
|
|
}
|
|
|
|
// Stats computes the derived progress metrics for a phase from its start
|
|
// time and counters — percent, throughput, human-formatted elapsed, and a
|
|
// human-formatted ETA (omitted when the total is unknown).
|
|
func Stats(phase string, start time.Time, current, total int) ProgressStats {
|
|
rate, eta := ETA(start, current, total)
|
|
ps := ProgressStats{
|
|
Phase: phase,
|
|
Current: current,
|
|
Total: total,
|
|
ItemsPerSec: rate,
|
|
Elapsed: FormatElapsed(time.Since(start)),
|
|
}
|
|
if total > 0 {
|
|
ps.Percent = int(float64(current) / float64(total) * 100)
|
|
}
|
|
if eta > 0 {
|
|
ps.ETA = FormatElapsed(eta)
|
|
}
|
|
return ps
|
|
}
|