fix(tui): realtime ETA updates during phase 3 cluster restore
Previously, the ETA during phase 3 (database restores) would appear to hang because dbPhaseElapsed was only updated when a new database started restoring, not during the restore operation itself. Fixed by: - Added phase3StartTime to track when phase 3 begins - Calculate dbPhaseElapsed in realtime using time.Since(phase3StartTime) - ETA now updates every 100ms tick instead of only on database transitions This ensures the elapsed time and ETA display continuously update during long-running database restores.
This commit is contained in:
@@ -4,8 +4,8 @@ This directory contains pre-compiled binaries for the DB Backup Tool across mult
|
|||||||
|
|
||||||
## Build Information
|
## Build Information
|
||||||
- **Version**: 3.42.50
|
- **Version**: 3.42.50
|
||||||
- **Build Time**: 2026-01-18_11:39:42_UTC
|
- **Build Time**: 2026-01-18_17:17:17_UTC
|
||||||
- **Git Commit**: 59a717a
|
- **Git Commit**: a0a401c
|
||||||
|
|
||||||
## Recent Updates (v1.1.0)
|
## Recent Updates (v1.1.0)
|
||||||
- ✅ Fixed TUI progress display with line-by-line output
|
- ✅ Fixed TUI progress display with line-by-line output
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ type sharedProgressState struct {
|
|||||||
// Timing info for database restore phase
|
// Timing info for database restore phase
|
||||||
dbPhaseElapsed time.Duration // Elapsed time since restore phase started
|
dbPhaseElapsed time.Duration // Elapsed time since restore phase started
|
||||||
dbAvgPerDB time.Duration // Average time per database restore
|
dbAvgPerDB time.Duration // Average time per database restore
|
||||||
|
phase3StartTime time.Time // When phase 3 started (for realtime ETA calculation)
|
||||||
|
|
||||||
// Overall phase tracking (1=Extract, 2=Globals, 3=Databases)
|
// Overall phase tracking (1=Extract, 2=Globals, 3=Databases)
|
||||||
overallPhase int
|
overallPhase int
|
||||||
@@ -190,12 +191,12 @@ func clearCurrentRestoreProgress() {
|
|||||||
currentRestoreProgressState = nil
|
currentRestoreProgressState = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCurrentRestoreProgress() (bytesTotal, bytesDone int64, description string, hasUpdate bool, dbTotal, dbDone int, speed float64, dbPhaseElapsed, dbAvgPerDB time.Duration, currentDB string, overallPhase int, extractionDone bool, dbBytesTotal, dbBytesDone int64) {
|
func getCurrentRestoreProgress() (bytesTotal, bytesDone int64, description string, hasUpdate bool, dbTotal, dbDone int, speed float64, dbPhaseElapsed, dbAvgPerDB time.Duration, currentDB string, overallPhase int, extractionDone bool, dbBytesTotal, dbBytesDone int64, phase3StartTime time.Time) {
|
||||||
currentRestoreProgressMu.Lock()
|
currentRestoreProgressMu.Lock()
|
||||||
defer currentRestoreProgressMu.Unlock()
|
defer currentRestoreProgressMu.Unlock()
|
||||||
|
|
||||||
if currentRestoreProgressState == nil {
|
if currentRestoreProgressState == nil {
|
||||||
return 0, 0, "", false, 0, 0, 0, 0, 0, "", 0, false, 0, 0
|
return 0, 0, "", false, 0, 0, 0, 0, 0, "", 0, false, 0, 0, time.Time{}
|
||||||
}
|
}
|
||||||
|
|
||||||
currentRestoreProgressState.mu.Lock()
|
currentRestoreProgressState.mu.Lock()
|
||||||
@@ -204,13 +205,20 @@ func getCurrentRestoreProgress() (bytesTotal, bytesDone int64, description strin
|
|||||||
// Calculate rolling window speed
|
// Calculate rolling window speed
|
||||||
speed = calculateRollingSpeed(currentRestoreProgressState.speedSamples)
|
speed = calculateRollingSpeed(currentRestoreProgressState.speedSamples)
|
||||||
|
|
||||||
|
// Calculate realtime phase elapsed if we have a phase 3 start time
|
||||||
|
dbPhaseElapsed = currentRestoreProgressState.dbPhaseElapsed
|
||||||
|
if !currentRestoreProgressState.phase3StartTime.IsZero() {
|
||||||
|
dbPhaseElapsed = time.Since(currentRestoreProgressState.phase3StartTime)
|
||||||
|
}
|
||||||
|
|
||||||
return currentRestoreProgressState.bytesTotal, currentRestoreProgressState.bytesDone,
|
return currentRestoreProgressState.bytesTotal, currentRestoreProgressState.bytesDone,
|
||||||
currentRestoreProgressState.description, currentRestoreProgressState.hasUpdate,
|
currentRestoreProgressState.description, currentRestoreProgressState.hasUpdate,
|
||||||
currentRestoreProgressState.dbTotal, currentRestoreProgressState.dbDone, speed,
|
currentRestoreProgressState.dbTotal, currentRestoreProgressState.dbDone, speed,
|
||||||
currentRestoreProgressState.dbPhaseElapsed, currentRestoreProgressState.dbAvgPerDB,
|
dbPhaseElapsed, currentRestoreProgressState.dbAvgPerDB,
|
||||||
currentRestoreProgressState.currentDB, currentRestoreProgressState.overallPhase,
|
currentRestoreProgressState.currentDB, currentRestoreProgressState.overallPhase,
|
||||||
currentRestoreProgressState.extractionDone,
|
currentRestoreProgressState.extractionDone,
|
||||||
currentRestoreProgressState.dbBytesTotal, currentRestoreProgressState.dbBytesDone
|
currentRestoreProgressState.dbBytesTotal, currentRestoreProgressState.dbBytesDone,
|
||||||
|
currentRestoreProgressState.phase3StartTime
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculateRollingSpeed calculates speed from recent samples (last 5 seconds)
|
// calculateRollingSpeed calculates speed from recent samples (last 5 seconds)
|
||||||
@@ -357,6 +365,10 @@ func executeRestoreWithTUIProgress(parentCtx context.Context, cfg *config.Config
|
|||||||
progressState.overallPhase = 3
|
progressState.overallPhase = 3
|
||||||
progressState.extractionDone = true
|
progressState.extractionDone = true
|
||||||
progressState.hasUpdate = true
|
progressState.hasUpdate = true
|
||||||
|
// Set phase 3 start time on first callback (for realtime ETA calculation)
|
||||||
|
if progressState.phase3StartTime.IsZero() {
|
||||||
|
progressState.phase3StartTime = time.Now()
|
||||||
|
}
|
||||||
// Clear byte progress when switching to db progress
|
// Clear byte progress when switching to db progress
|
||||||
progressState.bytesTotal = 0
|
progressState.bytesTotal = 0
|
||||||
progressState.bytesDone = 0
|
progressState.bytesDone = 0
|
||||||
@@ -375,6 +387,10 @@ func executeRestoreWithTUIProgress(parentCtx context.Context, cfg *config.Config
|
|||||||
progressState.dbPhaseElapsed = phaseElapsed
|
progressState.dbPhaseElapsed = phaseElapsed
|
||||||
progressState.dbAvgPerDB = avgPerDB
|
progressState.dbAvgPerDB = avgPerDB
|
||||||
progressState.hasUpdate = true
|
progressState.hasUpdate = true
|
||||||
|
// Set phase 3 start time on first callback (for realtime ETA calculation)
|
||||||
|
if progressState.phase3StartTime.IsZero() {
|
||||||
|
progressState.phase3StartTime = time.Now()
|
||||||
|
}
|
||||||
// Clear byte progress when switching to db progress
|
// Clear byte progress when switching to db progress
|
||||||
progressState.bytesTotal = 0
|
progressState.bytesTotal = 0
|
||||||
progressState.bytesDone = 0
|
progressState.bytesDone = 0
|
||||||
@@ -392,6 +408,10 @@ func executeRestoreWithTUIProgress(parentCtx context.Context, cfg *config.Config
|
|||||||
progressState.overallPhase = 3
|
progressState.overallPhase = 3
|
||||||
progressState.extractionDone = true
|
progressState.extractionDone = true
|
||||||
progressState.hasUpdate = true
|
progressState.hasUpdate = true
|
||||||
|
// Set phase 3 start time on first callback (for realtime ETA calculation)
|
||||||
|
if progressState.phase3StartTime.IsZero() {
|
||||||
|
progressState.phase3StartTime = time.Now()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Store progress state in a package-level variable for the ticker to access
|
// Store progress state in a package-level variable for the ticker to access
|
||||||
@@ -447,7 +467,8 @@ func (m RestoreExecutionModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.elapsed = time.Since(m.startTime)
|
m.elapsed = time.Since(m.startTime)
|
||||||
|
|
||||||
// Poll shared progress state for real-time updates
|
// Poll shared progress state for real-time updates
|
||||||
bytesTotal, bytesDone, description, hasUpdate, dbTotal, dbDone, speed, dbPhaseElapsed, dbAvgPerDB, currentDB, overallPhase, extractionDone, dbBytesTotal, dbBytesDone := getCurrentRestoreProgress()
|
// Note: dbPhaseElapsed is now calculated in realtime inside getCurrentRestoreProgress()
|
||||||
|
bytesTotal, bytesDone, description, hasUpdate, dbTotal, dbDone, speed, dbPhaseElapsed, dbAvgPerDB, currentDB, overallPhase, extractionDone, dbBytesTotal, dbBytesDone, _ := getCurrentRestoreProgress()
|
||||||
if hasUpdate && bytesTotal > 0 && !extractionDone {
|
if hasUpdate && bytesTotal > 0 && !extractionDone {
|
||||||
// Phase 1: Extraction
|
// Phase 1: Extraction
|
||||||
m.bytesTotal = bytesTotal
|
m.bytesTotal = bytesTotal
|
||||||
|
|||||||
Reference in New Issue
Block a user